Overview

Namespaces

  • Charcoal
    • Loader
    • Model
      • Service
      • ServiceProvider
    • Source
      • Database
    • Validator

Classes

  • Charcoal\Loader\CollectionLoader
  • Charcoal\Loader\FileLoader
  • Charcoal\Model\AbstractMetadata
  • Charcoal\Model\AbstractModel
  • Charcoal\Model\Collection
  • Charcoal\Model\Model
  • Charcoal\Model\ModelMetadata
  • Charcoal\Model\ModelValidator
  • Charcoal\Model\Service\MetadataLoader
  • Charcoal\Model\Service\ModelBuilder
  • Charcoal\Model\Service\ModelLoader
  • Charcoal\Model\Service\ModelLoaderBuilder
  • Charcoal\Model\ServiceProvider\ModelServiceProvider
  • Charcoal\Source\AbstractSource
  • Charcoal\Source\Database\DatabaseFilter
  • Charcoal\Source\Database\DatabaseOrder
  • Charcoal\Source\Database\DatabasePagination
  • Charcoal\Source\DatabaseSource
  • Charcoal\Source\DatabaseSourceConfig
  • Charcoal\Source\Filter
  • Charcoal\Source\Order
  • Charcoal\Source\Pagination
  • Charcoal\Source\SourceConfig
  • Charcoal\Validator\AbstractValidator
  • Charcoal\Validator\ValidatorResult

Interfaces

  • Charcoal\Model\CollectionInterface
  • Charcoal\Model\DescribableInterface
  • Charcoal\Model\MetadataInterface
  • Charcoal\Model\ModelInterface
  • Charcoal\Source\DatabaseSourceInterface
  • Charcoal\Source\FilterInterface
  • Charcoal\Source\OrderInterface
  • Charcoal\Source\PaginationInterface
  • Charcoal\Source\SourceInterface
  • Charcoal\Source\StorableInterface
  • Charcoal\Validator\ValidatableInterface
  • Charcoal\Validator\ValidatorInterface

Traits

  • Charcoal\Model\DescribableTrait
  • Charcoal\Source\StorableTrait
  • Charcoal\Validator\ValidatableTrait
  • Overview
  • Namespace
  • Class
  1: <?php
  2: 
  3: namespace Charcoal\Source;
  4: 
  5: use Exception;
  6: use InvalidArgumentException;
  7: 
  8: // From PSR-3
  9: use Psr\Log\LoggerAwareInterface;
 10: use Psr\Log\LoggerAwareTrait;
 11: 
 12: // From 'charcoal-config'
 13: use Charcoal\Config\ConfigurableInterface;
 14: use Charcoal\Config\ConfigurableTrait;
 15: 
 16: // From 'charcoal-core'
 17: use Charcoal\Model\ModelInterface;
 18: 
 19: use Charcoal\Source\SourceConfig;
 20: use Charcoal\Source\SourceInterface;
 21: use Charcoal\Source\Filter;
 22: use Charcoal\Source\FilterInterface;
 23: use Charcoal\Source\Order;
 24: use Charcoal\Source\OrderInterface;
 25: use Charcoal\Source\Pagination;
 26: use Charcoal\Source\PaginationInterface;
 27: 
 28: /**
 29:  * Full implementation, as abstract class, of the SourceInterface.
 30:  */
 31: abstract class AbstractSource implements
 32:     SourceInterface,
 33:     ConfigurableInterface,
 34:     LoggerAwareInterface
 35: {
 36:     use ConfigurableTrait;
 37:     use LoggerAwareTrait;
 38: 
 39:     /**
 40:      * @var ModelInterface $model
 41:      */
 42:     private $model = null;
 43: 
 44:     /**
 45:      * @var array $properties
 46:      */
 47:     private $properties = [];
 48: 
 49:     /**
 50:      * Array of `Filter` objects
 51:      * @var array $filters
 52:      */
 53:     protected $filters = [];
 54: 
 55:     /**
 56:      * Array of `Order` object
 57:      * @var array $orders
 58:      */
 59:     protected $orders = [];
 60: 
 61:     /**
 62:      * The `Pagination` object
 63:      * @var Pagination|null $pagination
 64:      */
 65:     protected $pagination = null;
 66: 
 67:     /**
 68:      * @param array|\ArrayAccess $dependencies The class dependencies.
 69:      * @return void
 70:      */
 71:     public function __construct($dependencies)
 72:     {
 73:         $this->setLogger($dependencies['logger']);
 74:     }
 75: 
 76:     /**
 77:      * Reset everything but the model.
 78:      *
 79:      * @return AbstractSource Chainable
 80:      */
 81:     public function reset()
 82:     {
 83:         $this->properties = [];
 84:         $this->filters = [];
 85:         $this->orders = [];
 86:         $this->pagination = null;
 87:         return $this;
 88:     }
 89: 
 90:     /**
 91:      * Initialize the source's properties with an array of data.
 92:      *
 93:      * @param array $data The source data.
 94:      * @return AbstractSource Chainable
 95:      */
 96:     public function setData(array $data)
 97:     {
 98:         foreach ($data as $prop => $val) {
 99:             $func = [$this, $this->setter($prop)];
100:             if (is_callable($func)) {
101:                 call_user_func($func, $val);
102:                 unset($data[$prop]);
103:             } else {
104:                 $this->{$prop} = $val;
105:             }
106:         }
107:         return $this;
108:     }
109: 
110:     /**
111:      * Set the source's Model.
112:      *
113:      * @param ModelInterface $model The source's model.
114:      * @return AbstractSource Chainable
115:      */
116:     public function setModel(ModelInterface $model)
117:     {
118:         $this->model = $model;
119:         return $this;
120:     }
121: 
122:     /**
123:      * Return the source's Model.
124:      *
125:      * @throws Exception If not model was previously set.
126:      * @return ModelInterface
127:      */
128:     public function model()
129:     {
130:         if ($this->model === null) {
131:             throw new Exception(
132:                 'No model set.'
133:             );
134:         }
135:         return $this->model;
136:     }
137: 
138:     /**
139:      * @return boolean
140:      */
141:     public function hasModel()
142:     {
143:         return ($this->model !== null);
144:     }
145: 
146:     /**
147:      * Set the properties of the source to fetch.
148:      *
149:      * This method accepts an array of property identifiers (property ident, as string)
150:      * that will, if supported, be fetched from the source.
151:      *
152:      * If no properties are set, it is assumed that all the Model's properties are to be fetched.
153:      *
154:      * @param array $properties The properties.
155:      * @return ColelectionLoader Chainable
156:      */
157:     public function setProperties(array $properties)
158:     {
159:         $this->properties = [];
160:         foreach ($properties as $p) {
161:             $this->addProperty($p);
162:         }
163:         return $this;
164:     }
165: 
166:     /**
167:      * @return array
168:      */
169:     public function properties()
170:     {
171:         return $this->properties;
172:     }
173: 
174:     /**
175:      * @param string $property Property ident.
176:      * @throws InvalidArgumentException If property is not a string or empty.
177:      * @return CollectionLoader Chainable
178:      */
179:     public function addProperty($property)
180:     {
181:         if (!is_string($property)) {
182:             throw new InvalidArgumentException(
183:                 'Property must be a string.'
184:             );
185:         }
186:         if ($property=='') {
187:             throw new InvalidArgumentException(
188:                 'Property can not be empty.'
189:             );
190:         }
191:         $this->properties[] = $property;
192:         return $this;
193:     }
194: 
195:     /**
196:      * @param array $filters The filters to set.
197:      * @return Collection Chainable
198:      */
199:     public function setFilters(array $filters)
200:     {
201:         $this->filters = [];
202:         foreach ($filters as $f) {
203:             $this->addFilter($f);
204:         }
205:         return $this;
206:     }
207: 
208:     /**
209:      * @return array
210:      */
211:     public function filters()
212:     {
213:         return $this->filters;
214:     }
215: 
216:     /**
217:      * Add a collection filter to the loader.
218:      *
219:      * There are 3 different ways of adding a filter:
220:      * - as a `Filter` object, in which case it will be added directly.
221:      *   - `addFilter($obj);`
222:      * - as an array of options, which will be used to build the `Filter` object
223:      *   - `addFilter(['property' => 'foo', 'val' => 42, 'operator' => '<=']);`
224:      * - as 3 parameters: `property`, `val` and `options`
225:      *   - `addFilter('foo', 42, ['operator' => '<=']);`
226:      *
227:      * @param string|array|Filter $param   The filter property, or a Filter object / array.
228:      * @param mixed               $val     Optional: Only used if the first argument is a string.
229:      * @param array               $options Optional: Only used if the first argument is a string.
230:      * @throws InvalidArgumentException If property is not a string or empty.
231:      * @return CollectionLoader (Chainable)
232:      */
233:     public function addFilter($param, $val = null, array $options = null)
234:     {
235:         if ($param instanceof FilterInterface) {
236:             $filter = $param;
237:         } elseif (is_array($param)) {
238:             $filter = $this->createFilter();
239:             $filter->setData($param);
240:         } elseif (is_string($param) && $val !== null) {
241:             $filter = $this->createFilter();
242:             $filter->setProperty($param);
243:             $filter->setVal($val);
244:             if (is_array($options)) {
245:                 $filter->setData($options);
246:             }
247:         } else {
248:             throw new InvalidArgumentException(
249:                 'Parameter must be an array or a property ident.'
250:             );
251:         }
252: 
253:         if ($this->hasModel()) {
254:             $property = $filter->property();
255:             if ($property) {
256:                 $p = $this->model()->p($property);
257:                 if ($p) {
258:                     if ($p->l10n()) {
259:                         $filter->setProperty($p->l10nIdent());
260:                     }
261: 
262:                     if ($p->multiple()) {
263:                         $filter->setOperator('FIND_IN_SET');
264:                     }
265:                 }
266:             }
267:         }
268: 
269:         $this->filters[] = $filter;
270: 
271:         return $this;
272:     }
273: 
274:     /**
275:      * @return FilterInterface
276:      */
277:     protected function createFilter()
278:     {
279:         $filter = new Filter();
280:         return $filter;
281:     }
282: 
283:     /**
284:      * @param array $orders The orders to set.
285:      * @return CollectionLoader Chainable
286:      */
287:     public function setOrders(array $orders)
288:     {
289:         $this->orders = [];
290:         foreach ($orders as $o) {
291:             $this->addOrder($o);
292:         }
293:         return $this;
294:     }
295: 
296:     /**
297:      * @return array
298:      */
299:     public function orders()
300:     {
301:         return $this->orders;
302:     }
303: 
304:     /**
305:      * @param string|array|Order $param        The order property, or an Order object / array.
306:      * @param string             $mode         Optional.
307:      * @param array              $orderOptions Optional.
308:      * @throws InvalidArgumentException If the param argument is invalid.
309:      * @return CollectionLoader Chainable
310:      */
311:     public function addOrder($param, $mode = 'asc', array $orderOptions = null)
312:     {
313:         if ($param instanceof OrderInterface) {
314:             $order = $param;
315:         } elseif (is_array($param)) {
316:             $order = $this->createOrder();
317:             $order->setData($param);
318:         } elseif (is_string($param)) {
319:             $order = $this->createOrder();
320:             $order->setProperty($param);
321:             $order->setMode($mode);
322:             if (isset($orderOptions['values'])) {
323:                 $order->setValues($orderOptions['values']);
324:             }
325:         } else {
326:             throw new InvalidArgumentException(
327:                 'Parameter must be an OrderInterface object or a property ident.'
328:             );
329:         }
330: 
331:         if ($this->hasModel()) {
332:             $property = $order->property();
333:             if ($property) {
334:                 $p = $this->model()->p($property);
335:                 if ($p) {
336:                     if ($p->l10n()) {
337:                         $order->setProperty($p->l10nIdent());
338:                     }
339:                 }
340:             }
341:         }
342: 
343:         $this->orders[] = $order;
344: 
345:         return $this;
346:     }
347: 
348:     /**
349:      * @return OrderInterface
350:      */
351:     protected function createOrder()
352:     {
353:         $order = new Order();
354:         return $order;
355:     }
356: 
357:     /**
358:      * @param mixed $param The pagination object or array.
359:      * @throws InvalidArgumentException If the argument is not an object or array.
360:      * @return CollectionLoader Chainable
361:      */
362:     public function setPagination($param)
363:     {
364:         if ($param instanceof PaginationInterface) {
365:             $this->pagination = $param;
366:         } elseif (is_array($param)) {
367:             $pagination = $this->createPagination();
368:             $pagination->setData($param);
369:             $this->pagination = $pagination;
370:         } else {
371:             throw new InvalidArgumentException(
372:                 'Can not set pagination, invalid argument.'
373:             );
374:         }
375:         return $this;
376:     }
377: 
378:     /**
379:      * Get the pagination object.
380:      *
381:      * If the pagination wasn't set previously, a new (default / blank) Pagination object will be created.
382:      * (Always return a `PaginationInterface` object)
383:      *
384:      * @return Pagination
385:      */
386:     public function pagination()
387:     {
388:         if ($this->pagination === null) {
389:             $this->pagination = $this->createPagination();
390:         }
391:         return $this->pagination;
392:     }
393: 
394:     /**
395:      * @return PaginationInterface
396:      */
397:     protected function createPagination()
398:     {
399:         $pagination = new Pagination();
400:         return $pagination;
401:     }
402: 
403:     /**
404:      * @param integer $page The page number.
405:      * @throws InvalidArgumentException If the page argument is not numeric.
406:      * @return CollectionLoader Chainable
407:      */
408:     public function setPage($page)
409:     {
410:         if (!is_numeric($page)) {
411:             throw new InvalidArgumentException(
412:                 'Page must be an integer.'
413:             );
414:         }
415:         $this->pagination()->setPage((int)$page);
416:         return $this;
417:     }
418: 
419:     /**
420:      * @return integer
421:      */
422:     public function page()
423:     {
424:         return $this->pagination()->page();
425:     }
426: 
427:     /**
428:      * @param integer $num The number of items to retrieve per page.
429:      * @throws InvalidArgumentException If the num per page argument is not numeric.
430:      * @return CollectionLoader Chainable
431:      */
432:     public function setNumPerPage($num)
433:     {
434:         if (!is_numeric($num)) {
435:             throw new InvalidArgumentException(
436:                 'Num must be an integer.'
437:             );
438:         }
439:         $this->pagination()->setNumPerPage((int)$num);
440:         return $this;
441:     }
442: 
443:     /**
444:      * @return integer
445:      */
446:     public function numPerPage()
447:     {
448:         return $this->pagination()->numPerPage();
449:     }
450: 
451:     /**
452:      * ConfigurableTrait > createConfig()
453:      *
454:      * @param array $data Optional.
455:      * @return SourceConfig
456:      */
457:     public function createConfig(array $data = null)
458:     {
459:         $config = new SourceConfig();
460:         if (is_array($data)) {
461:             $config->merge($data);
462:         }
463:         return $config;
464:     }
465: 
466:     /**
467:      * @param mixed             $ident The ID of the item to load.
468:      * @param StorableInterface $item  Optional item to load into.
469:      * @return StorableInterface
470:      */
471:     abstract public function loadItem($ident, StorableInterface $item = null);
472: 
473:     /**
474:      * @param StorableInterface|null $item The model to load items from.
475:      * @return array
476:      */
477:     abstract public function loadItems(StorableInterface $item = null);
478: 
479:     /**
480:      * Save an item (create a new row) in storage.
481:      *
482:      * @param StorableInterface $item The object to save.
483:      * @return mixed The created item ID, or false in case of an error.
484:      */
485:     abstract public function saveItem(StorableInterface $item);
486: 
487:     /**
488:      * Update an item in storage.
489:      *
490:      * @param StorableInterface $item       The object to update.
491:      * @param array             $properties The list of properties to update, if not all.
492:      * @return boolean Success / Failure
493:      */
494:     abstract public function updateItem(StorableInterface $item, array $properties = null);
495: 
496:     /**
497:      * Delete an item from storage
498:      *
499:      * @param StorableInterface $item Optional item to delete. If none, the current model object will be used..
500:      * @return boolean Success / Failure
501:      */
502:     abstract public function deleteItem(StorableInterface $item = null);
503: 
504:     /**
505:      * Allow an object to define how the key getter are called.
506:      *
507:      * @param string $key  The key to get the getter from.
508:      * @param string $case Optional. The type of case to return. camel, pascal or snake.
509:      * @return string The getter method name, for a given key.
510:      */
511:     protected function getter($key, $case = 'camel')
512:     {
513:         $getter = $key;
514: 
515:         if ($case == 'camel') {
516:             return $this->camelize($getter);
517:         } elseif ($case == 'pascal') {
518:             return $this->pascalize($getter);
519:         } else {
520:             return $getter;
521:         }
522:     }
523: 
524:     /**
525:      * Allow an object to define how the key setter are called.
526:      *
527:      * @param string $key  The key to get the setter from.
528:      * @param string $case Optional. The type of case to return. camel, pascal or snake.
529:      * @return string The setter method name, for a given key.
530:      */
531:     protected function setter($key, $case = 'camel')
532:     {
533:         $setter = 'set_'.$key;
534: 
535:         if ($case == 'camel') {
536:             return $this->camelize($setter);
537:         } elseif ($case == 'pascal') {
538:             return $this->pascalize($setter);
539:         } else {
540:             return $setter;
541:         }
542:     }
543: 
544:     /**
545:      * Transform a snake_case string to camelCase.
546:      *
547:      * @param string $str The snake_case string to camelize.
548:      * @return string The camelCase string.
549:      */
550:     private function camelize($str)
551:     {
552:         return lcfirst($this->pascalize($str));
553:     }
554: 
555:     /**
556:      * Transform a snake_case string to PamelCase.
557:      *
558:      * @param string $str The snake_case string to pascalize.
559:      * @return string The PamelCase string.
560:      */
561:     private function pascalize($str)
562:     {
563:         return implode('', array_map('ucfirst', explode('_', $str)));
564:     }
565: }
566: 
API documentation generated by ApiGen