]> git.mxchange.org Git - friendica.git/blobdiff - src/BaseRepository.php
Merge pull request #11519 from MrPetovan/task/11511-console-domain-move
[friendica.git] / src / BaseRepository.php
index 64a0d1c510626e995cb4c213f9565271fa6af062..3e8c2c292c0aebb02a4c99c399ba3fc787d4389f 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * @copyright Copyright (C) 2020, Friendica
+ * @copyright Copyright (C) 2010-2022, the Friendica project
  *
  * @license GNU AGPL version 3 or any later version
  *
 
 namespace Friendica;
 
+use Exception;
+use Friendica\Capabilities\ICanCreateFromTableRow;
 use Friendica\Database\Database;
 use Friendica\Database\DBA;
-use Friendica\Network\HTTPException;
+use Friendica\Network\HTTPException\NotFoundException;
 use Psr\Log\LoggerInterface;
 
 /**
- * Repositories are Factories linked to one or more database tables.
+ * Repositories are meant to store and retrieve Entities from the database.
  *
- * @see BaseModel
- * @see BaseCollection
+ * The reason why there are methods prefixed with an underscore is because PHP doesn't support generic polymorphism
+ * which means we can't directly overload base methods and make parameters more strict (from a parent class to a child
+ * class for example)
+ *
+ * Similarly, we can't make an overloaded method return type more strict until we only support PHP version 7.4 but this
+ * is less pressing.
  */
-abstract class BaseRepository extends BaseFactory
+abstract class BaseRepository
 {
        const LIMIT = 30;
 
-       /** @var Database */
-       protected $dba;
-
-       /** @var string */
+       /**
+        * @var string This should be set to the main database table name the depository is using
+        */
        protected static $table_name;
 
-       /** @var BaseModel */
-       protected static $model_class;
+       /** @var Database */
+       protected $db;
 
-       /** @var BaseCollection */
-       protected static $collection_class;
+       /** @var LoggerInterface */
+       protected $logger;
 
-       public function __construct(Database $dba, LoggerInterface $logger)
-       {
-               parent::__construct($logger);
+       /** @var ICanCreateFromTableRow */
+       protected $factory;
 
-               $this->dba = $dba;
-               $this->logger = $logger;
-       }
-
-       /**
-        * Fetches a single model record. The condition array is expected to contain a unique index (primary or otherwise).
-        *
-        * Chainable.
-        *
-        * @param array $condition
-        * @return BaseModel
-        * @throws HTTPException\NotFoundException
-        */
-       public function selectFirst(array $condition)
+       public function __construct(Database $database, LoggerInterface $logger, ICanCreateFromTableRow $factory)
        {
-               $data = $this->dba->selectFirst(static::$table_name, [], $condition);
-
-               if (!$data) {
-                       throw new HTTPException\NotFoundException(static::class . ' record not found.');
-               }
-
-               return $this->create($data);
+               $this->db      = $database;
+               $this->logger  = $logger;
+               $this->factory = $factory;
        }
 
        /**
-        * Populates a Collection according to the condition.
+        * Populates the collection according to the condition. Retrieves a limited subset of entities depending on the
+        * boundaries and the limit. The total count of rows matching the condition is stored in the collection.
         *
-        * Chainable.
+        * Depends on the corresponding table featuring a numerical auto incremented column called `id`.
         *
-        * @param array $condition
-        * @param array $params
-        * @return BaseCollection
-        * @throws \Exception
-        */
-       public function select(array $condition = [], array $params = [])
-       {
-               $models = $this->selectModels($condition, $params);
-
-               return new static::$collection_class($models);
-       }
-
-       /**
-        * Populates the collection according to the condition. Retrieves a limited subset of models depending on the boundaries
-        * and the limit. The total count of rows matching the condition is stored in the collection.
+        * max_id and min_id are susceptible to the query order:
+        * - min_id alone only reliably works with ASC order
+        * - max_id alone only reliably works with DESC order
+        * If the wrong order is detected in either case, we reverse the query order and the entity list order after the query
         *
         * Chainable.
         *
-        * @param array $condition
-        * @param array $params
-        * @param int?  $max_id
-        * @param int?  $since_id
-        * @param int   $limit
+        * @param array    $condition
+        * @param array    $params
+        * @param int|null $min_id Retrieve models with an id no fewer than this, as close to it as possible
+        * @param int|null $max_id Retrieve models with an id no greater than this, as close to it as possible
+        * @param int      $limit
         * @return BaseCollection
         * @throws \Exception
         */
-       public function selectByBoundaries(array $condition = [], array $params = [], int $max_id = null, int $since_id = null, int $limit = self::LIMIT)
-       {
-               $condition = DBA::collapseCondition($condition);
+       protected function _selectByBoundaries(
+               array $condition = [],
+               array $params = [],
+               int $min_id = null,
+               int $max_id = null,
+               int $limit = self::LIMIT
+       ): BaseCollection {
+               $totalCount = $this->count($condition);
 
                $boundCondition = $condition;
 
-               if (isset($max_id)) {
-                       $boundCondition[0] .= " AND `id` < ?";
-                       $boundCondition[] = $max_id;
-               }
+               $reverseOrder = false;
 
-               if (isset($since_id)) {
-                       $boundCondition[0] .= " AND `id` > ?";
-                       $boundCondition[] = $since_id;
-               }
+               if (isset($min_id)) {
+                       $boundCondition = DBA::mergeConditions($boundCondition, ['`id` > ?', $min_id]);
+                       if (!isset($max_id) && isset($params['order']['id']) && ($params['order']['id'] === true || $params['order']['id'] === 'DESC')) {
+                               $reverseOrder = true;
 
-               $params['limit'] = $limit;
+                               $params['order']['id'] = 'ASC';
+                       }
+               }
 
-               $models = $this->selectModels($boundCondition, $params);
+               if (isset($max_id) && $max_id > 0) {
+                       $boundCondition = DBA::mergeConditions($boundCondition, ['`id` < ?', $max_id]);
+                       if (!isset($min_id) && (!isset($params['order']['id']) || $params['order']['id'] === false || $params['order']['id'] === 'ASC')) {
+                               $reverseOrder = true;
 
-               $totalCount = DBA::count(static::$table_name, $condition);
+                               $params['order']['id'] = 'DESC';
+                       }
+               }
 
-               return new static::$collection_class($models, $totalCount);
-       }
+               $params['limit'] = $limit;
 
-       /**
-        * This method updates the database row from the model.
-        *
-        * @param BaseModel $model
-        * @return bool
-        * @throws \Exception
-        */
-       public function update(BaseModel $model)
-       {
-               if ($this->dba->update(static::$table_name, $model->toArray(), ['id' => $model->id], $model->getOriginalData())) {
-                       $model->resetOriginalData();
-                       return true;
+               $Entities = $this->_select($boundCondition, $params);
+               if ($reverseOrder) {
+                       $Entities->reverse();
                }
 
-               return false;
+               return new BaseCollection($Entities->getArrayCopy(), $totalCount);
        }
 
        /**
-        * This method creates a new database row and returns a model if it was successful.
-        *
-        * @param array $fields
-        * @return BaseModel|bool
-        * @throws \Exception
+        * @param array $condition
+        * @param array $params
+        * @return BaseCollection
+        * @throws Exception
         */
-       public function insert(array $fields)
+       protected function _select(array $condition, array $params = []): BaseCollection
        {
-               $return = $this->dba->insert(static::$table_name, $fields);
+               $rows = $this->db->selectToArray(static::$table_name, [], $condition, $params);
 
-               if (!$return) {
-                       throw new HTTPException\InternalServerErrorException('Unable to insert new row in table "' . static::$table_name . '"');
+               $Entities = new BaseCollection();
+               foreach ($rows as $fields) {
+                       $Entities[] = $this->factory->createFromTableRow($fields);
                }
 
-               $fields['id'] = $this->dba->lastInsertId();
-               $return = $this->create($fields);
-
-               return $return;
+               return $Entities;
        }
 
        /**
-        * Deletes the model record from the database.
-        *
-        * @param BaseModel $model
-        * @return bool
-        * @throws \Exception
+        * @param array $condition
+        * @param array $params
+        * @return BaseEntity
+        * @throws NotFoundException
         */
-       public function delete(BaseModel &$model)
+       protected function _selectOne(array $condition, array $params = []): BaseEntity
        {
-               if ($success = $this->dba->delete(static::$table_name, ['id' => $model->id])) {
-                       $model = null;
+               $fields = $this->db->selectFirst(static::$table_name, [], $condition, $params);
+               if (!$this->db->isResult($fields)) {
+                       throw new NotFoundException();
                }
 
-               return $success;
+               return $this->factory->createFromTableRow($fields);
        }
 
        /**
-        * Base instantiation method, can be overriden to add specific dependencies
-        *
-        * @param array $data
-        * @return BaseModel
-        */
-       protected function create(array $data)
-       {
-               return new static::$model_class($this->dba, $this->logger, $data);
-       }
-
-       /**
-        * @param array $condition Query condition
-        * @param array $params    Additional query parameters
-        * @return BaseModel[]
-        * @throws \Exception
+        * @param array $condition
+        * @param array $params
+        * @return int
+        * @throws Exception
         */
-       protected function selectModels(array $condition, array $params = [])
+       public function count(array $condition, array $params = []): int
        {
-               $result = $this->dba->select(static::$table_name, [], $condition, $params);
-
-               /** @var BaseModel $prototype */
-               $prototype = null;
-
-               $models = [];
-
-               while ($record = $this->dba->fetch($result)) {
-                       if ($prototype === null) {
-                               $prototype = $this->create($record);
-                               $models[] = $prototype;
-                       } else {
-                               $models[] = static::$model_class::createFromPrototype($prototype, $record);
-                       }
-               }
-
-               return $models;
+               return $this->db->count(static::$table_name, $condition, $params);
        }
 
        /**
-        * @param BaseCollection $collection
+        * @param array $condition
+        * @return bool
+        * @throws Exception
         */
-       public function saveCollection(BaseCollection $collection)
+       public function exists(array $condition): bool
        {
-               $collection->map([$this, 'update']);
+               return $this->db->exists(static::$table_name, $condition);
        }
 }