]> git.mxchange.org Git - friendica.git/blob - src/BaseRepository.php
Merge branch '2021.03-rc' into copyright-2021
[friendica.git] / src / BaseRepository.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2010-2021, the Friendica project
4  *
5  * @license GNU AGPL version 3 or any later version
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Affero General Public License as
9  * published by the Free Software Foundation, either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Affero General Public License for more details.
16  *
17  * You should have received a copy of the GNU Affero General Public License
18  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19  *
20  */
21
22 namespace Friendica;
23
24 use Friendica\Database\Database;
25 use Friendica\Database\DBA;
26 use Friendica\Network\HTTPException;
27 use Psr\Log\LoggerInterface;
28
29 /**
30  * Repositories are Factories linked to one or more database tables.
31  *
32  * @see BaseModel
33  * @see BaseCollection
34  */
35 abstract class BaseRepository extends BaseFactory
36 {
37         const LIMIT = 30;
38
39         /** @var Database */
40         protected $dba;
41
42         /** @var string */
43         protected static $table_name;
44
45         /** @var BaseModel */
46         protected static $model_class;
47
48         /** @var BaseCollection */
49         protected static $collection_class;
50
51         public function __construct(Database $dba, LoggerInterface $logger)
52         {
53                 parent::__construct($logger);
54
55                 $this->dba = $dba;
56                 $this->logger = $logger;
57         }
58
59         /**
60          * Fetches a single model record. The condition array is expected to contain a unique index (primary or otherwise).
61          *
62          * Chainable.
63          *
64          * @param array $condition
65          * @return BaseModel
66          * @throws HTTPException\NotFoundException
67          */
68         public function selectFirst(array $condition)
69         {
70                 $data = $this->dba->selectFirst(static::$table_name, [], $condition);
71
72                 if (!$data) {
73                         throw new HTTPException\NotFoundException(static::class . ' record not found.');
74                 }
75
76                 return $this->create($data);
77         }
78
79         /**
80          * Populates a Collection according to the condition.
81          *
82          * Chainable.
83          *
84          * @param array $condition
85          * @param array $params
86          * @return BaseCollection
87          * @throws \Exception
88          */
89         public function select(array $condition = [], array $params = [])
90         {
91                 $models = $this->selectModels($condition, $params);
92
93                 return new static::$collection_class($models);
94         }
95
96         /**
97          * Populates the collection according to the condition. Retrieves a limited subset of models depending on the boundaries
98          * and the limit. The total count of rows matching the condition is stored in the collection.
99          *
100          * max_id and min_id are susceptible to the query order:
101          * - min_id alone only reliably works with ASC order
102          * - max_id alone only reliably works with DESC order
103          * If the wrong order is detected in either case, we inverse the query order and we reverse the model array after the query
104          *
105          * Chainable.
106          *
107          * @param array $condition
108          * @param array $params
109          * @param int?  $min_id Retrieve models with an id no fewer than this, as close to it as possible
110          * @param int?  $max_id Retrieve models with an id no greater than this, as close to it as possible
111          * @param int   $limit
112          * @return BaseCollection
113          * @throws \Exception
114          */
115         public function selectByBoundaries(array $condition = [], array $params = [], int $min_id = null, int $max_id = null, int $limit = self::LIMIT)
116         {
117                 $totalCount = DBA::count(static::$table_name, $condition);
118
119                 $boundCondition = $condition;
120
121                 $reverseModels = false;
122
123                 if (isset($min_id)) {
124                         $boundCondition = DBA::mergeConditions($boundCondition, ['`id` > ?', $min_id]);
125                         if (!isset($max_id) && isset($params['order']['id']) && ($params['order']['id'] === true || $params['order']['id'] === 'DESC')) {
126                                 $reverseModels = true;
127                                 $params['order']['id'] = 'ASC';
128                         }
129                 }
130
131                 if (isset($max_id)) {
132                         $boundCondition = DBA::mergeConditions($boundCondition, ['`id` < ?', $max_id]);
133                         if (!isset($min_id) && (!isset($params['order']['id']) || $params['order']['id'] === false || $params['order']['id'] === 'ASC')) {
134                                 $reverseModels = true;
135                                 $params['order']['id'] = 'DESC';
136                         }
137                 }
138
139                 $params['limit'] = $limit;
140
141                 $models = $this->selectModels($boundCondition, $params);
142
143                 if ($reverseModels) {
144                         $models = array_reverse($models);
145                 }
146
147                 return new static::$collection_class($models, $totalCount);
148         }
149
150         /**
151          * This method updates the database row from the model.
152          *
153          * @param BaseModel $model
154          * @return bool
155          * @throws \Exception
156          */
157         public function update(BaseModel $model)
158         {
159                 if ($this->dba->update(static::$table_name, $model->toArray(), ['id' => $model->id], $model->getOriginalData())) {
160                         $model->resetOriginalData();
161                         return true;
162                 }
163
164                 return false;
165         }
166
167         /**
168          * This method creates a new database row and returns a model if it was successful.
169          *
170          * @param array $fields
171          * @return BaseModel|bool
172          * @throws \Exception
173          */
174         public function insert(array $fields)
175         {
176                 $return = $this->dba->insert(static::$table_name, $fields);
177
178                 if (!$return) {
179                         throw new HTTPException\InternalServerErrorException('Unable to insert new row in table "' . static::$table_name . '"');
180                 }
181
182                 $fields['id'] = $this->dba->lastInsertId();
183                 $return = $this->create($fields);
184
185                 return $return;
186         }
187
188         /**
189          * Deletes the model record from the database.
190          *
191          * @param BaseModel $model
192          * @return bool
193          * @throws \Exception
194          */
195         public function delete(BaseModel &$model)
196         {
197                 if ($success = $this->dba->delete(static::$table_name, ['id' => $model->id])) {
198                         $model = null;
199                 }
200
201                 return $success;
202         }
203
204         /**
205          * Base instantiation method, can be overriden to add specific dependencies
206          *
207          * @param array $data
208          * @return BaseModel
209          */
210         protected function create(array $data)
211         {
212                 return new static::$model_class($this->dba, $this->logger, $data);
213         }
214
215         /**
216          * @param array $condition Query condition
217          * @param array $params    Additional query parameters
218          * @return BaseModel[]
219          * @throws \Exception
220          */
221         protected function selectModels(array $condition, array $params = [])
222         {
223                 $result = $this->dba->select(static::$table_name, [], $condition, $params);
224
225                 /** @var BaseModel $prototype */
226                 $prototype = null;
227
228                 $models = [];
229
230                 while ($record = $this->dba->fetch($result)) {
231                         if ($prototype === null) {
232                                 $prototype = $this->create($record);
233                                 $models[] = $prototype;
234                         } else {
235                                 $models[] = static::$model_class::createFromPrototype($prototype, $record);
236                         }
237                 }
238
239                 $this->dba->close($result);
240
241                 return $models;
242         }
243
244         /**
245          * @param BaseCollection $collection
246          */
247         public function saveCollection(BaseCollection $collection)
248         {
249                 $collection->map([$this, 'update']);
250         }
251 }