2020.09 CHANGELOG
[friendica.git/.git] / src / BaseRepository.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2020, Friendica
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          * Chainable.
101          *
102          * @param array $condition
103          * @param array $params
104          * @param int?  $max_id
105          * @param int?  $since_id
106          * @param int   $limit
107          * @return BaseCollection
108          * @throws \Exception
109          */
110         public function selectByBoundaries(array $condition = [], array $params = [], int $max_id = null, int $since_id = null, int $limit = self::LIMIT)
111         {
112                 $totalCount = DBA::count(static::$table_name, $condition);
113
114                 $boundCondition = $condition;
115
116                 if (isset($max_id)) {
117                         $boundCondition = DBA::mergeConditions($boundCondition, ['`id` < ?', $max_id]);
118                 }
119
120                 if (isset($since_id)) {
121                         $boundCondition = DBA::mergeConditions($boundCondition, ['`id` > ?', $since_id]);
122                 }
123
124                 $params['limit'] = $limit;
125
126                 $models = $this->selectModels($boundCondition, $params);
127
128                 return new static::$collection_class($models, $totalCount);
129         }
130
131         /**
132          * This method updates the database row from the model.
133          *
134          * @param BaseModel $model
135          * @return bool
136          * @throws \Exception
137          */
138         public function update(BaseModel $model)
139         {
140                 if ($this->dba->update(static::$table_name, $model->toArray(), ['id' => $model->id], $model->getOriginalData())) {
141                         $model->resetOriginalData();
142                         return true;
143                 }
144
145                 return false;
146         }
147
148         /**
149          * This method creates a new database row and returns a model if it was successful.
150          *
151          * @param array $fields
152          * @return BaseModel|bool
153          * @throws \Exception
154          */
155         public function insert(array $fields)
156         {
157                 $return = $this->dba->insert(static::$table_name, $fields);
158
159                 if (!$return) {
160                         throw new HTTPException\InternalServerErrorException('Unable to insert new row in table "' . static::$table_name . '"');
161                 }
162
163                 $fields['id'] = $this->dba->lastInsertId();
164                 $return = $this->create($fields);
165
166                 return $return;
167         }
168
169         /**
170          * Deletes the model record from the database.
171          *
172          * @param BaseModel $model
173          * @return bool
174          * @throws \Exception
175          */
176         public function delete(BaseModel &$model)
177         {
178                 if ($success = $this->dba->delete(static::$table_name, ['id' => $model->id])) {
179                         $model = null;
180                 }
181
182                 return $success;
183         }
184
185         /**
186          * Base instantiation method, can be overriden to add specific dependencies
187          *
188          * @param array $data
189          * @return BaseModel
190          */
191         protected function create(array $data)
192         {
193                 return new static::$model_class($this->dba, $this->logger, $data);
194         }
195
196         /**
197          * @param array $condition Query condition
198          * @param array $params    Additional query parameters
199          * @return BaseModel[]
200          * @throws \Exception
201          */
202         protected function selectModels(array $condition, array $params = [])
203         {
204                 $result = $this->dba->select(static::$table_name, [], $condition, $params);
205
206                 /** @var BaseModel $prototype */
207                 $prototype = null;
208
209                 $models = [];
210
211                 while ($record = $this->dba->fetch($result)) {
212                         if ($prototype === null) {
213                                 $prototype = $this->create($record);
214                                 $models[] = $prototype;
215                         } else {
216                                 $models[] = static::$model_class::createFromPrototype($prototype, $record);
217                         }
218                 }
219
220                 return $models;
221         }
222
223         /**
224          * @param BaseCollection $collection
225          */
226         public function saveCollection(BaseCollection $collection)
227         {
228                 $collection->map([$this, 'update']);
229         }
230 }