diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e6161d0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,36 @@ +name: CI + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Composer install + uses: php-actions/composer@v6 + with: + php_version: 8.1 + + - name: PHP Code Sniffer + uses: php-actions/phpcs@v1 + with: + php_version: 8.1 + path: src/ + standard: PSR12 + exclude: Generic.Files.LineLength + + - name: PHPStan + uses: php-actions/phpstan@v3 + with: + php_version: 8.1 + path: src/ + level: max + + # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit" + # Docs: https://getcomposer.org/doc/articles/scripts.md + + # - name: Run test suite + # run: composer run-script test \ No newline at end of file diff --git a/composer.json b/composer.json index 344abfe..fe0c266 100644 --- a/composer.json +++ b/composer.json @@ -10,11 +10,13 @@ } ], "require": { - "php": ">=5.5", - "nette/database": "~2.4" + "php": ">=8.0", + "ext-pdo": "*", + "nette/database": "~3.1" }, "require-dev": { - "phpunit/phpunit": "^6.0", + "ramsey/uuid": "^4.7", + "phpunit/phpunit": "^9.5", "squizlabs/php_codesniffer": "^3.0" }, "autoload": { diff --git a/src/ActiveRow.php b/src/ActiveRow.php index 0f5328d..c2ae5d4 100644 --- a/src/ActiveRow.php +++ b/src/ActiveRow.php @@ -4,25 +4,23 @@ namespace SimpleMapper; -use Nette\Database\Table\Selection as NetteDatabaseSelection; -use Nette\Database\Table\ActiveRow as NetteDatabaseActiveRow; -use ArrayIterator; +use Iterator; use IteratorAggregate; +use Nette\Database\Table\ActiveRow as NetteDatabaseActiveRow; use Nette\Database\Table\IRow; -use Nette\DeprecatedException; +use Nette\Database\Table\Selection as NetteDatabaseSelection; +use Nette\MemberAccessException; use SimpleMapper\Exception\ActiveRowException; +use SimpleMapper\Exception\DeprecatedException; use SimpleMapper\Structure\Structure; class ActiveRow implements IteratorAggregate, IRow { - /** @var NetteDatabaseActiveRow */ - protected $record; + protected NetteDatabaseActiveRow $record; - /** @var Structure */ - protected $structure; + protected Structure $structure; - /** @var ActiveRow|null */ - protected $referencingRecord; + protected ?ActiveRow $referencingRecord = null; /** * @param NetteDatabaseActiveRow $record @@ -34,106 +32,47 @@ public function __construct(NetteDatabaseActiveRow $record, Structure $structure $this->structure = $structure; } - /** - * @param string $name - * @return mixed|ActiveRow - */ - public function __get($name) - { - $result = $this->record->$name; - return $result instanceof IRow ? $this->prepareRecord($result) : $result; - } + /**********************************************************************\ + * Wrapper function + \**********************************************************************/ /** - * @return NetteDatabaseActiveRow + * @throws ActiveRowException */ - public function getRecord(): NetteDatabaseActiveRow + public function setTable(NetteDatabaseSelection $selection): void { - return $this->record; + throw new ActiveRowException('Internal ActiveRow interface method'); } - /** - * @return NetteDatabaseSelection - */ public function getTable(): NetteDatabaseSelection { return $this->record->getTable(); } - /** - * @param NetteDatabaseSelection $selection - * @throws ActiveRowException - */ - public function setTable(NetteDatabaseSelection $selection): void - { - throw new ActiveRowException('Internal IRow interface method'); - } - - /**********************************************************************\ - * Wrapper function - \**********************************************************************/ - - /** - * @return string - */ public function __toString(): string { return (string) $this->record; } - /** - * @return array - */ public function toArray(): array { return $this->record->toArray(); } - /** - * @param bool|true $need - * @return mixed - */ - public function getPrimary($need = true) + public function getPrimary(bool $throw = true): mixed { - return $this->record->getPrimary($need); + return $this->record->getPrimary($throw); } - /** - * @param bool|true $need - * @return string - */ - public function getSignature($need = true): string + public function getSignature(bool $throw = true): string { - return $this->record->getSignature($need); - } - - /** - * @param array|\Traversable $data - * @return bool - */ - public function update($data): bool - { - return $this->record->update($data); - } - - /** - * @return int - */ - public function delete(): int - { - return $this->record->delete(); + return $this->record->getSignature($throw); } - /** - * Returns referenced row - * @param string $key - * @param string $throughColumn - * @return ActiveRow|null - */ - public function ref($key, $throughColumn = null): ?ActiveRow + public function ref(string $key, ?string $throughColumn = null): ?self { $row = $this->record->ref($key, $throughColumn); - if($row instanceof IRow) { + if ($row instanceof NetteDatabaseActiveRow) { $result = $this->prepareRecord($row); $result->setReferencingRecord($this); return $result; @@ -142,26 +81,26 @@ public function ref($key, $throughColumn = null): ?ActiveRow return null; } - /** - * Returns referencing rows - * @param string $key - * @param string $throughColumn - * @return Selection - */ - public function related($key, $throughColumn = null): ?Selection + public function related(string $key, ?string $throughColumn = null): Selection + { + return $this->prepareSelection($this->record->related($key, $throughColumn)); + } + + public function update(iterable $data): bool { - $selection = $this->record->related($key, $throughColumn); - return $selection instanceof NetteDatabaseSelection ? $this->prepareSelection($selection) : null; + return $this->record->update($data); + } + + public function delete(): int + { + return $this->record->delete(); } /**********************************************************************\ * IteratorAggregate interface \**********************************************************************/ - /** - * @return ArrayIterator - */ - public function getIterator(): ArrayIterator + public function getIterator(): Iterator { return $this->record->getIterator(); } @@ -171,45 +110,81 @@ public function getIterator(): ArrayIterator \**********************************************************************/ /** - * Returns value of column - * @param string $key column name + * Stores value in column. + * @param string $column + * @param mixed $value + */ + public function offsetSet($column, $value): void + { + $this->record->offsetSet($column, $value); + } + + /** + * Returns value of column. + * @param string $column * @return mixed */ - public function offsetGet($key) + #[\ReturnTypeWillChange] + public function offsetGet($column) { - $result = $this->record->offsetGet($key); - return $result instanceof IRow ? $this->prepareRecord($result) : $result; + $result = $this->record->offsetGet($column); + return $result instanceof NetteDatabaseActiveRow ? $this->prepareRecord($result) : $result; } /** - * Tests if column exists - * @param string $key column name - * @return bool + * Tests if column exists. + * @param string $column */ - public function offsetExists($key): bool + public function offsetExists($column): bool { - return $this->record->offsetExists($key); + return $this->record->offsetExists($column); } /** - * Stores value in column - * @param string $key column name - * @param string $value + * Removes column from data. + * @param string $column + */ + public function offsetUnset($column): void + { + $this->record->offsetUnset($column); + } + + /**********************************************************************\ + * Magic accessors + \**********************************************************************/ + + /** * @throws DeprecatedException */ - public function offsetSet($key, $value): void + public function __set($column, $value) + { + throw new DeprecatedException('ActiveRow is read-only; use update() method instead.'); + } + + /** + * @return mixed|ActiveRow + * @throws MemberAccessException + */ + public function &__get(string $key) + { + $result = $this->record->$key; + if ($result instanceof NetteDatabaseActiveRow) { + $result = $this->prepareRecord($result); + } + return $result; + } + + public function __isset($key) { - $this->record->offsetSet($key, $value); + return isset($this->record->$key); } /** - * Removes column from data - * @param string $key column name * @throws DeprecatedException */ - public function offsetUnset($key): void + public function __unset($key) { - $this->record->offsetUnset($key); + throw new DeprecatedException('ActiveRow is read-only.'); } /**********************************************************************\ @@ -234,10 +209,6 @@ public function getReferencingRecord(): ?ActiveRow /** * Returns mm referencing rows - * @param Selection $selection - * @param string $ref - * @param string $refPrimary - * @return array */ protected function mmRelated(Selection $selection, string $ref, string $refPrimary = 'id'): array { @@ -252,25 +223,24 @@ protected function mmRelated(Selection $selection, string $ref, string $refPrima * Build methods \**********************************************************************/ - /** - * Prepare one record - * @param IRow $row - * @return ActiveRow - */ - protected function prepareRecord(IRow $row): ActiveRow + protected function prepareRecord(NetteDatabaseActiveRow $row): ActiveRow { $recordClass = $this->structure->getActiveRowClass($row->getTable()->getName()); return new $recordClass($row, $this->structure); } - /** - * Prepare selection - * @param NetteDatabaseSelection $selection - * @return Selection - */ protected function prepareSelection(NetteDatabaseSelection $selection): Selection { $selectionClass = $this->structure->getSelectionClass($selection->getName()); return new $selectionClass($selection, $this->structure); } + + /**********************************************************************\ + * Help methods + \**********************************************************************/ + + public function getRecord(): NetteDatabaseActiveRow + { + return $this->record; + } } diff --git a/src/Behaviour/Behaviour.php b/src/Behaviour/Behaviour.php index 79d97bd..003e4c2 100644 --- a/src/Behaviour/Behaviour.php +++ b/src/Behaviour/Behaviour.php @@ -10,45 +10,31 @@ interface Behaviour { /** * Transform data before insert - * @param array $data - * @return array */ public function beforeInsert(array $data): array; /** * Handle after insert - * @param ActiveRow $record - * @param array $data */ public function afterInsert(ActiveRow $record, array $data): void; /** * Transform data before update - * @param ActiveRow $record - * @param array $data - * @return array */ public function beforeUpdate(ActiveRow $record, array $data): array; /** * Handle after update - * @param ActiveRow $oldRecord - * @param ActiveRow $newRecord - * @param array $data */ public function afterUpdate(ActiveRow $oldRecord, ActiveRow $newRecord, array $data): void; /** * Handle before delete - * @param ActiveRow $record - * @param bool $soft */ public function beforeDelete(ActiveRow $record, bool $soft): void; /** * Handle after delete - * @param ActiveRow $record - * @param bool $soft */ public function afterDelete(ActiveRow $record, bool $soft): void; } diff --git a/src/Behaviour/DateBehaviour.php b/src/Behaviour/DateBehaviour.php index a33cf08..63b9d61 100644 --- a/src/Behaviour/DateBehaviour.php +++ b/src/Behaviour/DateBehaviour.php @@ -9,26 +9,16 @@ class DateBehaviour extends AbstractBehaviour { - /** @var string */ - private $createdAtField; + private string $createdAtField; - /** @var string */ - private $updatedAtField; + private string $updatedAtField; - /** - * @param string $createdAtField - * @param string $updatedAtField - */ public function __construct(string $createdAtField = 'created_at', string $updatedAtField = 'updated_at') { $this->createdAtField = $createdAtField; $this->updatedAtField = $updatedAtField; } - /** - * @param array $data - * @return array - */ public function beforeInsert(array $data): array { $now = new DateTime('now'); @@ -41,11 +31,6 @@ public function beforeInsert(array $data): array return $data; } - /** - * @param ActiveRow $record - * @param array $data - * @return array - */ public function beforeUpdate(ActiveRow $record, array $data): array { if ($this->updatedAtField) { diff --git a/src/Behaviour/Uuid4Behaviour.php b/src/Behaviour/Uuid4Behaviour.php index 91d021d..193ed92 100644 --- a/src/Behaviour/Uuid4Behaviour.php +++ b/src/Behaviour/Uuid4Behaviour.php @@ -8,21 +8,13 @@ class Uuid4Behaviour extends AbstractBehaviour { - /** @var string */ - private $field; + private string $field; - /** - * @param string $field - */ public function __construct(string $field = 'id') { $this->field = $field; } - /** - * @param array $data - * @return array - */ public function beforeInsert(array $data): array { if (!array_key_exists($this->field, $data) || !$data[$this->field]) { diff --git a/src/Exception/ActiveRowException.php b/src/Exception/ActiveRowException.php index 915dc53..dc6db80 100644 --- a/src/Exception/ActiveRowException.php +++ b/src/Exception/ActiveRowException.php @@ -8,5 +8,4 @@ class ActiveRowException extends Exception { - } diff --git a/src/Exception/DeprecatedException.php b/src/Exception/DeprecatedException.php index efe0e68..b2fc864 100644 --- a/src/Exception/DeprecatedException.php +++ b/src/Exception/DeprecatedException.php @@ -6,5 +6,4 @@ class DeprecatedException extends SimpleMapperException { - } diff --git a/src/Exception/RepositoryException.php b/src/Exception/RepositoryException.php index 6552845..7eba72f 100644 --- a/src/Exception/RepositoryException.php +++ b/src/Exception/RepositoryException.php @@ -6,5 +6,4 @@ class RepositoryException extends SimpleMapperException { - } diff --git a/src/Exception/SimpleMapperException.php b/src/Exception/SimpleMapperException.php index 6983e85..d3d702a 100644 --- a/src/Exception/SimpleMapperException.php +++ b/src/Exception/SimpleMapperException.php @@ -8,5 +8,4 @@ class SimpleMapperException extends Exception { - } diff --git a/src/Mapper.php b/src/Mapper.php index 3c87e2b..f326ab7 100644 --- a/src/Mapper.php +++ b/src/Mapper.php @@ -8,11 +8,9 @@ class Mapper { - /** @var Structure */ - private $structure; + private Structure $structure; - /** @var array */ - private $repositories = []; + private array $repositories = []; /** * @param Structure $structure @@ -24,13 +22,12 @@ public function __construct(Structure $structure) /** * Map classes and scopes by repository - * @param Repository $repository - * @param string|null $activeRowClass - * @param string|null $selectionClass - * @return Mapper */ - public function mapRepository(Repository $repository, string $activeRowClass = null, string $selectionClass = null): Mapper - { + public function mapRepository( + Repository $repository, + ?string $activeRowClass = null, + ?string $selectionClass = null + ): Mapper { $this->repositories[get_class($repository)] = $repository; $repository->setStructure($this->structure); @@ -47,13 +44,12 @@ public function mapRepository(Repository $repository, string $activeRowClass = n /** * Map classes only by table name - * @param string $tableName - * @param string|null $activeRowClass - * @param string|null $selectionClass - * @return Mapper */ - public function mapTableName(string $tableName, string $activeRowClass = null, string $selectionClass = null): Mapper - { + public function mapTableName( + string $tableName, + string $activeRowClass = null, + string $selectionClass = null + ): Mapper { if ($activeRowClass) { $this->structure->registerActiveRowClass($tableName, $activeRowClass); } @@ -65,10 +61,6 @@ public function mapTableName(string $tableName, string $activeRowClass = null, s return $this; } - /** - * @param string $class - * @return null|Repository - */ public function getRepository(string $class): ?Repository { return $this->repositories[$class] ?? null; diff --git a/src/Repository.php b/src/Repository.php index a20e8f9..da8bd1a 100644 --- a/src/Repository.php +++ b/src/Repository.php @@ -4,62 +4,52 @@ namespace SimpleMapper; -use Nette\Database\Context; +use Exception; use Nette\Database\DriverException; +use Nette\Database\Explorer; use Nette\Database\Table\ActiveRow as NetteDatabaseActiveRow; use Nette\Database\Table\Selection as NetteDatabaseSelection; +use PDOException; use SimpleMapper\Behaviour\Behaviour; use SimpleMapper\Exception\RepositoryException; use SimpleMapper\Structure\EmptyStructure; use SimpleMapper\Structure\Structure; -use Traversable; -use Exception; -use PDOException; /** * Base repository class */ abstract class Repository { - /** @var Context */ - protected $databaseContext; + protected Explorer $databaseExplorer; - /** @var Structure|null */ - protected $structure; + protected ?Structure $structure; - /** @var string Soft delete field, if empty soft delete is disabled */ - protected $softDelete = ''; + /** Soft delete field, if empty soft delete is disabled */ + protected string $softDelete = ''; - /** @var array */ - private $behaviours = []; + private array $behaviours = []; - /** @var string */ - protected static $tableName = 'unknown'; + protected static string $tableName = 'unknown'; - /** - * @param Context $databaseContext - */ - public function __construct(Context $databaseContext) + public function __construct(Explorer $databaseExplorer, ?Structure $structure = null) { - $this->databaseContext = $databaseContext; - $this->structure = new EmptyStructure(); + $this->databaseExplorer = $databaseExplorer; + $this->structure = $this->configureStructure($structure); $this->configure(); } - /** - * @param Structure $structure - */ - public function setStructure(Structure $structure): void + private function configureStructure(?Structure $structure): Structure { - $this->structure = $structure; + if ($structure === null) { + return new EmptyStructure(); + } + if (count($this->getScopes())) { - $this->structure->registerScopes(static::getTableName(), $this->getScopes()); + $structure->registerScopes(static::getTableName(), $this->getScopes()); } + return $structure; } - /** - * @return string - */ public static function getTableName(): string { return static::$tableName; @@ -67,20 +57,15 @@ public static function getTableName(): string /** * Prefix given string (column name) with table name - * @param string $column - * @return string */ public static function prefixColumn(string $column): string { return static::getTableName() . '.' . $column; } - /** - * @return Context - */ - public function getDatabaseContext(): Context + public function getDatabaseExplorer(): Explorer { - return $this->databaseContext; + return $this->databaseExplorer; } /********************************************************************\ @@ -88,14 +73,11 @@ public function getDatabaseContext(): Context \********************************************************************/ /** - * @param string $name - * @param array $arguments - * @return mixed * @throws RepositoryException */ - public function __call(string $name, array $arguments) + public function __call(string $name, array $arguments): mixed { - if (substr($name, 0, 5) === 'scope') { + if (str_starts_with($name, 'scope')) { $scopeName = lcfirst(substr($name, 5)); $scope = $this->structure->getScope(static::$tableName, $scopeName); if (!$scope) { @@ -113,33 +95,16 @@ public function __call(string $name, array $arguments) | Wrapper methods \********************************************************************/ - /** - * Find all records - * @return Selection - */ public function findAll(): Selection { return $this->prepareSelection($this->getTable()); } - /** - * Find by conditions - * @param array $by - * @return Selection - */ public function findBy(array $by): Selection { return $this->prepareSelection($this->getTable()->where($by)); } - /** - * Returns all rows as associative array - * @param string|null $key - * @param string|null $value - * @param string|null $order - * @param array $where - * @return array - */ public function fetchPairs(string $key = null, string $value = null, string $order = null, array $where = []): array { $result = []; @@ -155,9 +120,6 @@ public function fetchPairs(string $key = null, string $value = null, string $ord } /** - * Insert one record - * @param array|Traversable $data - * @return ActiveRow|null * @throws Exception */ public function insert(array $data): ?ActiveRow @@ -184,12 +146,6 @@ public function insert(array $data): ?ActiveRow return $result instanceof NetteDatabaseActiveRow ? $this->prepareRecord($result) : $result; } - /** - * Update one record - * @param ActiveRow $record - * @param array $data - * @return ActiveRow|null - */ public function update(ActiveRow $record, array $data): ?ActiveRow { $result = $this->transaction(function () use ($record, $data) { @@ -211,14 +167,9 @@ public function update(ActiveRow $record, array $data): ?ActiveRow return $result instanceof NetteDatabaseActiveRow ? $this->prepareRecord($result) : $result; } - /** - * Delete one record - * @param ActiveRow $record - * @return bool - */ public function delete(ActiveRow $record): bool { - $result = $this->transaction(function () use ($record): bool { + return $this->transaction(function () use ($record): bool { $oldRecord = clone $record; foreach ($this->behaviours as $behaviour) { @@ -239,38 +190,24 @@ public function delete(ActiveRow $record): bool return (bool) $result; }); - - return $result; } /********************************************************************\ | Internal methods \********************************************************************/ - /** - * @return NetteDatabaseSelection - */ protected function getTable(): NetteDatabaseSelection { - return $this->databaseContext->table(static::getTableName()); + return $this->databaseExplorer->table(static::getTableName()); } - /** - * @param Behaviour $behaviour - * @return Repository - */ protected function registerBehaviour(Behaviour $behaviour): Repository { $this->behaviours[get_class($behaviour)] = $behaviour; return $this; } - /** - * Get behaviour by class - * @param string $class - * @return Behaviour|null - */ - protected function getBehaviour($class): ?Behaviour + protected function getBehaviour(string $class): ?Behaviour { return $this->behaviours[$class] ?? null; } @@ -297,20 +234,12 @@ protected function getScopes(): array | Builder methods \********************************************************************/ - /** - * @param NetteDatabaseSelection $selection - * @return Selection - */ private function prepareSelection(NetteDatabaseSelection $selection): Selection { $selectionClass = $this->structure->getSelectionClass($selection->getName()); return new $selectionClass($selection, $this->structure); } - /** - * @param NetteDatabaseActiveRow $row - * @return ActiveRow - */ private function prepareRecord(NetteDatabaseActiveRow $row): ActiveRow { $rowClass = $this->structure->getActiveRowClass($row->getTable()->getName()); @@ -323,26 +252,24 @@ private function prepareRecord(NetteDatabaseActiveRow $row): ActiveRow /** * Run new transaction if no transaction is running, do nothing otherwise - * @param callable $callback - * @return mixed */ - public function transaction(callable $callback) + public function transaction(callable $callback): mixed { try { // Check if transaction already running - $inTransaction = $this->getDatabaseContext()->getConnection()->getPdo()->inTransaction(); + $inTransaction = $this->getDatabaseExplorer()->getConnection()->getPdo()->inTransaction(); if (!$inTransaction) { - $this->getDatabaseContext()->beginTransaction(); + $this->getDatabaseExplorer()->beginTransaction(); } $result = $callback($this); if (!$inTransaction) { - $this->getDatabaseContext()->commit(); + $this->getDatabaseExplorer()->commit(); } } catch (Exception $e) { if (isset($inTransaction) && !$inTransaction && $e instanceof PDOException) { - $this->getDatabaseContext()->rollBack(); + $this->getDatabaseExplorer()->rollBack(); } throw $e; } @@ -351,12 +278,9 @@ public function transaction(callable $callback) } /** - * @param callable $callback - * @param int $retryTimes - * @return mixed * @throws DriverException */ - public function ensure(callable $callback, int $retryTimes = 1) + public function ensure(callable $callback, int $retryTimes = 1): mixed { try { return $callback($this); @@ -364,19 +288,16 @@ public function ensure(callable $callback, int $retryTimes = 1) if ($retryTimes == 0) { throw $e; } - $this->getDatabaseContext()->getConnection()->reconnect(); + $this->getDatabaseExplorer()->getConnection()->reconnect(); return $this->ensure($callback, $retryTimes - 1); } } /** * Try call callback X times - * @param callable $callback - * @param int $retryTimes - * @return mixed * @throws DriverException */ - public function retry(callable $callback, int $retryTimes = 3) + public function retry(callable $callback, int $retryTimes = 3): mixed { try { return $callback($this); @@ -390,11 +311,8 @@ public function retry(callable $callback, int $retryTimes = 3) /** * Paginate callback - * @param Selection $selection - * @param int $limit - * @param callable $callback */ - public function chunk(Selection $selection, int $limit, callable $callback) + public function chunk(Selection $selection, int $limit, callable $callback): void { $count = $selection->count('*'); $pages = ceil($count / $limit); diff --git a/src/Scope/Scope.php b/src/Scope/Scope.php index efdc99b..1df411a 100644 --- a/src/Scope/Scope.php +++ b/src/Scope/Scope.php @@ -6,33 +6,22 @@ class Scope { - /** @var string */ - private $name; + private string $name; /** @var callable */ private $callback; - /** - * @param string $name - * @param callable $callback - */ public function __construct(string $name, callable $callback) { $this->name = $name; $this->callback = $callback; } - /** - * @return string - */ public function getName(): string { return $this->name; } - /** - * @return callable - */ public function getCallback(): callable { return $this->callback; diff --git a/src/Selection.php b/src/Selection.php index d78b574..008ba74 100644 --- a/src/Selection.php +++ b/src/Selection.php @@ -4,42 +4,37 @@ namespace SimpleMapper; -use Nette\Database\Table\IRow; -use Nette\Database\Table\Selection as NetteDatabaseSelection; -use Nette\Database\Table\ActiveRow as NetteDatabaseActiveRow; -use Nette\InvalidArgumentException; use ArrayAccess; -use Iterator; use Countable; +use Iterator; +use Nette\Database\Table\ActiveRow as NetteDatabaseActiveRow; +use Nette\Database\Table\IRowContainer; +use Nette\Database\Table\Selection as NetteDatabaseSelection; +use Nette\InvalidArgumentException; use SimpleMapper\Structure\Structure; -use Traversable; -class Selection implements Iterator, Countable, ArrayAccess +class Selection implements Iterator, IRowContainer, ArrayAccess, Countable { - /** @var NetteDatabaseSelection */ - private $selection; + private NetteDatabaseSelection $selection; - /** @var Structure */ - protected $structure; + protected Structure $structure; - /** - * @param NetteDatabaseSelection $selection - * @param Structure $structure - */ public function __construct(NetteDatabaseSelection $selection, Structure $structure) { $this->selection = $selection; $this->structure = $structure; } - /** - * @return NetteDatabaseSelection - */ public function getSelection(): NetteDatabaseSelection { return $this->selection; } + public function getName(): string + { + return $this->selection->getName(); + } + /********************************************************************\ | Magic methods \********************************************************************/ @@ -59,11 +54,14 @@ public function __clone() */ public function __call($name, array $arguments) { - if (substr($name, 0, 5) === 'scope') { + if (str_starts_with($name, 'scope')) { $scopeName = lcfirst(substr($name, 5)); $scope = $this->structure->getScope($this->selection->getName(), $scopeName); if (!$scope) { - trigger_error('Scope ' . $scopeName . ' is not defined for table ' . $this->selection->getName(), E_USER_ERROR); + trigger_error( + 'Scope ' . $scopeName . ' is not defined for table ' . $this->selection->getName(), + E_USER_ERROR + ); } return $this->where(call_user_func_array($scope->getCallback(), $arguments)); } @@ -75,21 +73,12 @@ public function __call($name, array $arguments) * Wrapper function - fetch \**********************************************************************/ - /** - * Returns row specified by primary key - * @param mixed $key Primary key - * @return ActiveRow|null - */ - public function get($key): ?ActiveRow + public function get(mixed $key): ?ActiveRow { $row = $this->selection->get($key); return $row instanceof NetteDatabaseActiveRow ? $this->prepareRecord($row) : null; } - /** - * Returns one record - * @return ActiveRow|null - */ public function fetch(): ?ActiveRow { $row = $this->selection->fetch(); @@ -97,20 +86,17 @@ public function fetch(): ?ActiveRow } /** - * Fetches single field - * @param string|null $column - * @return mixed + * @deprecated */ - public function fetchField(string $column = null) + public function fetchField(?string $column = null): mixed { return $this->selection->fetchField($column); } /** - * Fetch key => value pairs - * @param mixed $key - * @param mixed $value - * @return array + * Fetches all rows as associative array. + * @param string|int $key column name used for an array key or null for numeric index + * @param string|int $value column name used for an array value or null for the whole row */ public function fetchPairs($key = null, $value = null): array { @@ -124,8 +110,8 @@ public function fetchPairs($key = null, $value = null): array } /** - * Returns all records - * @return array + * Fetches all rows. + * @return NetteDatabaseActiveRow[] */ public function fetchAll(): array { @@ -133,13 +119,13 @@ public function fetchAll(): array } /** + * Fetches all rows and returns associative tree. * Some examples of usage: https://github.com/nette/utils/blob/master/tests%2FUtils%2FArrays.associate().phpt - * @param mixed $path - * @return array|\stdClass + * @param string $path associative descriptor */ - public function fetchAssoc($path) + public function fetchAssoc(string $path): array { - return $this->selection->fetchAssoc($path); + return $this->prepareRecords($this->selection->fetchAssoc($path)); } /**********************************************************************\ @@ -147,10 +133,9 @@ public function fetchAssoc($path) \**********************************************************************/ /** - * Adds select clause, more calls appends to the end - * @param string $columns for example "column, MD5(column) AS column_md5" - * @param mixed ...$params - * @return Selection + * Adds select clause, more calls appends to the end. + * @param string|string[] $columns for example "column, MD5(column) AS column_md5" + * @return static */ public function select($columns, ...$params): Selection { @@ -158,22 +143,15 @@ public function select($columns, ...$params): Selection return $this; } - /** - * Adds condition for primary key - * @param mixed $key - * @return Selection - */ - public function wherePrimary($key): Selection + public function wherePrimary(mixed $key): Selection { $this->selection->wherePrimary($key); return $this; } /** - * Adds where condition, more calls appends with AND - * @param string|string[] $condition - * @param mixed ...$params - * @return Selection + * Adds where condition, more calls appends with AND. + * @param string|array $condition possibly containing ? */ public function where($condition, ...$params): Selection { @@ -182,13 +160,11 @@ public function where($condition, ...$params): Selection } /** - * Adds ON condition when joining specified table, more calls appends with AND - * @param string $tableChain table chain or table alias for which you need additional left join condition - * @param string|string[] $condition condition possibly containing ? - * @param mixed ...$params - * @return Selection + * Adds ON condition when joining specified table, more calls appends with AND. + * @param string $tableChain table chain or table alias for which you need additional left join condition + * @param string $condition possibly containing ? */ - public function joinWhere(string $tableChain, $condition, ...$params): Selection + public function joinWhere(string $tableChain, string $condition, ...$params): Selection { $this->selection->joinWhere($tableChain, $condition, ...$params); return $this; @@ -208,10 +184,8 @@ public function whereOr(array $parameters): Selection } /** - * Adds order clause, more calls appends to the end - * @param string $columns for example 'column1, column2 DESC' - * @param mixed ...$params - * @return Selection + * Adds order clause, more calls appends to the end. + * @param string $columns for example 'column1, column2 DESC' */ public function order(string $columns, ...$params): Selection { @@ -220,12 +194,9 @@ public function order(string $columns, ...$params): Selection } /** - * Sets limit clause, more calls rewrite old values - * @param int $limit - * @param int $offset - * @return Selection + * Sets limit clause, more calls rewrite old values. */ - public function limit(int $limit, int $offset = null): Selection + public function limit(?int $limit, ?int $offset = null): Selection { $this->selection->limit($limit, $offset); return $this; @@ -233,12 +204,8 @@ public function limit(int $limit, int $offset = null): Selection /** * Sets offset using page number, more calls rewrite old values - * @param int $page - * @param int $itemsPerPage - * @param int|null $numOfPages - * @return Selection */ - public function page(int $page, int $itemsPerPage, int & $numOfPages = null): Selection + public function page(int $page, int $itemsPerPage, int &$numOfPages = null): Selection { $this->selection->page($page, $itemsPerPage, $numOfPages); return $this; @@ -246,9 +213,6 @@ public function page(int $page, int $itemsPerPage, int & $numOfPages = null): Se /** * Sets group clause, more calls rewrite old value - * @param string $columns - * @param mixed ...$params - * @return Selection */ public function group(string $columns, ...$params): Selection { @@ -258,9 +222,6 @@ public function group(string $columns, ...$params): Selection /** * Sets having clause, more calls rewrite old value - * @param string $having - * @param mixed ...$params - * @return Selection */ public function having(string $having, ...$params): Selection { @@ -270,9 +231,6 @@ public function having(string $having, ...$params): Selection /** * Aliases table. Example ':book:book_tag.tag', 'tg' - * @param string $tableChain - * @param string $alias - * @return Selection */ public function alias(string $tableChain, string $alias): Selection { @@ -285,30 +243,25 @@ public function alias(string $tableChain, string $alias): Selection \**********************************************************************/ /** - * Executes aggregation function - * @param string $function Select call in "FUNCTION(column)" format - * @return float + * Executes aggregation function. + * @param string $function select call in "FUNCTION(column)" format */ - public function aggregation(string $function): float + public function aggregation(string $function, ?string $groupFunction = null): float { - return (float) $this->selection->aggregation($function); + return (float) $this->selection->aggregation($function, $groupFunction); } /** - * Counts number of rows - * Countable interface - * @param string $column If it is not provided returns count of result rows, otherwise runs new sql counting query - * @return int + * Counts number of rows. + * @param ?string $column if it is not provided returns count of result rows, otherwise runs new sql counting query */ - public function count(string $column = null): int + public function count(?string $column = null): int { - return (int) $this->selection->count($column); + return $this->selection->count($column); } /** - * Returns minimum value from a column - * @param string $column - * @return float + * Returns minimum value from a column. */ public function min(string $column): float { @@ -316,9 +269,7 @@ public function min(string $column): float } /** - * Returns maximum value from a column - * @param string $column - * @return float + * Returns maximum value from a column. */ public function max(string $column): float { @@ -327,8 +278,6 @@ public function max(string $column): float /** * Returns sum of values in a column - * @param string $column - * @return float */ public function sum(string $column): float { @@ -340,29 +289,29 @@ public function sum(string $column): float \**********************************************************************/ /** - * Inserts row in a table - * @param array|Traversable|Selection $data - * @return IRow|int|bool + * Inserts row in a table. + * @param array|\Traversable|Selection $data [$column => $value]|\Traversable|Selection for INSERT ... SELECT + * @return ActiveRow|int|bool Returns ActiveRow or number of affected rows for Selection or table without primary key */ - public function insert($data) + public function insert(iterable $data) { $insertResult = $this->selection->insert($data); - return $insertResult instanceof IRow ? $this->prepareRecord($insertResult) : $insertResult; + return $insertResult instanceof NetteDatabaseActiveRow ? $this->prepareRecord($insertResult) : $insertResult; } /** - * Updates all rows in result set - * @param array|Traversable $data ($column => $value) - * @return int + * Updates all rows in result set. + * Joins in UPDATE are supported only in MySQL + * @return int number of affected rows */ - public function update($data): int + public function update(iterable $data): int { return $this->selection->update($data); } /** - * Deletes all rows in result set - * @return int + * Deletes all rows in result set. + * @return int number of affected rows */ public function delete(): int { @@ -373,45 +322,33 @@ public function delete(): int * Iterator interface \**********************************************************************/ - /** - * Rewind selection - */ public function rewind(): void { $this->selection->rewind(); } /** - * Returns current selection data record * @return ActiveRow|null */ public function current(): ?ActiveRow { $row = $this->selection->current(); - return $row instanceof IRow ? $this->prepareRecord($row) : null; + return $row instanceof NetteDatabaseActiveRow ? $this->prepareRecord($row) : null; } /** - * Returns current selection data key * @return string|int Row ID */ - public function key() + public function key(): mixed { return $this->selection->key(); } - /** - * Move iterator - */ public function next(): void { $this->selection->next(); } - /** - * It is selection valid - * @return bool - */ public function valid(): bool { return $this->selection->valid(); @@ -421,40 +358,22 @@ public function valid(): bool * ArrayAccess interface \**********************************************************************/ - /** - * @param string $key Row ID - * @param IRow $value - */ public function offsetSet($key, $value): void { $this->selection->offsetSet($key, $value); } - /** - * Returns specified row - * @param string $key Row ID - * @return ActiveRow|null - */ public function offsetGet($key): ?ActiveRow { $row = $this->selection->offsetGet($key); - return $row instanceof IRow ? $this->prepareRecord($row) : null; + return $row instanceof NetteDatabaseActiveRow ? $this->prepareRecord($row) : null; } - /** - * Tests if row exists - * @param string $key Row ID - * @return bool - */ public function offsetExists($key): bool { return $this->selection->offsetExists($key); } - /** - * Removes row from result set - * @param string $key Row ID - */ public function offsetUnset($key): void { $this->selection->offsetUnset($key); @@ -464,22 +383,12 @@ public function offsetUnset($key): void * Build methods \**********************************************************************/ - /** - * Prepare one record - * @param IRow $row - * @return ActiveRow - */ - protected function prepareRecord(IRow $row): ActiveRow + protected function prepareRecord(NetteDatabaseActiveRow $row): ActiveRow { $recordClass = $this->structure->getActiveRowClass($row->getTable()->getName()); return new $recordClass($row, $this->structure); } - /** - * Prepare records array - * @param array $rows - * @return array - */ protected function prepareRecords(array $rows): array { $result = []; diff --git a/src/Structure/BaseStructure.php b/src/Structure/BaseStructure.php index e820006..5115b85 100644 --- a/src/Structure/BaseStructure.php +++ b/src/Structure/BaseStructure.php @@ -11,40 +11,20 @@ class BaseStructure implements Structure { - /** @var array */ - protected $data = []; + protected array $data = []; - /** - * Register new active row class for table - * @param string $tableName - * @param string $activeRowClass - * @return Structure|BaseStructure - */ public function registerActiveRowClass(string $tableName, string $activeRowClass): Structure { $this->data[$tableName]['row'] = $activeRowClass; return $this; } - /** - * Register new selection class for table - * @param string $tableName - * @param string $selectionClass - * @return Structure|BaseStructure - */ public function registerSelectionClass(string $tableName, string $selectionClass): Structure { $this->data[$tableName]['selection'] = $selectionClass; return $this; } - /** - * Register new scopes for table - * @param string $tableName - * @param array $scopes - * @return Structure|BaseStructure - * @throws SimpleMapperException - */ public function registerScopes(string $tableName, array $scopes): Structure { foreach ($scopes as $scope) { @@ -57,42 +37,21 @@ public function registerScopes(string $tableName, array $scopes): Structure return $this; } - /** - * Fetch row class by table - * @param string $tableName - * @return string - */ public function getActiveRowClass(string $tableName): string { return $this->data[$tableName]['row'] ?? ActiveRow::class; } - /** - * Fetch selection class by table - * @param string $tableName - * @return string - */ public function getSelectionClass(string $tableName): string { return $this->data[$tableName]['selection'] ?? Selection::class; } - /** - * Returns all scopes registered for table - * @param string $tableName - * @return array - */ public function getScopes(string $tableName): array { return $this->data[$tableName]['scopes'] ?? []; } - /** - * Returns one scope - * @param string $tableName - * @param string $scope - * @return Scope|null - */ public function getScope(string $tableName, string $scope): ?Scope { return $this->data[$tableName]['scopes'][$scope] ?? null; diff --git a/src/Structure/EmptyStructure.php b/src/Structure/EmptyStructure.php index 2c70b2f..c61d237 100644 --- a/src/Structure/EmptyStructure.php +++ b/src/Structure/EmptyStructure.php @@ -10,76 +10,37 @@ class EmptyStructure implements Structure { - /** - * Register new active row class for table - * @param string $tableName - * @param string $activeRowClass - * @return Structure - */ public function registerActiveRowClass(string $tableName, string $activeRowClass): Structure { return $this; } - /** - * Register new selection class for table - * @param string $tableName - * @param string $selectionClass - * @return Structure - */ public function registerSelectionClass(string $tableName, string $selectionClass): Structure { return $this; } - /** - * Register new scopes for table - * @param string $tableName - * @param array $scopes - * @return Structure - */ public function registerScopes(string $tableName, array $scopes): Structure { return $this; } - /** - * Fetch row class by table - * @param string $table - * @return string - */ - public function getActiveRowClass(string $table): string + public function getActiveRowClass(string $tableName): string { return ActiveRow::class; } - /** - * Fetch selection class by table - * @param string $table - * @return string - */ - public function getSelectionClass(string $table): string + public function getSelectionClass(string $tableName): string { return Selection::class; } - /** - * Returns all scopes registered for table - * @param string $table - * @return array - */ - public function getScopes(string $table): array + public function getScopes(string $tableName): array { return []; } - /** - * Returns one scope - * @param string $table - * @param string $scope - * @return Scope|null - */ - public function getScope(string $table, string $scope): ?Scope + public function getScope(string $tableName, string $scope): ?Scope { return null; } diff --git a/src/Structure/Structure.php b/src/Structure/Structure.php index 56d4489..31d6269 100644 --- a/src/Structure/Structure.php +++ b/src/Structure/Structure.php @@ -4,60 +4,44 @@ namespace SimpleMapper\Structure; +use SimpleMapper\Exception\SimpleMapperException; use SimpleMapper\Scope\Scope; interface Structure { /** * Register new active row class for table - * @param string $tableName - * @param string $activeRowClass - * @return Structure */ public function registerActiveRowClass(string $tableName, string $activeRowClass): Structure; /** * Register new selection class for table - * @param string $tableName - * @param string $selectionClass - * @return Structure */ public function registerSelectionClass(string $tableName, string $selectionClass): Structure; /** * Register new scopes for table - * @param string $tableName - * @param array $scopes - * @return Structure + * @throws SimpleMapperException */ public function registerScopes(string $tableName, array $scopes): Structure; /** * Fetch row class by table - * @param string $tableName - * @return string */ public function getActiveRowClass(string $tableName): string; /** * Fetch selection class by table - * @param string $tableName - * @return string */ public function getSelectionClass(string $tableName): string; /** * Returns all scopes registered for table - * @param string $tableName - * @return array */ public function getScopes(string $tableName): array; /** * Returns one scope - * @param string $tableName - * @param string $scope - * @return Scope|null */ public function getScope(string $tableName, string $scope): ?Scope; }