2021-08-28 21:55:04 -04:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace Friendica;
|
|
|
|
|
|
|
|
use Exception;
|
|
|
|
use Friendica\Capabilities\ICanCreateFromTableRow;
|
|
|
|
use Friendica\Database\Database;
|
2021-09-17 23:37:41 -04:00
|
|
|
use Friendica\Database\DBA;
|
2021-08-28 21:55:04 -04:00
|
|
|
use Friendica\Network\HTTPException\NotFoundException;
|
|
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
|
|
|
|
/**
|
2021-10-21 17:18:08 -04:00
|
|
|
* Repositories are meant to store and retrieve Entities from the database.
|
2021-08-28 21:55:04 -04:00
|
|
|
*
|
|
|
|
* The reason why there are methods prefixed with an underscore is because PHP doesn't support generic polymorphism
|
2021-10-05 17:30:10 -04:00
|
|
|
* which means we can't directly overload base methods and make parameters more strict (from a parent class to a child
|
2021-08-28 21:55:04 -04:00
|
|
|
* 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.
|
|
|
|
*/
|
2021-10-21 17:18:08 -04:00
|
|
|
abstract class BaseRepository
|
2021-08-28 21:55:04 -04:00
|
|
|
{
|
|
|
|
const LIMIT = 30;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var string This should be set to the main database table name the depository is using
|
|
|
|
*/
|
|
|
|
protected static $table_name;
|
|
|
|
|
|
|
|
/** @var Database */
|
|
|
|
protected $db;
|
|
|
|
|
|
|
|
/** @var LoggerInterface */
|
|
|
|
protected $logger;
|
|
|
|
|
|
|
|
/** @var ICanCreateFromTableRow */
|
|
|
|
protected $factory;
|
|
|
|
|
|
|
|
public function __construct(Database $database, LoggerInterface $logger, ICanCreateFromTableRow $factory)
|
|
|
|
{
|
|
|
|
$this->db = $database;
|
|
|
|
$this->logger = $logger;
|
|
|
|
$this->factory = $factory;
|
|
|
|
}
|
|
|
|
|
2021-09-17 23:37:41 -04:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
* Depends on the corresponding table featuring a numerical auto incremented column called `id`.
|
|
|
|
*
|
|
|
|
* 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|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
|
|
|
|
*/
|
|
|
|
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;
|
|
|
|
|
|
|
|
$reverseOrder = false;
|
|
|
|
|
|
|
|
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['order']['id'] = 'ASC';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-12 21:18:23 -04:00
|
|
|
if (isset($max_id) && $max_id > 0) {
|
2021-09-17 23:37:41 -04:00
|
|
|
$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;
|
|
|
|
|
|
|
|
$params['order']['id'] = 'DESC';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$params['limit'] = $limit;
|
|
|
|
|
|
|
|
$Entities = $this->_select($boundCondition, $params);
|
|
|
|
if ($reverseOrder) {
|
|
|
|
$Entities->reverse();
|
|
|
|
}
|
|
|
|
|
|
|
|
return new BaseCollection($Entities->getArrayCopy(), $totalCount);
|
|
|
|
}
|
2021-08-28 21:55:04 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param array $condition
|
|
|
|
* @param array $params
|
|
|
|
* @return BaseCollection
|
|
|
|
* @throws Exception
|
|
|
|
*/
|
|
|
|
protected function _select(array $condition, array $params = []): BaseCollection
|
|
|
|
{
|
|
|
|
$rows = $this->db->selectToArray(static::$table_name, [], $condition, $params);
|
|
|
|
|
|
|
|
$Entities = new BaseCollection();
|
|
|
|
foreach ($rows as $fields) {
|
|
|
|
$Entities[] = $this->factory->createFromTableRow($fields);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $Entities;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param array $condition
|
|
|
|
* @param array $params
|
|
|
|
* @return BaseEntity
|
|
|
|
* @throws NotFoundException
|
|
|
|
*/
|
|
|
|
protected function _selectOne(array $condition, array $params = []): BaseEntity
|
|
|
|
{
|
|
|
|
$fields = $this->db->selectFirst(static::$table_name, [], $condition, $params);
|
|
|
|
if (!$this->db->isResult($fields)) {
|
|
|
|
throw new NotFoundException();
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->factory->createFromTableRow($fields);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param array $condition
|
|
|
|
* @param array $params
|
|
|
|
* @return int
|
|
|
|
* @throws Exception
|
|
|
|
*/
|
|
|
|
public function count(array $condition, array $params = []): int
|
|
|
|
{
|
|
|
|
return $this->db->count(static::$table_name, $condition, $params);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param array $condition
|
|
|
|
* @return bool
|
|
|
|
* @throws Exception
|
|
|
|
*/
|
|
|
|
public function exists(array $condition): bool
|
|
|
|
{
|
|
|
|
return $this->db->exists(static::$table_name, $condition);
|
|
|
|
}
|
|
|
|
}
|