From 94235644b6b713a74cd47c1cbe54eaca4c1e5ec5 Mon Sep 17 00:00:00 2001 From: Olivier Laviale Date: Wed, 20 Mar 2024 23:35:47 +0100 Subject: [PATCH] Remove query method forwarding and ArrayAccessor implementation --- MIGRATION.md | 2 + README.md | 250 +++++++------------- lib/ActiveRecord/BelongsToRelation.php | 2 +- lib/ActiveRecord/HasManyRelation.php | 2 +- lib/ActiveRecord/Model.php | 118 ++-------- tests/ActiveRecord/HasManyRelationTest.php | 6 +- tests/ActiveRecord/ModelExtendTest.php | 2 +- tests/ActiveRecord/ModelTest.php | 254 ++------------------- tests/ActiveRecord/QueryTest.php | 41 ++-- 9 files changed, 148 insertions(+), 529 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index bbf664c..dfb5f8e 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -27,6 +27,8 @@ - Removed support for `implements` in `Table`. - Removed `get_model()` - Removed the notion of scopes on Model, they are better replaced with Query extensions. +- Removed forwarded query methods on the model. Still `query()` and `where()` remain available. +- The `Model` class no longer implements `ArrayAccess`. ### Deprecated Features diff --git a/README.md b/README.md index 7aec2ba..41654ba 100644 --- a/README.md +++ b/README.md @@ -62,10 +62,7 @@ Prototype::configure([ static $validate; - if (!$validate) - { - $validate = new ValidateActiveRecord; - } + $validate ??= new ValidateActiveRecord; return $validate($record); @@ -640,7 +637,7 @@ record to the database. /* @var $model \ICanBoogie\ActiveRecord\Model */ -$record = $model[10]; +$record = $model->find(10); $record->is_online = false; $record->save(); ``` @@ -691,7 +688,7 @@ The `delete()` method deletes the active record from the database: /* @var $model \ICanBoogie\ActiveRecord\Model */ -$record = $model[190]; +$record = $model->find(190); $record->delete(); ``` @@ -751,35 +748,20 @@ to a model managing _nodes_. ### Retrieving records from the database -To retrieve objects and values from the database several finder methods are provided. Each of these -methods defines the fragments of the database query. Complex queries can be created without having +Use the `query()` or `where()` methods to start building a [Query][]. Complex queries can be created without having to write any raw SQL. -The methods are: - -* where -* select -* group -* having -* order -* limit -* offset -* join - -All of the above methods return a [Query][] instance, allowing you to chain them. - Records can be retrieved in various ways, especially using the `all`, `one`, `pairs` or `rc` magic properties. The `find()` method—used to retrieve a single record or a set of records—is the -most simple of them. +most straight forward. -#### Retrieving a single record +#### Retrieving records -Retrieving a single record using its primary key is really simple. You can either use the `find()` -method of the model, or use the model as an array. +Use a `find()` method to retrieve one or multiple records. ```php find(10); - -# or - -$article = $model[10]; ``` - - - - -#### Retrieving a set of records - Retrieving a set or records using their primary key is really simple too: ```php @@ -826,14 +798,14 @@ to the `records` property of the [RecordNotFound][] exception. #### Records caching Records retrieved using `find()` are cached, they are reused by subsequent calls. This also -applies to the array notation. +applies with multiple records. ```php find(12); // '12' retrieved from database $articles = $model->find(11, 12, 13); // '11' and '13' retrieved from database, '12' is reused. ``` @@ -868,7 +840,7 @@ use placeholders when you can't trust the source of your inputs: $model->where('is_online = ?', $_GET['online']); ``` -Of course you can use multiple conditions: +Of course, you can use multiple conditions: ```php where([ '!order_count' => [ 1,3,5 ] ]); -#### Dynamic filters - -Conditions can also be specified as methods, prefixed by `filter_by_` and separated by `_and_`: - -```php -filter_by_slug('creer-nuage-mots-cle'); -$model->filter_by_is_online_and_uid(true, 3); -``` - -Is equivalent to: - -```php -where([ 'slug' => 'creer-nuage-mots-cle' ]); -$model->where([ 'is_online' => true, 'uid' => 3 ]); -``` - - - - - #### Scopes Scopes can be viewed as model defined filters. Models can define their own filters, @@ -1062,9 +1006,9 @@ date: ```php order('created'); +$query->order('created'); ``` A direction can be specified: @@ -1072,11 +1016,11 @@ A direction can be specified: ```php order('created ASC'); +$query->order('created ASC'); # or -$model->order('created DESC'); +$query->order('created DESC'); ``` Multiple fields can be used while ordering: @@ -1084,9 +1028,9 @@ Multiple fields can be used while ordering: ```php order('created DESC, title'); +$query->order('created DESC, title'); ``` Records can also be ordered by field: @@ -1114,9 +1058,9 @@ The following example demonstrates how to retrieve the first record of records g ```php group('date(created)')->order('created'); +$query->group('date(created)')->order('created'); ``` @@ -1134,9 +1078,9 @@ month: ```php group('date(created)')->having('created > ?', new DateTime('-1 month'))->order('created'); +$query->group('date(created)')->having('created > ?', new DateTime('-1 month'))->order('created'); ``` @@ -1150,9 +1094,9 @@ The `limit()` method limits the number of records to retrieve. ```php limit(10); // retrieves the first 10 records +$query->limit(10); // retrieves the first 10 records ``` With two arguments, an offset can be specified: @@ -1160,9 +1104,9 @@ With two arguments, an offset can be specified: ```php limit(5, 10); // retrieves records from the 6th to the 16th +$query->limit(5, 10); // retrieves records from the 6th to the 16th ``` The offset can also be defined using the `offset()` method: @@ -1170,10 +1114,10 @@ The offset can also be defined using the `offset()` method: ```php offset(5); // retrieves records from the 6th to the last -$model->limit(10)->offset(5); +$query->offset(5); // retrieves records from the 6th to the last +$query->limit(10)->offset(5); ``` @@ -1192,9 +1136,9 @@ The following example demonstrates how to get the identifier, creation date and ```php select('nid, created, title'); +$query->select('nid, created, title'); ``` Because the `SELECT` string is used _as is_ to build the query, complex SQL statements can be @@ -1203,9 +1147,9 @@ used: ```php select('nid, created, CONCAT_WS(":", title, language)'); +$query->select('nid, created, CONCAT_WS(":", title, language)'); ``` @@ -1241,8 +1185,8 @@ that did not publish articles are fetched as well. /* @var $users \ICanBoogie\ActiveRecord\Model */ $online_article_count = $articles + ->where([ 'type' => 'articles', 'created_at' => new DateTime('-1 year') ]) ->select('user_id, COUNT(node_id) AS online_article_count') - ->filter_by_type_and_created_at('articles', new DateTime('-1 year')) ->online ->group('user_id'); @@ -1342,7 +1286,7 @@ The magic property `all` retrieves the complete result set as an array: /* @var $model \ICanBoogie\ActiveRecord\Model */ -$array = $model->all; +$array = $model->query()->all; $array = $model->visible->order('created DESC')->all; ``` @@ -1353,7 +1297,7 @@ The `all()` method retrieves the complete result set using a specific fetch mode /* @var $model \ICanBoogie\ActiveRecord\Model */ -$array = $model->all(\PDO::FETCH_ASSOC); +$array = $model->query()->all(\PDO::FETCH_ASSOC); $array = $model->visible->order('created DESC')->all(\PDO::FETCH_ASSOC); ``` @@ -1370,7 +1314,7 @@ The `one` magic property retrieves a single record: /* @var $model \ICanBoogie\ActiveRecord\Model */ -$record = $model->one; +$record = $model->query()->one; $record = $model->visible->order('created DESC')->one; ``` @@ -1381,7 +1325,7 @@ The `one()` method retrieves a single record using a specific fetch mode: /* @var $model \ICanBoogie\ActiveRecord\Model */ -$record = $model->one(\PDO::FETCH_ASSOC); +$record = $model->query()->one(\PDO::FETCH_ASSOC); $record = $model->visible->order('created DESC')->one(\PDO::FETCH_ASSOC); ``` @@ -1401,7 +1345,7 @@ is the key and the second its value. /* @var $model \ICanBoogie\ActiveRecord\Model */ -$model->select('nid, title')->pairs; +$model->query()->select('nid, title')->pairs; ``` Results are similar to the following example: @@ -1427,7 +1371,7 @@ The `rc` magic property retrieves the first column of the first row. /* @var $model \ICanBoogie\ActiveRecord\Model */ -$title = $model->select('title')->rc; +$title = $model->query()->select('title')->rc; ``` Note: The number of records to retrieve is automatically limited to 1. @@ -1446,7 +1390,7 @@ it. /* @var $model \ICanBoogie\ActiveRecord\Model */ -$model->select('nid, title')->mode(\PDO::FETCH_NUM); +$model->query()->select('nid, title')->mode(\PDO::FETCH_NUM); ``` The `mode()` method accepts the same arguments as the @@ -1460,8 +1404,8 @@ with the `all()` and `one()` methods. /* @var $model \ICanBoogie\ActiveRecord\Model */ -$array = $model->order('created DESC')->all(\PDO::FETCH_ASSOC); -$record = $model->order('created DESC')->one(\PDO::FETCH_ASSOC); +$array = $model->query()->order('created DESC')->all(\PDO::FETCH_ASSOC); +$record = $model->query()->order('created DESC')->one(\PDO::FETCH_ASSOC); ``` @@ -1476,9 +1420,9 @@ but returns `true` when a record is found and `false` otherwise. ```php exists(1); +$query->exists(1); ``` The method accepts multiple identifiers in which case it returns `true` when all the @@ -1487,11 +1431,11 @@ records exist, `false` when all the record don't exist, and an array otherwise. ```php exists(1, 2, 999); +$query->exists(1, 2, 999); # or -$model->exists([ 1, 2, 999 ]); +$query->exists([ 1, 2, 999 ]); ``` The method would return the following result if records "1" and "2" exist but not record "999". @@ -1511,18 +1455,7 @@ exists, `false` otherwise. /* @var $model \ICanBoogie\ActiveRecord\Model */ -$model->filter_by_author('Madonna')->exists; -``` - -The `exists` magic property of the model is `true` if the modal has at least one record, `false` -otherwise. - -```php -exists; +$model->where([ 'author' => 'Madonna' ])->exists; ``` @@ -1536,9 +1469,9 @@ The `count` magic property is the number of records in a model or matching a que ```php count; +$query->count; ``` Or on a query: @@ -1548,7 +1481,7 @@ Or on a query: /* @var $model \ICanBoogie\ActiveRecord\Model */ -$model->filter_by_firstname('Ryan')->count; +$model->where([ 'firstname' => 'Ryan' ])->count; ``` Of course, all query methods can be combined: @@ -1558,7 +1491,7 @@ Of course, all query methods can be combined: /* @var $model \ICanBoogie\ActiveRecord\Model */ -$model->filter_by_firstname('Ryan')->join(with: Content::class)->where('YEAR(date) = 2011')->count; +$model->where([ 'firstname' => 'Ryan' ])->join(with: Content::class)->and('YEAR(date) = 2011')->count; ``` The `count()` method returns an array with the number of recond for each value of a field: @@ -1568,7 +1501,7 @@ The `count()` method returns an array with the number of recond for each value o /* @var $model \ICanBoogie\ActiveRecord\Model */ -$model->count('is_online'); +$model->query()->count('is_online'); ``` ``` @@ -1593,9 +1526,9 @@ All calculation methods work directly on the model: ```php average('price'); +$query->average('price'); ``` And on a query: @@ -1605,7 +1538,7 @@ And on a query: /* @var $model \ICanBoogie\ActiveRecord\Model */ -$model->filter_by_category('Toys')->average('price'); +$model->where([ 'category' => 'Toys' ])->average('price'); ``` Of course, all query methods can be combined: @@ -1615,7 +1548,7 @@ Of course, all query methods can be combined: /* @var $model \ICanBoogie\ActiveRecord\Model */ -$model->filter_by_category('Toys')->join(with: Content::class)->where('YEAR(date) = 2011')->average('price'); +$model->where([ 'category' => 'Toys' ])->join(with: Content::class)->and('YEAR(date) = 2011')->average('price'); ``` @@ -1647,9 +1580,6 @@ to obtain only the online articles in a "music" category: /* @var $articles \ICanBoogie\ActiveRecord\Model */ $taxonomy_query = $taxonomy_terms_nodes - ->query() - ->join(with: Vocabulary::class) - ->join(with: VocabularyScope::class) ->where([ 'termslug' => "music", @@ -1657,6 +1587,8 @@ $taxonomy_query = $taxonomy_terms_nodes 'constructor' => "articles" ]) + ->join(with: Vocabulary::class) + ->join(with: VocabularyScope::class) ->select('nid'); $matches = $articles @@ -1685,7 +1617,7 @@ The records matching a query can be deleted using the `delete()` method: /* @var $nodes \ICanBoogie\ActiveRecord\Model */ $nodes - ->filter_by_is_deleted_and_uid(true, 123) + ->where([ 'is_deleted' => true, 'uid' => 123 ]) ->limit(10) ->delete(); ``` @@ -1700,7 +1632,7 @@ how to delete the nodes and comments of nodes belonging to user 123 and marked a /* @var $comments \ICanBoogie\ActiveRecord\Model */ $comments - ->filter_by_is_deleted_and_uid(true, 123) + ->where([ 'is_deleted' => true, 'uid' => 123 ]) ->join(with: Node::class) ->delete('comments, nodes'); ``` @@ -1733,8 +1665,6 @@ Retrieving records: /* @var $model \ICanBoogie\ActiveRecord\Model */ -$record = $model[10]; -# or $record = $model->find(10); $records = $model->find(10, 15, 19); @@ -1758,11 +1688,6 @@ $model->where('site_id = 0 OR site_id = ?', 1)->and('language = "" OR language = $model->where([ 'order_count' => [ 1, 2, 3 ] ]); $model->where([ '!order_count' => [ 1, 2, 3 ] ]); # NOT -# Dynamic filters - -$model->filter_by_nid(1); -$model->filter_by_site_id_and_language(1, 'fr'); - # Query extensions $model->query()->visible; @@ -1776,8 +1701,8 @@ Grouping and ordering: /* @var $model \ICanBoogie\ActiveRecord\Model */ -$model->group('date(created)')->order('created'); -$model->group('date(created)')->having('created > ?', new DateTime('-1 month'))->order('created'); +$model->query()->group('date(created)')->order('created'); +$model->query()->group('date(created)')->having('created > ?', new DateTime('-1 month'))->order('created'); ``` Limits and offsets: @@ -1785,13 +1710,13 @@ Limits and offsets: ```php limit(10); // first 10 records -$model->limit(5, 10); // 6th to the 16th records +$query->limit(10); // first 10 records +$query->limit(5, 10); // 6th to the 16th records -$model->offset(5); // from the 6th to the last -$model->offset(5)->limit(10); +$query->offset(5); // from the 6th to the last +$query->offset(5)->limit(10); ``` Fields selection: @@ -1799,10 +1724,10 @@ Fields selection: ```php select('nid, created, title'); -$model->select('nid, created, CONCAT_WS(":", title, language)'); +$query->select('nid, created, title'); +$query->select('nid, created, CONCAT_WS(":", title, language)'); ``` Joins: @@ -1824,12 +1749,12 @@ Retrieving data: /* @var $model \ICanBoogie\ActiveRecord\Model */ -$model->all; -$model->order('created DESC')->all(PDO::FETCH_ASSOC); -$model->order('created DESC')->mode(PDO::FETCH_ASSOC)->all; -$model->order('created DESC')->one; -$model->select('nid, title')->pairs; -$model->select('title')->rc; +$model->query()->all; +$model->query()->order('created DESC')->all(PDO::FETCH_ASSOC); +$model->query()->order('created DESC')->mode(PDO::FETCH_ASSOC)->all; +$model->query()->order('created DESC')->one; +$model->query()->select('nid, title')->pairs; +$model->query()->select('title')->rc; ``` Testing object existence: @@ -1839,9 +1764,9 @@ Testing object existence: /* @var $model \ICanBoogie\ActiveRecord\Model */ -$model->exists; -$model->exists(1, 2, 3); -$model->exists([ 1, 2, 3 ]); +$model->query()->exists; +$model->query()->exists(1, 2, 3); +$model->query()->exists([ 1, 2, 3 ]); $model->where('author = ?', 'madonna')->exists; ``` @@ -1852,13 +1777,12 @@ Calculations: /* @var $model \ICanBoogie\ActiveRecord\Model */ -$model->count; -$model->count('is_online'); // count is_online = 0 and is_online = 1 -$model->filter_by_is_online(true)->count; // count is_online = 1 -$model->average('score'); -$model->minimum('age'); -$model->maximum('age'); -$model->sum('comments_count'); +$model->query()->count; +$model->query()->count('is_online'); // count is_online = 0 and is_online = 1 +$model->query()->average('score'); +$model->query()->minimum('age'); +$model->query()->maximum('age'); +$model->query()->sum('comments_count'); ``` diff --git a/lib/ActiveRecord/BelongsToRelation.php b/lib/ActiveRecord/BelongsToRelation.php index 7df0664..66c8723 100644 --- a/lib/ActiveRecord/BelongsToRelation.php +++ b/lib/ActiveRecord/BelongsToRelation.php @@ -40,7 +40,7 @@ public function __invoke(ActiveRecord $record): ?ActiveRecord throw new LogicException("Unable to establish relation, '$local_key' is empty."); } - return $this->resolve_related_model()[$id]; + return $this->resolve_related_model()->find($id); } /** diff --git a/lib/ActiveRecord/HasManyRelation.php b/lib/ActiveRecord/HasManyRelation.php index e961270..e5f452a 100644 --- a/lib/ActiveRecord/HasManyRelation.php +++ b/lib/ActiveRecord/HasManyRelation.php @@ -84,7 +84,7 @@ private function build_through_query(ActiveRecord $record, string $through): Que ?? throw new LogicException("Unable to find related model for " . $related::class); $r2_model = $this->model_for_activerecord($r2->related); - $q = $related->select("`{alias}`.*"); + $q = $related->query()->select("`{alias}`.*"); // Because of the select, we need to set the mode otherwise an array would be // fetched instead of an object. $q->mode(PDO::FETCH_CLASS, $related->activerecord_class); diff --git a/lib/ActiveRecord/Model.php b/lib/ActiveRecord/Model.php index 96d8419..6e84189 100644 --- a/lib/ActiveRecord/Model.php +++ b/lib/ActiveRecord/Model.php @@ -35,24 +35,6 @@ * @template TKey of int|non-empty-string|non-empty-string[] * @template TValue of ActiveRecord * - * @implements ArrayAccess - * - * @method Query select($expression) The method is forwarded to Query::select(). - * @method Query where($conditions, $conditions_args = null, $_ = null) - * The method is forwarded to {@link Query::where}. - * @method Query group($group) The method is forwarded to Query::group(). - * @method Query order(...$order) The method is forwarded to @link Query::order(). - * @method Query limit($limit, $offset = null) The method is forwarded to Query::limit(). - * @method Query offset($offset) The method is forwarded to Query::offset(). - * @method bool exists($key = null) The method is forwarded to Query::exists(). - * @method mixed count($column = null) The method is forwarded to Query::count(). - * @method string average($column) The method is forwarded to Query::average(). - * @method string maximum($column) The method is forwarded to Query::maximum(). - * @method string minimum($column) The method is forwarded to Query::minimum(). - * @method int sum($column) The method is forwarded to Query::sum(). - * @method array all() The method is forwarded to Query::all(). - * @method ActiveRecord one() The method is forwarded to Query::one(). - * * @property-read Model|null $parent Parent model. * @property-read array $all Retrieve all the records from the model. * @property-read int $count The number of records of the model. @@ -61,7 +43,7 @@ * @property ActiveRecordCache $activerecord_cache The cache use to store activerecords. */ #[AllowDynamicProperties] -class Model extends Table implements ArrayAccess +class Model extends Table { /** * @var class-string @@ -122,10 +104,6 @@ private function resolve_parent(ModelProvider $models): ?Model */ public function __call($method, $arguments) { - if (method_exists($this->query_class, $method) || str_starts_with($method, 'filter_by_')) { - return $this->query()->$method(...$arguments); - } - if (is_callable([ $this->relations, $method ])) { return $this->relations->$method(...$arguments); } @@ -170,7 +148,7 @@ public function find(mixed $key) * * @param TKey $key * - * @return TValue + * @return ActiveRecord */ private function find_one($key): ActiveRecord { @@ -268,6 +246,18 @@ public function query(): Query return new $this->query_class($this); } + /** + * Returns a new query with the WHERE clause initialized with the provided conditions and arguments. + * + * @param ...$conditions_and_args + * + * @return Query + */ + public function where(...$conditions_and_args): Query + { + return $this->query()->where(...$conditions_and_args); + } + /** * Because records are cached, we need to remove the record from the cache when it is saved, * so that loading the record again returns the updated record, not the one in the cache. @@ -293,86 +283,6 @@ public function delete($key) return parent::delete($key); } - /** - * Checks that the SQL table associated with the model exists. - */ - protected function get_exists(): bool - { - return $this->exists(); - } - - /** - * Returns the number of records of the model. - */ - protected function get_count(): int - { - return $this->count(); - } - - /** - * Returns all the records of the model. - * - * @return TValue[] - */ - protected function get_all(): array - { - return $this->all(); - } - - /** - * Returns the first record of the model. - * - * @phpstan-return TValue - */ - protected function get_one(): ActiveRecord - { - return $this->one(); - } - - /** - * @inheritdoc - * - * @throws OffsetNotWritable when one tries to write an offset. - */ - public function offsetSet(mixed $offset, mixed $value): void - { - throw new OffsetNotWritable($offset, $this); - } - - /** - * Alias to {@link exists()}. - * - * @param int $key ActiveRecord identifier. - * - * @return bool - */ - public function offsetExists(mixed $key): bool - { - return $this->exists($key); - } - - /** - * Alias to {@link delete()}. - * - * @param int $key ActiveRecord identifier. - */ - public function offsetUnset(mixed $key): void - { - $this->delete($key); - } - - /** - * Alias to {@link find()}. - * - * @param int $key ActiveRecord identifier. - * - * @return TValue - */ - public function offsetGet(mixed $key): ActiveRecord - { - return $this->find($key); - } - /** * Creates a new ActiveRecord instance. * diff --git a/tests/ActiveRecord/HasManyRelationTest.php b/tests/ActiveRecord/HasManyRelationTest.php index 902f339..fb9d752 100644 --- a/tests/ActiveRecord/HasManyRelationTest.php +++ b/tests/ActiveRecord/HasManyRelationTest.php @@ -76,7 +76,7 @@ public function test_undefined_relation(): void public function test_getter(): void { - $article = $this->articles[1]; + $article = $this->articles->find(1); $article_comments = $article->comments; $this->assertInstanceOf(Query::class, $article_comments); @@ -85,7 +85,7 @@ public function test_getter(): void public function test_association_auto(): void { - $comments = $this->articles[1]->comments->all(); + $comments = $this->articles->find(1)->comments->all(); $this->assertCount(4, $comments); $this->assertEquals("Comment 1", $comments[0]->body); @@ -96,7 +96,7 @@ public function test_association_auto(): void public function test_association_as(): void { - $article = $this->articles[1]; + $article = $this->articles->find(1); $comments = $article->article_comments; $this->assertInstanceOf(Query::class, $comments); diff --git a/tests/ActiveRecord/ModelExtendTest.php b/tests/ActiveRecord/ModelExtendTest.php index ee5c560..4199a26 100644 --- a/tests/ActiveRecord/ModelExtendTest.php +++ b/tests/ActiveRecord/ModelExtendTest.php @@ -36,7 +36,7 @@ public function test_save(): void $this->assertEquals(1, $nid); - $record = $model[$nid]; + $record = $model->find($nid); assert($record instanceof Article); diff --git a/tests/ActiveRecord/ModelTest.php b/tests/ActiveRecord/ModelTest.php index 29d314b..b0bce00 100644 --- a/tests/ActiveRecord/ModelTest.php +++ b/tests/ActiveRecord/ModelTest.php @@ -24,7 +24,6 @@ use ICanBoogie\ActiveRecord\RecordNotFound; use ICanBoogie\ActiveRecord\SchemaBuilder; use ICanBoogie\DateTime; -use ICanBoogie\OffsetNotWritable; use PHPUnit\Framework\TestCase; use Test\ICanBoogie\Acme\Article; use Test\ICanBoogie\Acme\Count; @@ -44,7 +43,6 @@ final class ModelTest extends TestCase private Model $nodes; private Model $articles; private Model $counts_model; - private int $model_records_count; protected function setUp(): void { @@ -76,7 +74,6 @@ protected function setUp(): void $this->articles = $articles; $this->nodes = $models->model_for_record(Node::class); - $this->model_records_count = 3; $this->counts_model = $counts; } @@ -227,23 +224,6 @@ public function test_find_many_with_an_array(): void $this->assertSame($records[$id3], $records2[$id3]); } - public function test_offsets(): void - { - $model = $this->nodes; - $this->assertFalse(isset($model[uniqid()])); - $id = $model->save([ 'title' => uniqid() ]); - $this->assertTrue(isset($model[$id])); - unset($model[$id]); - $this->assertFalse(isset($model[$id])); - - try { - /** @phpstan-ignore-next-line */ - $model[$id] = null; - $this->fail("Expected OffsetNotWritable"); - } catch (OffsetNotWritable) { - } - } - public function test_new_record(): void { $model = $this->articles; @@ -255,105 +235,6 @@ public function test_new_record(): void $this->assertSame($model, $record->model); } - public function test_get_exists(): void - { - $this->assertTrue($this->articles->exists); - } - - public function test_get_count(): void - { - $this->assertEquals($this->model_records_count, $this->articles->count); - } - - public function test_get_all(): void - { - $all = $this->articles->all; - $this->assertIsArray($all); - $this->assertCount($this->model_records_count, $all); - $this->assertContainsOnlyInstancesOf(ActiveRecord::class, $all); - } - - public function test_get_one(): void - { - $one = $this->articles->one; - $this->assertInstanceOf(ActiveRecord::class, $one); - } - - /** - * @dataProvider provide_test_initiate_query - * - * @param string[] $args - */ - public function test_initiate_query(string $method, array $args): void - { - $this->assertInstanceOf(Query::class, $this->articles->$method(...$args)); - } - - /** - * @return array - */ - public static function provide_test_initiate_query(): array - { - return [ - - [ 'select', [ 'id, name' ] ], - [ 'join', [ 'JOIN some_other_table' ] ], - [ 'where', [ '1=1' ] ], - [ 'group', [ 'name' ] ], - [ 'order', [ 'name' ] ], - [ 'limit', [ '12' ] ], - [ 'offset', [ '12' ] ] - - ]; - } - - /* - * Record existence - */ - - /** - * `exists()` must return `true` when a record or all the records of a subset exist. - */ - public function test_exists_true(): void - { - $m = $this->counts_model; - $this->assertTrue($m->exists(1)); - $this->assertTrue($m->exists(1, 2, 3)); - $this->assertTrue($m->exists([ 1, 2, 3 ])); - } - - /** - * `exists()` must return `false` when a record or all the records of a subset don't exist. - */ - public function test_exists_false(): void - { - $m = $this->counts_model; - $u = rand(999, 9999); - - $this->assertFalse($m->exists($u)); - $this->assertFalse($m->exists($u + 1, $u + 2, $u + 3)); - $this->assertFalse($m->exists([ $u + 1, $u + 2, $u + 3 ])); - } - - /** - * `exists()` must return an array when some records of a subset don't exist. - */ - public function test_exists_mixed(): void - { - $m = $this->counts_model; - $u = rand(999, 9999); - $a = [ 1 => true, $u => false, 3 => true ]; - - $this->assertEquals($a, $m->exists(1, $u, 3)); - $this->assertEquals($a, $m->exists([ 1, $u, 3 ])); - } - - public function test_exists_condition(): void - { - $this->assertTrue($this->counts_model->filter_by_name('one')->exists); - $this->assertFalse($this->counts_model->filter_by_name('one ' . uniqid())->exists); - } - public function test_cache_should_be_revoked_on_save(): void { $name1 = uniqid(); @@ -361,119 +242,15 @@ public function test_cache_should_be_revoked_on_save(): void $model = $this->counts_model; $id = $model->save([ 'name' => $name1, 'date' => DateTime::now() ]); - $record = $model[$id]; + $record = $model->find($id); $model->save([ 'name' => $name2 ], $id); - $record_now = $model[$id]; + $record_now = $model->find($id); $this->assertEquals($name1, $record->name); $this->assertEquals($name2, $record_now->name); $this->assertNotSame($record, $record_now); } - /** - * @dataProvider provide_test_querying - * - * @param callable $callback - * @param string $expected - */ - public function test_querying($callback, $expected): void - { - $this->assertSame($expected, (string)$callback($this->articles)); - } - - /** - * @return array[] - */ - public static function provide_test_querying(): array - { - $p = self::PREFIX; - $l = Query::LIMIT_MAX; - - return [ - - [ - function (Model $m) { - return $m->select('nid, UPPER(name)'); - }, - <<query()->join(expression: 'INNER JOIN other USING(nid)'); - }, - <<where([ 'nid' => 1, 'name' => 'madonna' ]); - }, - <<group('name'); - }, - <<order('nid'); - }, - <<order('nid', 1, 2, 3); - }, - <<limit(5); - }, - <<limit(5, 10); - }, - <<offset(5); - }, - <<assertInstanceOf(ActiveRecordCache::class, $activerecord_cache); for ($i = 1; $i < 5; $i++) { - $records[$i] = $model[$i]; + $records[$i] = $model->find($i); } for ($i = 1; $i < 5; $i++) { $this->assertSame($records[$i], $activerecord_cache->retrieve($i)); - $this->assertSame($records[$i], $model[$i]); + $this->assertSame($records[$i], $model->find($i)); } $activerecord_cache->clear(); for ($i = 1; $i < 5; $i++) { $this->assertNull($activerecord_cache->retrieve($i)); - $this->assertNotSame($records[$i], $model[$i]); + $this->assertNotSame($records[$i], $model->find($i)); } # @@ -528,17 +305,24 @@ public function test_activerecord_cache(): void $records[1]->delete(); $this->assertNull($activerecord_cache->retrieve(1)); $this->expectException(RecordNotFound::class); - $model[1]; + $model->find(1); } - public function test_regular_query(): void + + public function test_query(): void { - $model = $this->nodes; - $query = $model->query(); + $actual = $this->articles->query(); + + $this->assertInstanceOf(Query::class, $actual); + $this->assertNotSame($actual, $this->articles->query()); + } + + public function test_where(): void + { + $actual = $this->articles->where(); - $this->assertSame($model, $query->model); - // The method creates a new query - $this->assertNotSame($query, $model->query()); + $this->assertInstanceOf(Query::class, $actual); + $this->assertNotSame($actual, $this->articles->where()); } public function test_custom_query(): void diff --git a/tests/ActiveRecord/QueryTest.php b/tests/ActiveRecord/QueryTest.php index 58b2f4b..8532b3a 100644 --- a/tests/ActiveRecord/QueryTest.php +++ b/tests/ActiveRecord/QueryTest.php @@ -30,23 +30,24 @@ final class QueryTest extends TestCase { private const N = 10; - private Model $nodes; - private Model $articles; - private Model $updates; - private Model $subscribers; + private Query $nodes; + private Query $articles; + private Query $updates; + private Query $subscribers; protected function setUp(): void { parent::setUp(); - $models = Fixtures::only_models('nodes', 'comments', 'articles', 'subscribers', 'updates'); + $models = Fixtures::only_models('nodes', 'comments', 'articles', 'subscribers', 'updates'); $models->install(); + $articles = $models->model_for_record(Article::class); - $this->nodes = $models->model_for_record(Node::class); - $this->articles = $models->model_for_record(Article::class); - $this->updates = $models->model_for_record(Update::class); - $this->subscribers = $models->model_for_record(Subscriber::class); + $this->nodes = $models->model_for_record(Node::class)->query(); + $this->articles = $articles->query(); + $this->updates = $models->model_for_record(Update::class)->query(); + $this->subscribers = $models->model_for_record(Subscriber::class)->query(); for ($i = 0; $i < self::N; $i++) { $properties = [ @@ -58,7 +59,7 @@ protected function setUp(): void ]; - $key = $this->articles->save($properties); + $key = $articles->save($properties); } } @@ -115,7 +116,7 @@ public function test_order_by_field(): void public function test_conditions(): void { - $query = new Query($this->articles); + $query = $this->articles; $query->where([ 'title' => 'madonna' ]) ->filter_by_rating(2) @@ -140,7 +141,7 @@ public function test_conditions(): void public function test_join_with_expression(): void { - $query = $this->updates->query()->join(expression: "INNER JOIN madonna USING(madonna_id)"); + $query = $this->updates->join(expression: "INNER JOIN madonna USING(madonna_id)"); $this->assertEquals( [ "INNER JOIN madonna USING(madonna_id)" ], @@ -158,7 +159,6 @@ public function test_join_with_query(): void ->order('updated_at DESC'); $subscriber_query = $subscribers - ->query() ->join(query: $update_query, on: 'subscriber_id') ->group("`{alias}`.subscriber_id"); @@ -184,7 +184,6 @@ public function test_join_with_query_with_args(): void ->order('updated_at DESC'); $subscriber_query = $subscribers - ->query() ->join(query: $update_query, on: 'subscriber_id') ->filter_by_email('person@example.com') ->group("`{alias}`.subscriber_id"); @@ -200,29 +199,29 @@ public function test_join_with_query_with_args(): void public function test_join_with_model(): void { - $updates = $this->updates; - - $actual = (string)$updates->select('update_id, email')->join(with: Subscriber::class); + $q1 = clone $this->updates; + $q2 = clone $this->updates; + $q3 = clone $this->updates; $this->assertEquals( "SELECT update_id, email FROM `updates` `update` INNER JOIN `subscribers` AS `subscriber` USING(`subscriber_id`)", - $actual + (string)$q1->select('update_id, email')->join(with: Subscriber::class) ); $this->assertEquals( "SELECT update_id, email FROM `updates` `update` INNER JOIN `subscribers` AS `sub` USING(`subscriber_id`)", - (string)$updates->select('update_id, email')->join(with: Subscriber::class, as: 'sub') + (string)$q2->select('update_id, email')->join(with: Subscriber::class, as: 'sub') ); $this->assertEquals( "SELECT update_id, email FROM `updates` `update` LEFT JOIN `subscribers` AS `sub` USING(`subscriber_id`)", - (string)$updates->select('update_id, email')->join(with: Subscriber::class, mode: 'LEFT', as: 'sub') + (string)$q3->select('update_id, email')->join(with: Subscriber::class, mode: 'LEFT', as: 'sub') ); } public function test_query_extension(): void { - $query = $this->articles->query(); + $query = clone $this->articles; $this->assertInstanceOf(ArticleQuery::class, $query);