      1: <?php
      2: /**
      3:  * Copyright 2012-2014 Rackspace US, Inc.
      4:  *
      5:  * Licensed under the Apache License, Version 2.0 (the "License");
      6:  * you may not use this file except in compliance with the License.
      7:  * You may obtain a copy of the License at
      8:  *
      9:  * http://www.apache.org/licenses/LICENSE-2.0
     10:  *
     11:  * Unless required by applicable law or agreed to in writing, software
     12:  * distributed under the License is distributed on an "AS IS" BASIS,
     13:  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14:  * See the License for the specific language governing permissions and
     15:  * limitations under the License.
     16:  */
     17:
     18: namespace OpenCloud\Common\Collection;
     19:
     20: use Iterator;
     21: use OpenCloud\Common\Exceptions\InvalidArgumentError;
     22: use OpenCloud\Common\Log\Logger;
     23:
     24: class ResourceIterator extends ArrayCollection implements Iterator
     25: {
     26:     /**
     27:      * @var int Internal pointer of the iterator - reveals its current position.
     28:      */
     29:     protected $position;
     30:
     31:     /**
     32:      * @var object The parent object which resource models are instantiated from. The parent needs to have appropriate
     33:      *             methods to instantiate the particular object.
     34:      */
     35:     protected $resourceParent;
     36:
     37:     /**
     38:      * @var array The options for this iterator.
     39:      */
     40:     protected $options;
     41:
     42:     /**
     43:      * @var array Fallback defaults if options are not explicitly set or provided.
     44:      */
     45:     protected $defaults = array('limit.total' => 1000);
     46:
     47:     /**
     48:      * @var array Required options
     49:      */
     50:     protected $required = array();
     51:
     52:     public static function factory($parent, array $options = array(), array $data = array())
     53:     {
     54:         $iterator = new static($data);
     55:
     56:         $iterator->setResourceParent($parent)
     57:             ->setElements($data)
     58:             ->setOptions($iterator->parseOptions($options))
     59:             ->rewind();
     60:
     61:         return $iterator;
     62:     }
     63:
     64:     protected function parseOptions(array $options)
     65:     {
     66:         $options = $options + $this->defaults;
     67:
     68:         if ($missing = array_diff($this->required, array_keys($options))) {
     69:             throw new InvalidArgumentError(sprintf('%s is a required option', implode(',', $missing)));
     70:         }
     71:
     72:         return $options;
     73:     }
     74:
     75:     /**
     76:      * @param $parent
     77:      * @return $this
     78:      */
     79:     public function setResourceParent($parent)
     80:     {
     81:         $this->resourceParent = $parent;
     82:
     83:         return $this;
     84:     }
     85:
     86:     /**
     87:      * @param array $options
     88:      * @return $this
     89:      */
     90:     public function setOptions(array $options)
     91:     {
     92:         $this->options = $options;
     93:
     94:         return $this;
     95:     }
     96:
     97:     /**
     98:      * @return array Options for the resource iterator.
     99:      */
    100:     public function getOptions()
    101:     {
    102:         return $this->options;
    103:     }
    104:
    105:     /**
    106:      * Set a particular option.
    107:      *
    108:      * @param $key
    109:      * @param $value
    110:      * @return $this
    111:      */
    112:     public function setOption($key, $value)
    113:     {
    114:         $this->options[$key] = $value;
    115:
    116:         return $this;
    117:     }
    118:
    119:     /**
    120:      * @param $key
    121:      * @return null
    122:      */
    123:     public function getOption($key)
    124:     {
    125:         return (isset($this->options[$key])) ? $this->options[$key] : null;
    126:     }
    127:
    128:     /**
    129:      * This method is called after self::rewind() and self::next() to check if the current position is valid.
    130:      *
    131:      * @return bool
    132:      */
    133:     public function valid()
    134:     {
    135:         return $this->offsetExists($this->position) && $this->position < $this->getOption('limit.total');
    136:     }
    137:
    138:     /**
    139:      * Increment the current pointer by 1, and also update the current marker.
    140:      */
    141:     public function next()
    142:     {
    143:         $this->position++;
    144:
    145:         return $this->current();
    146:     }
    147:
    148:     /**
    149:      * Reset the pointer and current marker.
    150:      */
    151:     public function rewind()
    152:     {
    153:         $this->position = 0;
    154:     }
    155:
    156:     /**
    157:      * @return mixed
    158:      */
    159:     public function current()
    160:     {
    161:         return $this->constructResource($this->currentElement());
    162:     }
    163:
    164:     /**
    165:      * @return mixed
    166:      */
    167:     public function currentElement()
    168:     {
    169:         return $this->offsetGet($this->key());
    170:     }
    171:
    172:     /**
    173:      * Using a standard object, this method populates a resource model with all the object data. It does this using a
    174:      * whatever method the parent object has for resource creation.
    175:      *
    176:      * @param $object Standard object
    177:      * @return mixed
    178:      * @throws \OpenCloud\Common\Exceptions\CollectionException
    179:      */
    180:     public function constructResource($object)
    181:     {
    182:         $className = $this->getOption('resourceClass');
    183:
    184:         if (substr_count($className, '\\')) {
    185:             $array = explode('\\', $className);
    186:             $className = end($array);
    187:         }
    188:
    189:         $parent = $this->resourceParent;
    190:         $getter = sprintf('get%s', ucfirst($className));
    191:
    192:         if (method_exists($parent, $className)) {
    193:             // $parent->server($data)
    194:             return call_user_func(array($parent, $className), $object);
    195:         } elseif (method_exists($parent, $getter)) {
    196:             // $parent->getServer($data)
    197:             return call_user_func(array($parent, $getter), $object);
    198:         } elseif (method_exists($parent, 'resource')) {
    199:             // $parent->resource('Server', $data)
    200:             return $parent->resource($className, $object);
    201:         } else {
    202:             return $object;
    203:         }
    204:     }
    205:
    206:     /**
    207:      * Return the current position/internal pointer.
    208:      *
    209:      * @return int|mixed
    210:      */
    211:     public function key()
    212:     {
    213:         return $this->position;
    214:     }
    215:
    216:     public function getElement($offset)
    217:     {
    218:         return (!$this->offsetExists($offset)) ? false : $this->constructResource($this->offsetGet($offset));
    219:     }
    220:
    221:     /**
    222:      * @deprecated
    223:      */
    224:     public function first()
    225:     {
    226:         Logger::newInstance()->deprecated(__METHOD__, 'getElement');
    227:
    228:         return $this->getElement(0);
    229:     }
    230:
    231:     /**
    232:      * @todo Implement
    233:      */
    234:     public function sort()
    235:     {
    236:     }
    237:
    238:     public function search($callback)
    239:     {
    240:         $return = false;
    241:
    242:         if (!is_callable($callback)) {
    243:             throw new InvalidArgumentError('The provided argument must be a valid callback');
    244:         }
    245:
    246:         foreach ($this->elements as $element) {
    247:             $resource = $this->constructResource($element);
    248:             if (call_user_func($callback, $resource) === true) {
    249:                 $return = $resource;
    250:                 break;
    251:             }
    252:         }
    253:
    254:         return $return;
    255:     }
    256: }
    257: