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: // Dependencies from `PHP`
  6: use \Exception;
  7: use \InvalidArgumentException;
  8: 
  9: // Dependencies from `PHP` modules
 10: use \PDO;
 11: 
 12: // Intra-module (`charcoal-core`) dependencies
 13: use \Charcoal\Model\ModelInterface;
 14: 
 15: // Local namespace dependencies
 16: use \Charcoal\Source\AbstractSource;
 17: use \Charcoal\Source\DatabaseSourceConfig;
 18: use \Charcoal\Source\DatabaseSourceInterface;
 19: use \Charcoal\Source\Database\DatabaseFilter;
 20: use \Charcoal\Source\Database\DatabaseOrder;
 21: use \Charcoal\Source\Database\DatabasePagination;
 22: 
 23: /**
 24:  * Database Source, through PDO.
 25:  */
 26: class DatabaseSource extends AbstractSource implements DatabaseSourceInterface
 27: {
 28:     const DEFAULT_DB_HOSTNAME = 'localhost';
 29:     const DEFAULT_DB_TYPE = 'mysql';
 30: 
 31:     /**
 32:      * @var PDO
 33:      */
 34:     private $pdo;
 35: 
 36:     /**
 37:      * @var string $table
 38:      */
 39:     private $table = null;
 40: 
 41:     /**
 42:      * @var array $dbs
 43:      */
 44:     private static $db;
 45: 
 46:     /**
 47:      * @param array $data Class dependencies.
 48:      */
 49:     public function __construct(array $data)
 50:     {
 51:         $this->pdo = $data['pdo'];
 52: 
 53:         parent::__construct($data);
 54:     }
 55: 
 56:     /**
 57:      * Set the database's table to use.
 58:      *
 59:      * @param string $table The source table.
 60:      * @throws InvalidArgumentException If argument is not a string or alphanumeric/underscore.
 61:      * @return DatabaseSource Chainable
 62:      */
 63:     public function setTable($table)
 64:     {
 65:         if (!is_string($table)) {
 66:             throw new InvalidArgumentException(
 67:                 sprintf(
 68:                     'DatabaseSource::setTable() expects a string as table. (%s given). [%s]',
 69:                     gettype($table),
 70:                     get_class($this->model())
 71:                 )
 72:             );
 73:         }
 74:         // For security reason, only alphanumeric characters (+ underscores) are valid table names.
 75:         // Although SQL can support more, there's really no reason to.
 76:         if (!preg_match('/[A-Za-z0-9_]/', $table)) {
 77:             throw new InvalidArgumentException(
 78:                 sprintf('Table name "%s" is invalid: must be alphanumeric / underscore.', $table)
 79:             );
 80:         }
 81:         $this->table = $table;
 82: 
 83:         return $this;
 84:     }
 85: 
 86:     /**
 87:      * Get the database's current table.
 88:      *
 89:      * @throws Exception If the table was not set.
 90:      * @return string
 91:      */
 92:     public function table()
 93:     {
 94:         if ($this->table === null) {
 95:             throw new Exception(
 96:                 'Table was not set.'
 97:             );
 98:         }
 99:         return $this->table;
100:     }
101: 
102:     /**
103:      * Create a table from a model's metadata.
104:      *
105:      * @return boolean Success / Failure
106:      */
107:     public function createTable()
108:     {
109:         if ($this->tableExists()) {
110:             // Table already exists
111:             return true;
112:         }
113: 
114:         $dbDriver = $this->db()->getAttribute(PDO::ATTR_DRIVER_NAME);
115: 
116:         $model = $this->model();
117:         $metadata = $model->metadata();
118:         $fields = $this->getModelFields($model);
119:         $fieldsSql = [];
120:         foreach ($fields as $field) {
121:             $fieldsSql[] = $field->sql();
122:         }
123: 
124:         $q = 'CREATE TABLE  `'.$this->table().'` ('."\n";
125:         $q .= implode(',', $fieldsSql);
126:         $key = $model->key();
127:         if ($key) {
128:             $q .= ', PRIMARY KEY (`'.$key.'`) '."\n";
129:         }
130:         /** @todo add indexes for all defined list constraints (yea... tough job...) */
131:         if ($dbDriver === 'mysql') {
132:             $engine = 'InnoDB';
133:             $q .= ') ENGINE='.$engine.' DEFAULT CHARSET=utf8 COMMENT=\''.addslashes($metadata['name']).'\';';
134:         } else {
135:             $q .= ');';
136:         }
137:         $this->logger->debug($q);
138:         $this->db()->query($q);
139: 
140:         return true;
141:     }
142: 
143:     /**
144:      * Alter an existing table to match the model's metadata.
145:      *
146:      * @return boolean Success / Failure
147:      */
148:     public function alterTable()
149:     {
150:         if (!$this->tableExists()) {
151:             return false;
152:         }
153: 
154:         $fields = $this->getModelFields($this->model());
155: 
156:         $cols = $this->tableStructure();
157: 
158:         foreach ($fields as $field) {
159:             $ident = $field->ident();
160: 
161:             if (!array_key_exists($ident, $cols)) {
162:                 // The key does not exist at all.
163:                 $q = 'ALTER TABLE `'.$this->table().'` ADD '.$field->sql();
164:                 $this->logger->debug($q);
165:                 $this->db()->query($q);
166:             } else {
167:                 // The key exists. Validate.
168:                 $col = $cols[$ident];
169:                 $alter = true;
170:                 if (strtolower($col['Type']) != strtolower($field->sqlType())) {
171:                     $alter = true;
172:                 }
173:                 if ((strtolower($col['Null']) == 'no') && !$field->allowNull()) {
174:                     $alter = true;
175:                 }
176:                 if ((strtolower($col['Null']) != 'no') && $field->allowNull()) {
177:                     $alter = true;
178:                 }
179:                 if ($col['Default'] != $field->defaultVal()) {
180:                     $alter = true;
181:                 }
182: 
183:                 if ($alter === true) {
184:                     $q = 'ALTER TABLE `'.$this->table().'` CHANGE `'.$ident.'` '.$field->sql();
185:                     $this->logger->debug($q);
186:                     $this->db()->query($q);
187:                 }
188:             }
189:         }
190: 
191:         return true;
192:     }
193: 
194:     /**
195:      * @return boolean
196:      */
197:     public function tableExists()
198:     {
199:         $dbDriver = $this->db()->getAttribute(PDO::ATTR_DRIVER_NAME);
200:         if ($dbDriver === 'sqlite') {
201:             $q = 'SELECT name FROM sqlite_master WHERE type=\'table\' AND name=\''.$this->table().'\';';
202:         } else {
203:             $q = 'SHOW TABLES LIKE \''.$this->table().'\'';
204:         }
205:         $this->logger->debug($q);
206:         $res = $this->db()->query($q);
207:         $tableExists = $res->fetchColumn(0);
208: 
209:         // Return as boolean
210:         return !!$tableExists;
211:     }
212: 
213:     /**
214:      * Get the table columns information.
215:      *
216:      * @return array
217:      */
218:     public function tableStructure()
219:     {
220:         $dbDriver = $this->db()->getAttribute(PDO::ATTR_DRIVER_NAME);
221:         if ($dbDriver === 'sqlite') {
222:             $q = 'PRAGMA table_info(\''.$this->table().'\') ';
223:         } else {
224:             $q = 'SHOW COLUMNS FROM `'.$this->table().'`';
225:         }
226:         $this->logger->debug($q);
227:         $res = $this->db()->query($q);
228:         $cols = $res->fetchAll((PDO::FETCH_GROUP|PDO::FETCH_UNIQUE|PDO::FETCH_ASSOC));
229:         if ($dbDriver === 'sqlite') {
230:             $ret = [];
231:             foreach ($cols as $c) {
232:                 // Normalize SQLite's result (PRAGMA) with mysql's (SHOW COLUMNS)
233:                 $ret[$c['name']] = [
234:                     'Type'      => $c['type'],
235:                     'Null'      => !!$c['notnull'] ? 'NO' : 'YES',
236:                     'Default'   => $c['dflt_value'],
237:                     'Key'       => !!$c['pk'] ? 'PRI' : '',
238:                     'Extra'     => ''
239:                 ];
240:             }
241:             return $ret;
242:         } else {
243:             return $cols;
244:         }
245:     }
246: 
247:     /**
248:      * Check wether the source table is empty (`true`) or not (`false`)
249:      *
250:      * @return boolean
251:      */
252:     public function tableIsEmpty()
253:     {
254:         $q = 'SELECT NULL FROM `'.$this->table().'` LIMIT 1';
255:         $this->logger->debug($q);
256:         $res = $this->db()->query($q);
257:         return ($res->rowCount() === 0);
258:     }
259: 
260:     /**
261:      * @throws Exception If the database can not set.
262:      * @return PDO
263:      */
264:     public function db()
265:     {
266:         return $this->pdo;
267:     }
268: 
269:     /**
270:      * Get all the fields of a model.
271:      *
272:      * @param ModelInterface $model      The model to get fields from.
273:      * @param array|null     $properties Optional list of properties to get. If null, retrieve all (from metadata).
274:      * @return array
275:      * @todo Move this method in StorableTrait or AbstractModel
276:      */
277:     private function getModelFields(ModelInterface $model, $properties = null)
278:     {
279:         if ($properties === null) {
280:             // No custom properties; use all (from model metadata)
281:             $properties = array_keys($model->metadata()->properties());
282:         } else {
283:             // Ensure the key is always in the required fields.
284:             $properties = array_merge($properties, [ $model->key() ]);
285:         }
286: 
287:         $fields = [];
288:         foreach ($properties as $propertyIdent) {
289:             $p = $model->p($propertyIdent);
290:             if (!$p || !$p->active() || !$p->storable()) {
291:                 continue;
292:             }
293: 
294:             $v = $model->propertyValue($propertyIdent);
295:             foreach ($p->fields($v) as $fieldIdent => $field) {
296:                 $fields[$field->ident()] = $field;
297:             }
298:         }
299:         return $fields;
300:     }
301: 
302:     /**
303:      * @param mixed             $ident Ident can be any scalar value.
304:      * @param StorableInterface $item  Optional item to load into.
305:      * @return StorableInterface
306:      */
307:     public function loadItem($ident, StorableInterface $item = null)
308:     {
309:         $key = $this->model()->key();
310: 
311:         return $this->loadItemFromKey($key, $ident, $item);
312:     }
313: 
314:     /**
315:      * Load item from a custom column's name ($key)
316:      *
317:      * @param  string                 $key   Column name.
318:      * @param  mixed                  $ident Value of said column.
319:      * @param  StorableInterface|null $item  Optional. Item (storable object) to load into.
320:      * @throws Exception If the query fails.
321:      * @return StorableInterface             Item
322:      */
323:     public function loadItemFromKey($key, $ident, StorableInterface $item = null)
324:     {
325:         if ($item !== null) {
326:             $this->setModel($item);
327:         } else {
328:             $class = get_class($this->model());
329:             $item = new $class;
330:         }
331: 
332:         // Missing parameters
333:         if (!$key || !$ident) {
334:             return $item;
335:         }
336: 
337:         $q = '
338:             SELECT
339:                 *
340:             FROM
341:                `'.$this->table().'`
342:             WHERE
343:                `'.$key.'`=:ident
344:             LIMIT
345:                1';
346: 
347:         $binds = [
348:             'ident' => $ident
349:         ];
350: 
351:         return $this->loadItemFromQuery($q, $binds, $item);
352:     }
353: 
354:     /**
355:      * @param string            $query The SQL query.
356:      * @param array             $binds Optional. The query parameters.
357:      * @param StorableInterface $item  Optional. Item (storable object) to load into.
358:      * @throws Exception If there is a query error.
359:      * @return StorableInterface Item.
360:      */
361:     public function loadItemFromQuery($query, array $binds = [], StorableInterface $item = null)
362:     {
363:         if ($item !== null) {
364:             $this->setModel($item);
365:         } else {
366:             $class = get_class($this->model());
367:             $item = new $class;
368:         }
369: 
370:         // Missing parameters
371:         if (!$query) {
372:             return $item;
373:         }
374: 
375:         $sth = $this->dbQuery($query, $binds);
376:         if ($sth === false) {
377:             throw new Exception('Error');
378:         }
379: 
380:         $data = $sth->fetch(PDO::FETCH_ASSOC);
381:         if ($data) {
382:             $item->setFlatData($data);
383:         }
384: 
385:         return $item;
386:     }
387: 
388:     /**
389:      * @param StorableInterface|null $item Optional item to use as model.
390:      * @see this->loadItemsFromQuery()
391:      * @return array
392:      */
393:     public function loadItems(StorableInterface $item = null)
394:     {
395:         if ($item !== null) {
396:             $this->setModel($item);
397:         }
398: 
399:         $q = $this->sqlLoad();
400:         return $this->loadItemsFromQuery($q, [], $item);
401:     }
402: 
403:     /**
404:      * Loads items to a list from a query
405:      * Allows external use.
406:      *
407:      * @param  string                 $q     The actual query.
408:      * @param  array                  $binds This has to be done.
409:      * @param  StorableInterface|null $item  Model Item.
410:      * @return array                         Collection of Items | Model
411:      */
412:     public function loadItemsFromQuery($q, array $binds = [], StorableInterface $item = null)
413:     {
414:         if ($item !== null) {
415:             $this->setModel($item);
416:         }
417: 
418:         // Out
419:         $items = [];
420: 
421:         $model = $this->model();
422:         $db = $this->db();
423: 
424:         $this->logger->debug($q);
425:         $sth = $db->prepare($q);
426: 
427:         // @todo Binds
428:         if (!empty($binds)) {
429:             //
430:             unset($binds);
431:         }
432: 
433:         $sth->execute();
434:         $sth->setFetchMode(PDO::FETCH_ASSOC);
435: 
436:         $classname = get_class($model);
437:         while ($objData = $sth->fetch()) {
438:             $obj = new $classname;
439:             $obj->setFlatData($objData);
440:             $items[] = $obj;
441:         }
442: 
443:         return $items;
444:     }
445: 
446:     /**
447:      * Save an item (create a new row) in storage.
448:      *
449:      * @param StorableInterface $item The object to save.
450:      * @throws Exception If a database error occurs.
451:      * @return mixed The created item ID, or false in case of an error.
452:      */
453:     public function saveItem(StorableInterface $item)
454:     {
455:         if ($this->tableExists() === false) {
456:             /** @todo Optionnally turn off for some models */
457:             $this->createTable();
458:         }
459: 
460:         if ($item !== null) {
461:             $this->setModel($item);
462:         }
463:         $model = $this->model();
464: 
465:         $tableStructure = array_keys($this->tableStructure());
466: 
467:         $fields = $this->getModelFields($model);
468: 
469:         $keys   = [];
470:         $values = [];
471:         $binds  = [];
472:         $binds_types = [];
473:         foreach ($fields as $f) {
474:             $k = $f->ident();
475:             if (in_array($k, $tableStructure)) {
476:                 $keys[]    = '`'.$k.'`';
477:                 $values[]  = ':'.$k.'';
478:                 $binds[$k] = $f->val();
479:                 $binds_types[$k] = $f->sqlPdoType();
480:             }
481:         }
482: 
483:         $q = '
484:             INSERT
485:                 INTO
486:             `'.$this->table().'`
487:                 ('.implode(', ', $keys).')
488:             VALUES
489:                 ('.implode(', ', $values).')';
490: 
491:         $res = $this->dbQuery($q, $binds, $binds_types);
492: 
493:         if ($res === false) {
494:             throw new Exception(
495:                 'Could not save item.'
496:             );
497:         } else {
498:             if ($model->id()) {
499:                 return $model->id();
500:             } else {
501:                 return $this->db()->lastInsertId();
502:             }
503:         }
504:     }
505: 
506:     /**
507:      * Update an item in storage.
508:      *
509:      * @param StorableInterface $item       The object to update.
510:      * @param array             $properties The list of properties to update, if not all.
511:      * @return boolean Success / Failure
512:      */
513:     public function updateItem(StorableInterface $item, array $properties = null)
514:     {
515:         if ($item !== null) {
516:             $this->setModel($item);
517:         }
518:         $model = $this->model();
519: 
520:         $tableStructure = array_keys($this->tableStructure());
521:         $fields = $this->getModelFields($model, $properties);
522: 
523:         $updates = [];
524:         $binds   = [];
525:         $binds_types = [];
526:         foreach ($fields as $f) {
527:             $k = $f->ident();
528:             if (in_array($k, $tableStructure)) {
529:                 if ($k !== $model->key()) {
530:                     $updates[] = '`'.$k.'` = :'.$k;
531:                 }
532:                 $binds[$k] = $f->val();
533:                 $binds_types[$k] = $f->sqlPdoType();
534:             } else {
535:                 $this->logger->debug(
536:                     sprintf('Field %s not in table structure', $k)
537:                 );
538:             }
539:         }
540:         if (empty($updates)) {
541:             $this->logger->warning('Could not update items. No valid fields were set / available in database table.', [
542:                 'properties'    => $properties,
543:                 'structure'     => $tableStructure
544:             ]);
545:             return false;
546:         }
547: 
548:         $binds[$model->key()] = $model->id();
549:         $binds_types[$model->key()] = PDO::PARAM_STR;
550: 
551:         $q = '
552:             UPDATE
553:                 `'.$this->table().'`
554:             SET
555:                 '.implode(", \n\t", $updates).'
556:             WHERE
557:                 `'.$model->key().'`=:'.$model->key().'';
558: 
559:         $dbDriver = $this->db()->getAttribute(PDO::ATTR_DRIVER_NAME);
560:         if ($dbDriver == 'mysql') {
561:             $q .= "\n".'LIMIT 1';
562:         }
563: 
564:         $res = $this->dbQuery($q, $binds, $binds_types);
565: 
566:         if ($res === false) {
567:             return false;
568:         } else {
569:             return true;
570:         }
571:     }
572: 
573:     /**
574:      * Delete an item from storage
575:      *
576:      * @param StorableInterface $item Optional item to delete. If none, the current model object will be used.
577:      * @throws Exception If the item does not have an ID.
578:      * @return boolean Success / Failure
579:      */
580:     public function deleteItem(StorableInterface $item = null)
581:     {
582:         if ($item !== null) {
583:             $this->setModel($item);
584:         }
585:         $model = $this->model();
586: 
587:         if (!$model->id()) {
588:             throw new Exception(
589:                 sprintf('Can not delete "%s" item. No ID.', get_class($this))
590:             );
591:         }
592: 
593:         $q = '
594:             DELETE FROM
595:                 `'.$this->table().'`
596:             WHERE
597:                 `'.$model->key().'` = :id';
598: 
599:         $dbDriver = $this->db()->getAttribute(PDO::ATTR_DRIVER_NAME);
600:         if ($dbDriver == 'mysql') {
601:             $q .= "\n".'LIMIT 1';
602:         }
603: 
604:         $binds = [
605:             'id' => $model->id()
606:         ];
607: 
608:         $res = $this->dbQuery($q, $binds);
609: 
610:         if ($res === false) {
611:             return false;
612:         } else {
613:             return true;
614:         }
615:     }
616: 
617:     /**
618:      * Execute a SQL query, with PDO, and returns the PDOStatement.
619:      *
620:      * If the query fails, this method will return false.
621:      *
622:      * @param string $q           The SQL query to executed.
623:      * @param array  $binds       Optional. Query parameter binds.
624:      * @param array  $binds_types Optional. Types of parameter bindings.
625:      * @return PDOStatement|false The PDOStatement, or false in case of error
626:      */
627:     public function dbQuery($q, array $binds = [], array $binds_types = [])
628:     {
629:         $this->logger->debug($q, $binds);
630:         $sth = $this->db()->prepare($q);
631:         if (!$sth) {
632:             return false;
633:         }
634:         if (!empty($binds)) {
635:             foreach ($binds as $k => $v) {
636:                 if ($binds[$k] === null) {
637:                     $binds_types[$k] = PDO::PARAM_NULL;
638:                 } elseif (!is_scalar($binds[$k])) {
639:                     $binds[$k] = json_encode($binds[$k]);
640:                 }
641:                 $type = (isset($binds_types[$k]) ? $binds_types[$k] : PDO::PARAM_STR);
642:                 $sth->bindParam(':'.$k, $binds[$k], $type);
643:             }
644:         }
645: 
646:         $ret = $sth->execute();
647:         if ($ret === false) {
648:             return false;
649:         }
650: 
651:         return $sth;
652:     }
653: 
654:     /**
655:      * @throws Exception If the source does not have a table defined.
656:      * @return string
657:      */
658:     public function sqlLoad()
659:     {
660:         $table = $this->table();
661:         if (!$table) {
662:             throw new Exception(
663:                 'Can not get SQL. No table defined.'
664:             );
665:         }
666: 
667:         $selects = $this->sqlSelect();
668:         $tables  = '`'.$table.'` AS objTable';
669:         $filters = $this->sqlFilters();
670:         $orders  = $this->sqlOrders();
671:         $limits  = $this->sqlPagination();
672: 
673:         $q = 'SELECT '.$selects.' FROM '.$tables.$filters.$orders.$limits;
674:         return $q;
675:     }
676: 
677:     /**
678:      * Get a special SQL query for loading the count.
679:      *
680:      * @throws Exception If the source does not have a table defined.
681:      * @return string
682:      */
683:     public function sqlLoadCount()
684:     {
685:         $table = $this->table();
686:         if (!$table) {
687:             throw new Exception(
688:                 'Can not get count SQL. No table defined.s'
689:             );
690:         }
691: 
692:         $tables = '`'.$table.'` AS objTable';
693:         $filters = $this->sqlFilters();
694:         $q = 'SELECT COUNT(*) FROM '.$tables.$filters;
695:         return $q;
696:     }
697: 
698:     /**
699:      * @return string
700:      */
701:     public function sqlSelect()
702:     {
703:         $properties = $this->properties();
704:         if (empty($properties)) {
705:             return 'objTable.*';
706:         }
707: 
708:         $sql = '';
709:         $propsSql = [];
710:         foreach ($properties as $p) {
711:             $propsSql[] = 'objTable.`'.$p.'`';
712:         }
713:         if (!empty($propsSql)) {
714:             $sql = implode(', ', $propsSql);
715:         }
716: 
717:         return $sql;
718:     }
719: 
720:     /**
721:      * @return string
722:      * @todo 2016-02-19 Use bindings for filters value
723:      */
724:     public function sqlFilters()
725:     {
726:         $sql = '';
727: 
728:         $filters = $this->filters();
729:         if (empty($filters)) {
730:             return '';
731:         }
732: 
733:         // Process filters
734:         $filtersSql = [];
735:         foreach ($filters as $f) {
736:             $fSql = $f->sql();
737:             if ($fSql) {
738:                 $filtersSql[] = [
739:                     'sql'     => $f->sql(),
740:                     'operand' => $f->operand()
741:                 ];
742:             }
743:         }
744:         if (empty($filtersSql)) {
745:             return '';
746:         }
747: 
748:         $sql .= ' WHERE';
749:         $i = 0;
750: 
751:         foreach ($filtersSql as $f) {
752:             if ($i > 0) {
753:                 $sql .= ' '.$f['operand'];
754:             }
755:             $sql .= ' '.$f['sql'];
756:             $i++;
757:         }
758:         return $sql;
759:     }
760: 
761:     /**
762:      * @return string
763:      */
764:     public function sqlOrders()
765:     {
766:         $sql = '';
767: 
768:         if (!empty($this->orders)) {
769:             $ordersSql = [];
770:             foreach ($this->orders as $o) {
771:                 $ordersSql[] = $o->sql();
772:             }
773:             if (!empty($ordersSql)) {
774:                 $sql = ' ORDER BY '.implode(', ', $ordersSql);
775:             }
776:         }
777: 
778:         return $sql;
779:     }
780: 
781:     /**
782:      * @return string
783:      */
784:     public function sqlPagination()
785:     {
786:         return $this->pagination()->sql();
787:     }
788: 
789:     /**
790:      * @return FilterInterface
791:      */
792:     protected function createFilter()
793:     {
794:         $filter = new DatabaseFilter();
795:         return $filter;
796:     }
797: 
798:     /**
799:      * @return OrderInterface
800:      */
801:     protected function createOrder()
802:     {
803:         $order = new DatabaseOrder();
804:         return $order;
805:     }
806: 
807:     /**
808:      * @return PaginationInterface
809:      */
810:     protected function createPagination()
811:     {
812:         $pagination = new DatabasePagination();
813:         return $pagination;
814:     }
815: 
816:     /**
817:      * ConfigurableTrait > createConfig()
818:      *
819:      * Overrides the method defined in AbstractSource to returns a `DatabaseSourceConfig` object.
820:      *
821:      * @param array $data Optional.
822:      * @return DatabaseSourceConfig
823:      */
824:     public function createConfig(array $data = null)
825:     {
826:         $config = new DatabaseSourceConfig($data);
827:         return $config;
828:     }
829: }
830: 
API documentation generated by ApiGen