├── .phpunit-watcher.yml
├── .styleci.yml
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── composer-require-checker.json
├── composer.json
├── infection.json.dist
├── psalm.xml
├── rector.php
├── src
├── .meta-storm.xml
├── AbstractActiveRecord.php
├── ActiveQuery.php
├── ActiveQueryInterface.php
├── ActiveQueryTrait.php
├── ActiveRecord.php
├── ActiveRecordInterface.php
├── ActiveRelationTrait.php
├── ArArrayHelper.php
├── ConnectionProvider.php
├── ConnectionProviderMiddleware.php
├── NotFoundException.php
├── OptimisticLockException.php
├── OptimisticLockInterface.php
└── Trait
│ ├── ArrayAccessTrait.php
│ ├── ArrayIteratorTrait.php
│ ├── ArrayableTrait.php
│ ├── CustomConnectionTrait.php
│ ├── CustomTableNameTrait.php
│ ├── FactoryTrait.php
│ ├── MagicPropertiesTrait.php
│ ├── MagicRelationsTrait.php
│ └── RepositoryTrait.php
└── tests
├── ActiveQueryFindTest.php
├── ActiveQueryTest.php
├── ActiveRecordTest.php
├── ArrayableTraitTest.php
├── BatchQueryResultTest.php
├── ConnectionProviderTest.php
├── Driver
├── Mssql
│ ├── ActiveQueryFindTest.php
│ ├── ActiveQueryTest.php
│ ├── ActiveRecordTest.php
│ ├── ArrayableTraitTest.php
│ ├── BatchQueryResultTest.php
│ ├── ConnectionProviderTest.php
│ ├── MagicActiveRecordTest.php
│ └── RepositoryTraitTest.php
├── Mysql
│ ├── ActiveQueryFindTest.php
│ ├── ActiveQueryTest.php
│ ├── ActiveRecordTest.php
│ ├── ArrayableTraitTest.php
│ ├── BatchQueryResultTest.php
│ ├── ConnectionProviderTest.php
│ ├── MagicActiveRecordTest.php
│ ├── RepositoryTraitTest.php
│ └── Stubs
│ │ └── Type.php
├── Oracle
│ ├── ActiveQueryFindTest.php
│ ├── ActiveQueryTest.php
│ ├── ActiveRecordTest.php
│ ├── ArrayableTraitTest.php
│ ├── BatchQueryResultTest.php
│ ├── ConnectionProviderTest.php
│ ├── MagicActiveRecordTest.php
│ ├── RepositoryTraitTest.php
│ └── Stubs
│ │ ├── Customer.php
│ │ ├── MagicCustomer.php
│ │ ├── MagicOrder.php
│ │ └── Order.php
├── Pgsql
│ ├── ActiveQueryFindTest.php
│ ├── ActiveQueryTest.php
│ ├── ActiveRecordTest.php
│ ├── ArrayableTraitTest.php
│ ├── BatchQueryResultTest.php
│ ├── ConnectionProviderTest.php
│ ├── MagicActiveRecordTest.php
│ ├── RepositoryTraitTest.php
│ └── Stubs
│ │ ├── Item.php
│ │ ├── Promotion.php
│ │ └── Type.php
└── Sqlite
│ ├── ActiveQueryFindTest.php
│ ├── ActiveQueryTest.php
│ ├── ActiveRecordTest.php
│ ├── ArrayableTraitTest.php
│ ├── BatchQueryResultTest.php
│ ├── ConnectionProviderTest.php
│ ├── MagicActiveRecordTest.php
│ └── RepositoryTraitTest.php
├── MagicActiveRecordTest.php
├── RepositoryTraitTest.php
├── Stubs
├── ActiveRecord
│ ├── Alpha.php
│ ├── Animal.php
│ ├── ArrayAndJsonTypes.php
│ ├── Beta.php
│ ├── BitValues.php
│ ├── BoolAR.php
│ ├── Cat.php
│ ├── Category.php
│ ├── Customer.php
│ ├── CustomerClosureField.php
│ ├── CustomerForArrayable.php
│ ├── CustomerQuery.php
│ ├── CustomerWithAlias.php
│ ├── CustomerWithCustomConnection.php
│ ├── CustomerWithFactory.php
│ ├── DefaultPk.php
│ ├── Department.php
│ ├── Document.php
│ ├── Dog.php
│ ├── Dossier.php
│ ├── Employee.php
│ ├── Item.php
│ ├── NoExist.php
│ ├── NullValues.php
│ ├── Order.php
│ ├── OrderItem.php
│ ├── OrderItemWithNullFK.php
│ ├── OrderWithFactory.php
│ ├── OrderWithNullFK.php
│ ├── Profile.php
│ ├── ProfileWithConstructor.php
│ ├── Promotion.php
│ ├── TestTrigger.php
│ ├── TestTriggerAlert.php
│ ├── Type.php
│ ├── UnqueryableQueryMock.php
│ └── UserAR.php
├── ArrayableActiveRecord.php
├── MagicActiveRecord.php
└── MagicActiveRecord
│ ├── Alpha.php
│ ├── Animal.php
│ ├── ArrayAndJsonTypes.php
│ ├── Beta.php
│ ├── BitValues.php
│ ├── BoolAR.php
│ ├── Cat.php
│ ├── Category.php
│ ├── Customer.php
│ ├── CustomerQuery.php
│ ├── CustomerWithAlias.php
│ ├── CustomerWithProperties.php
│ ├── DefaultPk.php
│ ├── Department.php
│ ├── Document.php
│ ├── Dog.php
│ ├── Dossier.php
│ ├── Employee.php
│ ├── Item.php
│ ├── NoExist.php
│ ├── NullValues.php
│ ├── Order.php
│ ├── OrderItem.php
│ ├── OrderItemWithNullFK.php
│ ├── OrderWithNullFK.php
│ ├── Profile.php
│ ├── ProfileWithConstructor.php
│ ├── TestTrigger.php
│ ├── TestTriggerAlert.php
│ ├── Type.php
│ ├── UnqueryableQueryMock.php
│ └── UserAR.php
├── Support
├── Assert.php
├── ConnectionHelper.php
├── DbHelper.php
├── ModelFactory.php
├── MssqlHelper.php
├── MysqlHelper.php
├── OracleHelper.php
├── PgsqlHelper.php
└── SqliteHelper.php
├── TestCase.php
└── data
├── mssql.sql
├── mysql.sql
├── oci.sql
├── pgsql.sql
├── runtime
└── .gitignore
└── sqlite.sql
/.phpunit-watcher.yml:
--------------------------------------------------------------------------------
1 | watch:
2 | directories:
3 | - src
4 | - tests
5 | fileMask: '*.php'
6 | notifications:
7 | passingTests: false
8 | failingTests: false
9 | phpunit:
10 | binaryPath: vendor/bin/phpunit
11 | timeout: 180
12 |
--------------------------------------------------------------------------------
/.styleci.yml:
--------------------------------------------------------------------------------
1 | preset: psr12
2 | risky: true
3 |
4 | version: 8.1
5 |
6 | finder:
7 | exclude:
8 | - docs
9 | - vendor
10 |
11 | enabled:
12 | - alpha_ordered_traits
13 | - array_indentation
14 | - array_push
15 | - combine_consecutive_issets
16 | - combine_consecutive_unsets
17 | - combine_nested_dirname
18 | - declare_strict_types
19 | - dir_constant
20 | - fully_qualified_strict_types
21 | - function_to_constant
22 | - hash_to_slash_comment
23 | - is_null
24 | - logical_operators
25 | - magic_constant_casing
26 | - magic_method_casing
27 | - method_separation
28 | - modernize_types_casting
29 | - native_function_casing
30 | - native_function_type_declaration_casing
31 | - no_alias_functions
32 | - no_empty_comment
33 | - no_empty_phpdoc
34 | - no_empty_statement
35 | - no_extra_block_blank_lines
36 | - no_short_bool_cast
37 | - no_superfluous_elseif
38 | - no_unneeded_control_parentheses
39 | - no_unneeded_curly_braces
40 | - no_unneeded_final_method
41 | - no_unset_cast
42 | - no_unused_imports
43 | - no_unused_lambda_imports
44 | - no_useless_else
45 | - no_useless_return
46 | - normalize_index_brace
47 | - php_unit_dedicate_assert
48 | - php_unit_dedicate_assert_internal_type
49 | - php_unit_expectation
50 | - php_unit_mock
51 | - php_unit_mock_short_will_return
52 | - php_unit_namespaced
53 | - php_unit_no_expectation_annotation
54 | - phpdoc_no_empty_return
55 | - phpdoc_no_useless_inheritdoc
56 | - phpdoc_order
57 | - phpdoc_property
58 | - phpdoc_scalar
59 | - phpdoc_singular_inheritdoc
60 | - phpdoc_trim
61 | - phpdoc_trim_consecutive_blank_line_separation
62 | - phpdoc_type_to_var
63 | - phpdoc_types
64 | - phpdoc_types_order
65 | - print_to_echo
66 | - regular_callable_call
67 | - return_assignment
68 | - self_accessor
69 | - self_static_accessor
70 | - set_type_to_cast
71 | - short_array_syntax
72 | - short_list_syntax
73 | - simplified_if_return
74 | - single_quote
75 | - standardize_not_equals
76 | - ternary_to_null_coalescing
77 | - trailing_comma_in_multiline_array
78 | - unalign_double_arrow
79 | - unalign_equals
80 | - empty_loop_body_braces
81 | - integer_literal_case
82 | - union_type_without_spaces
83 |
84 | disabled:
85 | - function_declaration
86 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Yii Active Record Change Log
2 |
3 | ## 1.0.0 under development
4 |
5 | - Enh #40: Added public method `yii\data\ActiveDataProvider::prepareQuery()` for debug facilitation (GHopperMSK)
6 | - Enh #34: Adjustments to new DI and events replacements (fabriziocaldarelli)
7 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright © 2008 by Yii Software ()
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions
6 | are met:
7 |
8 | * Redistributions of source code must retain the above copyright
9 | notice, this list of conditions and the following disclaimer.
10 | * Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in
12 | the documentation and/or other materials provided with the
13 | distribution.
14 | * Neither the name of Yii Software nor the names of its
15 | contributors may be used to endorse or promote products derived
16 | from this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 | POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/composer-require-checker.json:
--------------------------------------------------------------------------------
1 | {
2 | "symbol-whitelist": [
3 | "Psr\\Http\\Message\\ResponseInterface",
4 | "Psr\\Http\\Message\\ServerRequestInterface",
5 | "Psr\\Http\\Server\\MiddlewareInterface",
6 | "Psr\\Http\\Server\\RequestHandlerInterface",
7 | "Yiisoft\\Arrays\\ArrayableTrait",
8 | "Yiisoft\\Factory\\Factory"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "yiisoft/active-record",
3 | "type": "library",
4 | "description": "Yii ActiveRecord Library",
5 | "keywords": [
6 | "yii",
7 | "active record"
8 | ],
9 | "homepage": "https://www.yiiframework.com/",
10 | "license": "BSD-3-Clause",
11 | "support": {
12 | "issues": "https://github.com/yiisoft/active-record/issues?state=open",
13 | "source": "https://github.com/yiisoft/active-record",
14 | "forum": "https://www.yiiframework.com/forum/",
15 | "wiki": "https://www.yiiframework.com/wiki/",
16 | "irc": "ircs://irc.libera.chat:6697/yii",
17 | "chat": "https://t.me/yii3en"
18 | },
19 | "funding": [
20 | {
21 | "type": "opencollective",
22 | "url": "https://opencollective.com/yiisoft"
23 | },
24 | {
25 | "type": "github",
26 | "url": "https://github.com/sponsors/yiisoft"
27 | }
28 | ],
29 | "minimum-stability": "dev",
30 | "prefer-stable": true,
31 | "require": {
32 | "php": "8.1 - 8.4",
33 | "yiisoft/db": "dev-master"
34 | },
35 | "require-dev": {
36 | "maglnet/composer-require-checker": "^4.7.1",
37 | "phpunit/phpunit": "^10.5.45",
38 | "rector/rector": "^2.0.10",
39 | "roave/infection-static-analysis-plugin": "^1.35",
40 | "spatie/phpunit-watcher": "^1.24",
41 | "vimeo/psalm": "^5.26.1 || ^6.8.8",
42 | "yiisoft/aliases": "^2.0",
43 | "yiisoft/arrays": "^3.2",
44 | "yiisoft/cache": "^3.0",
45 | "yiisoft/db-sqlite": "dev-master",
46 | "yiisoft/di": "^1.3",
47 | "yiisoft/factory": "^1.3",
48 | "yiisoft/middleware-dispatcher": "^5.2"
49 | },
50 | "suggest": {
51 | "yiisoft/arrays": "For \\Yiisoft\\Arrays\\ArrayableInterface support",
52 | "yiisoft/db-sqlite": "For SQLite database support",
53 | "yiisoft/db-mysql": "For MySQL database support",
54 | "yiisoft/db-pgsql": "For PostgreSQL database support",
55 | "yiisoft/db-mssql": "For MSSQL database support",
56 | "yiisoft/db-oracle": "For Oracle database support",
57 | "yiisoft/factory": "For factory support",
58 | "yiisoft/middleware-dispatcher": "For middleware support"
59 | },
60 | "autoload": {
61 | "psr-4": {
62 | "Yiisoft\\ActiveRecord\\": "src"
63 | }
64 | },
65 | "autoload-dev": {
66 | "psr-4": {
67 | "Yiisoft\\ActiveRecord\\Tests\\": "tests"
68 | }
69 | },
70 | "extra": {
71 | "branch-alias": {
72 | "dev-master": "3.0.x-dev"
73 | }
74 | },
75 | "config": {
76 | "sort-packages": true,
77 | "allow-plugins": {
78 | "infection/extension-installer": true,
79 | "composer/package-versions-deprecated": true
80 | }
81 | },
82 | "scripts": {
83 | "test": "phpunit --testdox --no-interaction",
84 | "test-watch": "phpunit-watcher watch"
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/infection.json.dist:
--------------------------------------------------------------------------------
1 | {
2 | "source": {
3 | "directories": [
4 | "src"
5 | ]
6 | },
7 | "timeout": 45,
8 | "logs": {
9 | "text": "php:\/\/stderr",
10 | "stryker": {
11 | "report": "master"
12 | }
13 | },
14 | "mutators": {
15 | "@default": true,
16 | "ArrayItemRemoval": false
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/psalm.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/rector.php:
--------------------------------------------------------------------------------
1 | paths([
13 | __DIR__ . '/src',
14 | /** Disable rector on tests */
15 | // __DIR__ . '/tests',
16 | ]);
17 |
18 | // register a single rule
19 | $rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class);
20 |
21 | // define sets of rules
22 | $rectorConfig->sets([
23 | LevelSetList::UP_TO_PHP_81,
24 | ]);
25 |
26 | $rectorConfig->skip([
27 | NullToStrictStringFuncCallArgRector::class,
28 | ReadOnlyPropertyRector::class,
29 | ]);
30 | };
31 |
--------------------------------------------------------------------------------
/src/.meta-storm.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/ActiveQueryTrait.php:
--------------------------------------------------------------------------------
1 | asArray = $value;
34 | return $this;
35 | }
36 |
37 | /**
38 | * Specifies the relations with which this query should be performed.
39 | *
40 | * The parameters to this method can be either one or multiple strings, or a single array of relation names and the
41 | * optional callbacks to customize the relations.
42 | *
43 | * A relation name can refer to a relation defined in {@see modelClass} or a sub-relation that stands for a relation
44 | * of a related record.
45 | *
46 | * For example, `orders.address` means the `address` relation defined in the model class corresponding to the
47 | * `orders` relation.
48 | *
49 | * The following are some usage examples:
50 | *
51 | * ```php
52 | * // Create active query
53 | * CustomerQuery = new ActiveQuery(Customer::class);
54 | * // find customers together with their orders and country
55 | * CustomerQuery->with('orders', 'country')->all();
56 | * // find customers together with their orders and the orders' shipping address
57 | * CustomerQuery->with('orders.address')->all();
58 | * // find customers together with their country and orders of status 1
59 | * CustomerQuery->with([
60 | * 'orders' => function (ActiveQuery $query) {
61 | * $query->andWhere('status = 1');
62 | * },
63 | * 'country',
64 | * ])->all();
65 | * ```
66 | *
67 | * You can call `with()` multiple times. Each call will add relations to the existing ones.
68 | *
69 | * For example, the following two statements are equivalent:
70 | *
71 | * ```php
72 | * CustomerQuery->with('orders', 'country')->all();
73 | * CustomerQuery->with('orders')->with('country')->all();
74 | * ```
75 | *
76 | * @param array|string ...$with A list of relation names or relation definitions.
77 | *
78 | * @return static The query object itself.
79 | */
80 | public function with(array|string ...$with): static
81 | {
82 | if (isset($with[0]) && is_array($with[0])) {
83 | /** the parameter is given as an array */
84 | $with = $with[0];
85 | }
86 |
87 | if (empty($this->with)) {
88 | $this->with = $with;
89 | } elseif (!empty($with)) {
90 | foreach ($with as $name => $value) {
91 | if (is_int($name)) {
92 | /** repeating relation is fine as normalizeRelations() handle it well */
93 | $this->with[] = $value;
94 | } else {
95 | $this->with[$name] = $value;
96 | }
97 | }
98 | }
99 |
100 | return $this;
101 | }
102 |
103 | /**
104 | * Converts found rows into model instances.
105 | *
106 | * @param array[] $rows The rows to be converted.
107 | *
108 | * @throws InvalidConfigException
109 | * @return ActiveRecordInterface[]|array[] The model instances.
110 | *
111 | * @psalm-param non-empty-list $rows
112 | * @psalm-return non-empty-list
113 | */
114 | protected function createModels(array $rows): array
115 | {
116 | if ($this->asArray) {
117 | return $rows;
118 | }
119 |
120 | if ($this->resultCallback !== null) {
121 | $rows = ($this->resultCallback)($rows);
122 |
123 | if ($rows[0] instanceof ActiveRecordInterface) {
124 | /** @psalm-var non-empty-list */
125 | return $rows;
126 | }
127 | }
128 |
129 | $models = [];
130 |
131 | foreach ($rows as $row) {
132 | $arClass = $this->getARInstance();
133 | $arClass->populateRecord($row);
134 |
135 | $models[] = $arClass;
136 | }
137 |
138 | return $models;
139 | }
140 |
141 | /**
142 | * Finds records corresponding to one or multiple relations and populates them into the primary models.
143 | *
144 | * @param array $with a list of relations that this query should be performed with. Please refer to {@see with()}
145 | * for details about specifying this parameter.
146 | * @param ActiveRecordInterface[]|array[] $models the primary models (can be either AR instances or arrays)
147 | *
148 | * @throws Exception
149 | * @throws InvalidArgumentException
150 | * @throws NotSupportedException
151 | * @throws ReflectionException
152 | * @throws Throwable
153 | *
154 | * @psalm-param non-empty-list $models
155 | * @psalm-param-out non-empty-list $models
156 | */
157 | public function findWith(array $with, array &$models): void
158 | {
159 | $primaryModel = reset($models);
160 |
161 | if (!$primaryModel instanceof ActiveRecordInterface) {
162 | $primaryModel = $this->getARInstance();
163 | }
164 |
165 | $relations = $this->normalizeRelations($primaryModel, $with);
166 |
167 | foreach ($relations as $name => $relation) {
168 | if ($relation->isAsArray() === null) {
169 | /** inherit asArray from a primary query */
170 | $relation->asArray($this->asArray);
171 | }
172 |
173 | $relation->populateRelation($name, $models);
174 | }
175 | }
176 |
177 | /**
178 | * @return ActiveQueryInterface[]
179 | */
180 | private function normalizeRelations(ActiveRecordInterface $model, array $with): array
181 | {
182 | $relations = [];
183 |
184 | foreach ($with as $name => $callback) {
185 | if (is_int($name)) {
186 | $name = $callback;
187 | $callback = null;
188 | }
189 |
190 | if (($pos = strpos($name, '.')) !== false) {
191 | /** with sub-relations */
192 | $childName = substr($name, $pos + 1);
193 | $name = substr($name, 0, $pos);
194 | } else {
195 | $childName = null;
196 | }
197 |
198 | if (!isset($relations[$name])) {
199 | /** @var ActiveQuery $relation */
200 | $relation = $model->relationQuery($name);
201 | $relation->primaryModel = null;
202 | $relations[$name] = $relation;
203 | } else {
204 | $relation = $relations[$name];
205 | }
206 |
207 | if (isset($childName)) {
208 | $relation->with[$childName] = $callback;
209 | } elseif ($callback !== null) {
210 | $callback($relation);
211 | }
212 | }
213 |
214 | return $relations;
215 | }
216 |
217 | public function isAsArray(): bool|null
218 | {
219 | return $this->asArray;
220 | }
221 |
222 | public function getWith(): array
223 | {
224 | return $this->with;
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/src/ActiveRecord.php:
--------------------------------------------------------------------------------
1 | name`.
30 | *
31 | * In this example, Active Record is providing an object-oriented interface for accessing data stored in the database.
32 | * But Active Record provides much more functionality than this.
33 | *
34 | * To declare an ActiveRecord class, you need to extend {@see ActiveRecord} and implement the `tableName` method:
35 | *
36 | * ```php
37 | * class Customer extends ActiveRecord
38 | * {
39 | * public static function tableName(): string
40 | * {
41 | * return 'customer';
42 | * }
43 | * }
44 | * ```
45 | *
46 | * The `tableName` method only has to return the name of the database table associated with the class.
47 | *
48 | * Class instances are obtained in one of two ways:
49 | *
50 | * Using the `new` operator to create a new, empty object.
51 | * Using a method to fetch an existing record (or records) from the database.
52 | *
53 | * Below is an example showing some typical usage of ActiveRecord:
54 | *
55 | * ```php
56 | * $user = new User();
57 | * $user->name = 'Qiang';
58 | * $user->save(); // a new row is inserted into user table
59 | *
60 | * // the following will retrieve the user 'CeBe' from the database
61 | * $userQuery = new ActiveQuery(User::class);
62 | * $user = $userQuery->where(['name' => 'CeBe'])->one();
63 | *
64 | * // this will get related records from orders table when relation is defined
65 | * $orders = $user->orders;
66 | * ```
67 | *
68 | * For more details and usage information on ActiveRecord,
69 | * {@see the [guide article on ActiveRecord](guide:db-active-record)}
70 | *
71 | * @psalm-suppress ClassMustBeFinal
72 | */
73 | class ActiveRecord extends AbstractActiveRecord
74 | {
75 | public function propertyNames(): array
76 | {
77 | return $this->tableSchema()->getColumnNames();
78 | }
79 |
80 | public function columnType(string $propertyName): string
81 | {
82 | return $this->tableSchema()->getColumn($propertyName)?->getType() ?? ColumnType::STRING;
83 | }
84 |
85 | /**
86 | * Returns the schema information of the DB table associated with this AR class.
87 | *
88 | * @throws Exception
89 | * @throws InvalidConfigException If the table for the AR class doesn't exist.
90 | *
91 | * @return TableSchemaInterface The schema information of the DB table associated with this AR class.
92 | */
93 | public function tableSchema(): TableSchemaInterface
94 | {
95 | $tableSchema = $this->db()->getSchema()->getTableSchema($this->tableName());
96 |
97 | if ($tableSchema === null) {
98 | throw new InvalidConfigException('The table does not exist: ' . $this->tableName());
99 | }
100 |
101 | return $tableSchema;
102 | }
103 |
104 | /**
105 | * Loads default values from database table schema.
106 | *
107 | * You may call this method to load default values after creating a new instance:
108 | *
109 | * ```php
110 | * // class Customer extends ActiveRecord
111 | * $customer = new Customer();
112 | * $customer->loadDefaultValues();
113 | * ```
114 | *
115 | * @param bool $skipIfSet Whether existing value should be preserved. This will only set defaults for properties
116 | * that are `null`.
117 | *
118 | * @throws Exception
119 | * @throws InvalidConfigException
120 | *
121 | * @return static The active record instance itself.
122 | */
123 | public function loadDefaultValues(bool $skipIfSet = true): static
124 | {
125 | foreach ($this->tableSchema()->getColumns() as $name => $column) {
126 | if ($column->getDefaultValue() !== null && (!$skipIfSet || $this->get($name) === null)) {
127 | $this->set($name, $column->getDefaultValue());
128 | }
129 | }
130 |
131 | return $this;
132 | }
133 |
134 | public function populateRecord(array|object $row): void
135 | {
136 | $row = ArArrayHelper::toArray($row);
137 | $columns = $this->tableSchema()->getColumns();
138 | $rowColumns = array_intersect_key($row, $columns);
139 |
140 | foreach ($rowColumns as $name => &$value) {
141 | $value = $columns[$name]->phpTypecast($value);
142 | }
143 |
144 | parent::populateRecord($rowColumns + $row);
145 | }
146 |
147 | public function primaryKey(): array
148 | {
149 | return $this->tableSchema()->getPrimaryKey();
150 | }
151 |
152 | protected function propertyValuesInternal(): array
153 | {
154 | return get_object_vars($this);
155 | }
156 |
157 | protected function insertInternal(array|null $properties = null): bool
158 | {
159 | if (!$this->isNewRecord()) {
160 | throw new InvalidCallException('The record is not new and cannot be inserted.');
161 | }
162 |
163 | $values = $this->newPropertyValues($properties);
164 | $primaryKeys = $this->db()->createCommand()->insertWithReturningPks($this->tableName(), $values);
165 |
166 | if ($primaryKeys === false) {
167 | return false;
168 | }
169 |
170 | $columns = $this->tableSchema()->getColumns();
171 |
172 | foreach ($primaryKeys as $name => $value) {
173 | $id = $columns[$name]->phpTypecast($value);
174 | $this->set($name, $id);
175 | $values[$name] = $id;
176 | }
177 |
178 | $this->assignOldValues($values);
179 |
180 | return true;
181 | }
182 |
183 | protected function populateProperty(string $name, mixed $value): void
184 | {
185 | $this->$name = $value;
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/src/ArArrayHelper.php:
--------------------------------------------------------------------------------
1 | '123', 'data' => 'abc'],
38 | * ['id' => '345', 'data' => 'def'],
39 | * ];
40 | * $result = ArArrayHelper::getColumn($array, 'id');
41 | * // the result is: ['123', '345']
42 | * ```
43 | *
44 | * @param ActiveRecordInterface[]|array[] $array Array to extract values from.
45 | * @param string $name The column name.
46 | *
47 | * @return array The list of column values.
48 | */
49 | public static function getColumn(array $array, string $name): array
50 | {
51 | return array_map(
52 | static fn (ActiveRecordInterface|array $element): mixed => self::getValueByPath($element, $name),
53 | $array
54 | );
55 | }
56 |
57 | /**
58 | * Retrieves a value from the array by the given key or from the {@see ActiveRecordInterface} instance
59 | * by the given property or relation name.
60 | *
61 | * If the key doesn't exist, the default value will be returned instead.
62 | *
63 | * The key may be specified in a dot format to retrieve the value of a sub-array or a property or relation of the
64 | * {@see ActiveRecordInterface} instance.
65 | *
66 | * In particular, if the key is `x.y.z`, then the returned value would be `$array['x']['y']['z']` or
67 | * `$array->x->y->z` (if `$array` is an {@see ActiveRecordInterface} instance).
68 | *
69 | * Note that if the array already has an element `x.y.z`, then its value will be returned instead of going through
70 | * the sub-arrays.
71 | *
72 | * Below are some usage examples.
73 | *
74 | * ```php
75 | * // working with an array
76 | * $username = ArArrayHelper::getValueByPath($array, 'username');
77 | * // working with an {@see ActiveRecordInterface} instance
78 | * $username = ArArrayHelper::getValueByPath($user, 'username');
79 | * // using dot format to retrieve the property of an {@see ActiveRecordInterface} instance
80 | * $street = ArArrayHelper::getValue($users, 'address.street');
81 | * ```
82 | *
83 | * @param ActiveRecordInterface|array $array Array or an {@see ActiveRecordInterface} instance to extract value from.
84 | * @param string $key Key name of the array element or a property or relation name
85 | * of the {@see ActiveRecordInterface} instance.
86 | * @param mixed|null $default The default value to be returned if the specified `$key` doesn't exist.
87 | *
88 | * @return mixed The value of the element if found, default value otherwise
89 | */
90 | public static function getValueByPath(ActiveRecordInterface|array $array, string $key, mixed $default = null): mixed
91 | {
92 | if ($array instanceof ActiveRecordInterface) {
93 | if ($array->hasProperty($key)) {
94 | return $array->get($key);
95 | }
96 |
97 | if (property_exists($array, $key)) {
98 | return array_key_exists($key, get_object_vars($array)) ? $array->$key : $default;
99 | }
100 |
101 | if ($array->isRelationPopulated($key)) {
102 | return $array->relation($key);
103 | }
104 | } elseif (array_key_exists($key, $array)) {
105 | return $array[$key];
106 | }
107 |
108 | if (!empty($key) && ($pos = strrpos($key, '.')) !== false) {
109 | $array = self::getValueByPath($array, substr($key, 0, $pos), $default);
110 | $key = substr($key, $pos + 1);
111 |
112 | return self::getValueByPath($array, $key, $default);
113 | }
114 |
115 | return $default;
116 | }
117 |
118 | /**
119 | * Indexes an array of rows with the specified column value as keys.
120 | *
121 | * The input array should be multidimensional or an array of {@see ActiveRecordInterface} instances.
122 | *
123 | * For example,
124 | *
125 | * ```php
126 | * $rows = [
127 | * ['id' => '123', 'data' => 'abc'],
128 | * ['id' => '345', 'data' => 'def'],
129 | * ];
130 | * $result = ArArrayHelper::populate($rows, 'id');
131 | * // the result is: ['123' => ['id' => '123', 'data' => 'abc'], '345' => ['id' => '345', 'data' => 'def']]
132 | * ```
133 | *
134 | * @param ActiveRecordInterface[]|array[] $rows Array to populate.
135 | * @param Closure|string|null $indexBy The column name or anonymous function that specifies the index by which to
136 | * populate the array of rows.
137 | *
138 | * @return ActiveRecordInterface[]|array[]
139 | *
140 | * @psalm-template TRow of Row
141 | * @psalm-param array $rows
142 | * @psalm-param IndexKey|null $indexBy
143 | * @psalm-return array
144 | */
145 | public static function index(array $rows, Closure|string|null $indexBy = null): array
146 | {
147 | if ($indexBy === null) {
148 | return $rows;
149 | }
150 |
151 | if ($indexBy instanceof Closure) {
152 | return array_combine(array_map($indexBy, $rows), $rows);
153 | }
154 |
155 | $result = [];
156 |
157 | foreach ($rows as $row) {
158 | /** @psalm-suppress MixedArrayOffset */
159 | $result[self::getValueByPath($row, $indexBy)] = $row;
160 | }
161 |
162 | return $result;
163 | }
164 |
165 | /**
166 | * Converts an object into an array.
167 | *
168 | * @param array|object $object The object to be converted into an array.
169 | *
170 | * @return array The array representation of the object.
171 | */
172 | public static function toArray(array|object $object): array
173 | {
174 | if (is_array($object)) {
175 | return $object;
176 | }
177 |
178 | if ($object instanceof ActiveRecordInterface) {
179 | return $object->propertyValues();
180 | }
181 |
182 | if ($object instanceof Traversable) {
183 | return iterator_to_array($object);
184 | }
185 |
186 | return get_object_vars($object);
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/src/ConnectionProvider.php:
--------------------------------------------------------------------------------
1 | db);
22 |
23 | return $handler->handle($request);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/NotFoundException.php:
--------------------------------------------------------------------------------
1 | hasProperty($offset)) {
53 | return $this->get($offset) !== null;
54 | }
55 |
56 | if (property_exists($this, $offset)) {
57 | return isset($this->$offset);
58 | }
59 |
60 | if ($this->isRelationPopulated($offset)) {
61 | return $this->relation($offset) !== null;
62 | }
63 |
64 | return false;
65 | }
66 |
67 | /**
68 | * @param string $offset The offset to retrieve element.
69 | */
70 | public function offsetGet(mixed $offset): mixed
71 | {
72 | if ($this->hasProperty($offset)) {
73 | return $this->get($offset);
74 | }
75 |
76 | if (property_exists($this, $offset)) {
77 | return $this->$offset ?? null;
78 | }
79 |
80 | return $this->relation($offset);
81 | }
82 |
83 | /**
84 | * Sets the element at the specified offset.
85 | *
86 | * This method is required by the SPL interface {@see ArrayAccess}.
87 | *
88 | * It is implicitly called when you use something like `$model[$offset] = $item;`.
89 | *
90 | * @param string $offset The offset to set element.
91 | */
92 | public function offsetSet(mixed $offset, mixed $value): void
93 | {
94 | if ($this->hasProperty($offset)) {
95 | $this->set($offset, $value);
96 | return;
97 | }
98 |
99 | if (property_exists($this, $offset)) {
100 | $this->$offset = $value;
101 | return;
102 | }
103 |
104 | if ($value instanceof ActiveRecordInterface || is_array($value) || $value === null) {
105 | $this->populateRelation($offset, $value);
106 | return;
107 | }
108 |
109 | throw new InvalidArgumentException('Setting unknown property: ' . static::class . '::' . $offset);
110 | }
111 |
112 | /**
113 | * Sets the element value at the specified offset to null.
114 | *
115 | * This method is required by the SPL interface {@see ArrayAccess}.
116 | *
117 | * It is implicitly called when you use something like `unset($model[$offset])`.
118 | *
119 | * @param string $offset The offset to unset element.
120 | */
121 | public function offsetUnset(mixed $offset): void
122 | {
123 | if ($this->hasProperty($offset) || property_exists($this, $offset)) {
124 | $this->set($offset, null);
125 | unset($this->$offset);
126 | return;
127 | }
128 |
129 | $this->resetRelation($offset);
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/Trait/ArrayIteratorTrait.php:
--------------------------------------------------------------------------------
1 | propertyValues();
28 |
29 | return new ArrayIterator($values);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Trait/ArrayableTrait.php:
--------------------------------------------------------------------------------
1 | relatedRecords());
34 |
35 | return array_combine($fields, $fields);
36 | }
37 |
38 | /**
39 | * @psalm-return array
40 | */
41 | public function fields(): array
42 | {
43 | $fields = $this->propertyNames();
44 |
45 | return array_combine($fields, $fields);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Trait/CustomConnectionTrait.php:
--------------------------------------------------------------------------------
1 | connectionName = $connectionName;
27 | return $new;
28 | }
29 |
30 | public function db(): ConnectionInterface
31 | {
32 | if (!empty($this->connectionName)) {
33 | return ConnectionProvider::get($this->connectionName);
34 | }
35 |
36 | return parent::db();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Trait/CustomTableNameTrait.php:
--------------------------------------------------------------------------------
1 | tableName = $tableName;
23 | return $new;
24 | }
25 |
26 | public function tableName(): string
27 | {
28 | return $this->tableName ??= parent::tableName();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Trait/FactoryTrait.php:
--------------------------------------------------------------------------------
1 | factory = $factory;
32 | return $new;
33 | }
34 |
35 | public function query(ActiveRecordInterface|Closure|null|string $arClass = null): ActiveQueryInterface
36 | {
37 | if ($arClass === null) {
38 | return new ActiveQuery($this);
39 | }
40 |
41 | if (!isset($this->factory)) {
42 | return new ActiveQuery($arClass);
43 | }
44 |
45 | if (is_string($arClass)) {
46 | if (method_exists($arClass, 'withFactory')) {
47 | return new ActiveQuery(
48 | fn (): ActiveRecordInterface => $this->factory->create($arClass)->withFactory($this->factory)
49 | );
50 | }
51 |
52 | return new ActiveQuery(fn (): ActiveRecordInterface => $this->factory->create($arClass));
53 | }
54 |
55 | if ($arClass instanceof ActiveRecordInterface && method_exists($arClass, 'withFactory')) {
56 | return new ActiveQuery($arClass->withFactory($this->factory));
57 | }
58 |
59 | return new ActiveQuery($arClass);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Trait/MagicPropertiesTrait.php:
--------------------------------------------------------------------------------
1 | $propertyValues */
46 | private array $propertyValues = [];
47 |
48 | /**
49 | * PHP getter magic method.
50 | * This method is overridden so that values and related objects can be accessed like properties.
51 | *
52 | * @param string $name Property or relation name.
53 | *
54 | * @throws InvalidArgumentException|InvalidCallException|InvalidConfigException|ReflectionException|Throwable
55 | * @throws UnknownPropertyException
56 | *
57 | * @throws Exception
58 | * @return mixed Property or relation value.
59 | *
60 | * @see get()
61 | */
62 | public function __get(string $name)
63 | {
64 | if (method_exists($this, $getter = "get$name")) {
65 | /** Read getter, e.g., getName() */
66 | return $this->$getter();
67 | }
68 |
69 | if ($this->hasProperty($name)) {
70 | return $this->get($name);
71 | }
72 |
73 | if ($this->isRelationPopulated($name)) {
74 | return $this->relatedRecords()[$name];
75 | }
76 |
77 | if (method_exists($this, "get{$name}Query")) {
78 | /** Read relation query getter, e.g., getUserQuery() */
79 | return $this->retrieveRelation($name);
80 | }
81 |
82 | if (method_exists($this, "set$name")) {
83 | throw new InvalidCallException('Getting write-only property: ' . static::class . '::' . $name);
84 | }
85 |
86 | throw new UnknownPropertyException('Getting unknown property or relation: ' . static::class . '::' . $name);
87 | }
88 |
89 | /**
90 | * PHP isset magic method.
91 | * Checks if a property or relation exists and its value is not `null`.
92 | *
93 | * @param string $name The property or relation name.
94 | */
95 | public function __isset(string $name): bool
96 | {
97 | try {
98 | return $this->__get($name) !== null;
99 | } catch (InvalidCallException|UnknownPropertyException) {
100 | return false;
101 | }
102 | }
103 |
104 | /**
105 | * PHP unset magic method.
106 | * Unsets the property or relation.
107 | *
108 | * @param string $name The property or relation name.
109 | */
110 | public function __unset(string $name): void
111 | {
112 | if ($this->hasProperty($name)) {
113 | unset($this->propertyValues[$name]);
114 |
115 | if ($this->hasDependentRelations($name)) {
116 | $this->resetDependentRelations($name);
117 | }
118 | } elseif ($this->isRelationPopulated($name)) {
119 | $this->resetRelation($name);
120 | }
121 | }
122 |
123 | /**
124 | * PHP setter magic method.
125 | * Sets the value of a property.
126 | *
127 | * @param string $name Property name.
128 | *
129 | * @throws InvalidCallException|UnknownPropertyException
130 | */
131 | public function __set(string $name, mixed $value): void
132 | {
133 | if (method_exists($this, $setter = "set$name")) {
134 | $this->$setter($value);
135 | return;
136 | }
137 |
138 | if ($this->hasProperty($name)) {
139 | parent::set($name, $value);
140 | return;
141 | }
142 |
143 | if (
144 | method_exists($this, "get$name")
145 | || method_exists($this, "get{$name}Query")
146 | ) {
147 | throw new InvalidCallException('Setting read-only property: ' . static::class . '::' . $name);
148 | }
149 |
150 | throw new UnknownPropertyException('Setting unknown property: ' . static::class . '::' . $name);
151 | }
152 |
153 | public function hasProperty(string $name): bool
154 | {
155 | return isset($this->propertyValues[$name]) || in_array($name, $this->propertyNames(), true);
156 | }
157 |
158 | public function set(string $propertyName, mixed $value): void
159 | {
160 | if ($this->hasProperty($propertyName)) {
161 | parent::set($propertyName, $value);
162 | } else {
163 | throw new InvalidArgumentException(static::class . ' has no property named "' . $propertyName . '".');
164 | }
165 | }
166 |
167 | /**
168 | * Returns a value indicating whether a property is defined for this component.
169 | *
170 | * A property is defined if:
171 | *
172 | * - the class has a getter or setter method associated with the specified name (in this case, property name is
173 | * case-insensitive).
174 | * - the class has a member variable with the specified name (when `$checkVars` is true).
175 | *
176 | * @param string $name The property name.
177 | * @param bool $checkVars Whether to treat member variables as properties.
178 | *
179 | * @return bool Whether the property is defined.
180 | *
181 | * {@see canGetProperty()}
182 | * {@see canSetProperty()}
183 | */
184 | public function isProperty(string $name, bool $checkVars = true): bool
185 | {
186 | return method_exists($this, "get$name")
187 | || method_exists($this, "set$name")
188 | || method_exists($this, "get{$name}Query")
189 | || ($checkVars && property_exists($this, $name))
190 | || $this->hasProperty($name);
191 | }
192 |
193 | public function canGetProperty(string $name, bool $checkVars = true): bool
194 | {
195 | return method_exists($this, "get$name")
196 | || method_exists($this, "get{$name}Query")
197 | || ($checkVars && property_exists($this, $name))
198 | || $this->hasProperty($name);
199 | }
200 |
201 | public function canSetProperty(string $name, bool $checkVars = true): bool
202 | {
203 | return method_exists($this, "set$name")
204 | || ($checkVars && property_exists($this, $name))
205 | || $this->hasProperty($name);
206 | }
207 |
208 | /** @psalm-return array */
209 | protected function propertyValuesInternal(): array
210 | {
211 | return array_merge($this->propertyValues, parent::propertyValuesInternal());
212 | }
213 |
214 | protected function populateProperty(string $name, mixed $value): void
215 | {
216 | if ($name !== 'propertyValues' && property_exists($this, $name)) {
217 | $this->$name = $value;
218 | } else {
219 | $this->propertyValues[$name] = $value;
220 | }
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/src/Trait/MagicRelationsTrait.php:
--------------------------------------------------------------------------------
1 | hasMany(Order::class, ['customer_id' => 'id']);
41 | * }
42 | * ```
43 | *
44 | * @throws InvalidArgumentException If the named relation doesn't exist.
45 | * @throws ReflectionException
46 | */
47 | public function relationQuery(string $name): ActiveQueryInterface
48 | {
49 | $getter = 'get' . ucfirst($name) . 'Query';
50 |
51 | if (!method_exists($this, $getter)) {
52 | throw new InvalidArgumentException(static::class . ' has no relation named "' . $name . '".');
53 | }
54 |
55 | $method = new ReflectionMethod($this, $getter);
56 | $type = $method->getReturnType();
57 |
58 | if (
59 | $type === null
60 | || !is_a('\\' . $type->getName(), ActiveQueryInterface::class, true)
61 | ) {
62 | $typeName = $type === null ? 'mixed' : $type->getName();
63 |
64 | throw new InvalidArgumentException(
65 | 'Relation query method "' . static::class . '::' . $getter . '()" should return type "'
66 | . ActiveQueryInterface::class . '", but returns "' . $typeName . '" type.'
67 | );
68 | }
69 |
70 | /** Relation name is case-sensitive, trying to validate it when the relation is defined within this class. */
71 | $realName = lcfirst(substr($method->getName(), 3, -5));
72 |
73 | if ($realName !== $name) {
74 | throw new InvalidArgumentException(
75 | 'Relation names are case sensitive. ' . static::class
76 | . " has a relation named \"$realName\" instead of \"$name\"."
77 | );
78 | }
79 |
80 | return $this->$getter();
81 | }
82 |
83 | /**
84 | * Returns names of all relations defined in the ActiveRecord class using getter methods with `get` prefix and
85 | * `Query` suffix.
86 | *
87 | * @throws ReflectionException
88 | * @return string[]
89 | */
90 | public function relationNames(): array
91 | {
92 | $methods = get_class_methods($this);
93 |
94 | $relations = [];
95 |
96 | foreach ($methods as $method) {
97 | if (str_starts_with($method, 'get') && str_ends_with($method, 'Query')) {
98 | $reflection = new ReflectionMethod($this, $method);
99 | $type = $reflection->getReturnType();
100 |
101 | if (
102 | $type === null
103 | || !is_a('\\' . $type->getName(), ActiveQueryInterface::class, true)
104 | ) {
105 | continue;
106 | }
107 |
108 | $relations[] = lcfirst(substr($method, 3, -5));
109 | }
110 | }
111 |
112 | return $relations;
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/tests/ArrayableTraitTest.php:
--------------------------------------------------------------------------------
1 | findByPk(1)->fields();
19 |
20 | $this->assertEquals(
21 | [
22 | 'id' => 'id',
23 | 'email' => 'email',
24 | 'name' => 'name',
25 | 'address' => 'address',
26 | 'status' => 'status',
27 | 'bool_status' => 'bool_status',
28 | 'profile_id' => 'profile_id',
29 | 'item' => 'item',
30 | 'items' => 'items',
31 | ],
32 | $fields,
33 | );
34 | }
35 |
36 | public function testToArray(): void
37 | {
38 | $customerQuery = new ActiveQuery(Customer::class);
39 | $customer = $customerQuery->findByPk(1);
40 |
41 | $this->assertSame(
42 | [
43 | 'id' => 1,
44 | 'email' => 'user1@example.com',
45 | 'name' => 'user1',
46 | 'address' => 'address1',
47 | 'status' => 1,
48 | 'bool_status' => true,
49 | 'profile_id' => 1,
50 | ],
51 | $customer->toArray(),
52 | );
53 | }
54 |
55 | public function testToArrayWithClosure(): void
56 | {
57 | $customerQuery = new ActiveQuery(CustomerClosureField::class);
58 | $customer = $customerQuery->findByPk(1);
59 |
60 | $this->assertSame(
61 | [
62 | 'id' => 1,
63 | 'email' => 'user1@example.com',
64 | 'name' => 'user1',
65 | 'address' => 'address1',
66 | 'status' => 'active',
67 | 'bool_status' => true,
68 | 'profile_id' => 1,
69 | ],
70 | $customer->toArray(),
71 | );
72 | }
73 |
74 | public function testToArrayForArrayable(): void
75 | {
76 | $customerQuery = new ActiveQuery(CustomerForArrayable::class);
77 |
78 | /** @var CustomerForArrayable $customer */
79 | $customer = $customerQuery->findByPk(1);
80 | /** @var CustomerForArrayable $customer2 */
81 | $customer2 = $customerQuery->findByPk(2);
82 | /** @var CustomerForArrayable $customer3 */
83 | $customer3 = $customerQuery->findByPk(3);
84 |
85 | $customer->setItem($customer2);
86 | $customer->setItems($customer3);
87 |
88 | $this->assertSame(
89 | [
90 | 'id' => 1,
91 | 'email' => 'user1@example.com',
92 | 'name' => 'user1',
93 | 'address' => 'address1',
94 | 'status' => 'active',
95 | 'item' => [
96 | 'id' => 2,
97 | 'email' => 'user2@example.com',
98 | 'name' => 'user2',
99 | 'status' => 'active',
100 | ],
101 | 'items' => [
102 | [
103 | 'id' => 3,
104 | 'email' => 'user3@example.com',
105 | 'name' => 'user3',
106 | 'status' => 'inactive',
107 | ],
108 | ],
109 | ],
110 | $customer->toArray([
111 | 'id',
112 | 'name',
113 | 'email',
114 | 'address',
115 | 'status',
116 | 'item.id',
117 | 'item.name',
118 | 'item.email',
119 | 'items.0.id',
120 | 'items.0.name',
121 | 'items.0.email',
122 | ]),
123 | );
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/tests/BatchQueryResultTest.php:
--------------------------------------------------------------------------------
1 | orderBy('id');
18 |
19 | $result = $query->batch(2);
20 |
21 | $this->assertEquals(2, $result->getBatchSize());
22 | $this->assertSame($result->getQuery(), $query);
23 |
24 | /** normal query */
25 | $customerQuery = new ActiveQuery(Customer::class);
26 |
27 | $query = $customerQuery->orderBy('id');
28 |
29 | $allRows = [];
30 |
31 | $batch = $query->batch(2);
32 |
33 | foreach ($batch as $rows) {
34 | $allRows = array_merge($allRows, $rows);
35 | }
36 |
37 | $this->assertCount(3, $allRows);
38 | $this->assertEquals('user1', $allRows[0]->getName());
39 | $this->assertEquals('user2', $allRows[1]->getName());
40 | $this->assertEquals('user3', $allRows[2]->getName());
41 |
42 | /** rewind */
43 | $allRows = [];
44 |
45 | foreach ($batch as $rows) {
46 | $allRows = array_merge($allRows, $rows);
47 | }
48 |
49 | $this->assertCount(3, $allRows);
50 |
51 | /** rewind */
52 | $batch->rewind();
53 |
54 | /** empty query */
55 | $query = $customerQuery->where(['id' => 100]);
56 |
57 | $allRows = [];
58 |
59 | $batch = $query->batch(2);
60 |
61 | foreach ($batch as $rows) {
62 | $allRows = array_merge($allRows, $rows);
63 | }
64 |
65 | $this->assertCount(0, $allRows);
66 |
67 | /** query with index */
68 | $customerQuery = new ActiveQuery(Customer::class);
69 |
70 | $query = $customerQuery->indexBy('name');
71 |
72 | $allRows = [];
73 |
74 | foreach ($query->batch(2) as $rows) {
75 | $allRows = array_merge($allRows, $rows);
76 | }
77 |
78 | $this->assertCount(3, $allRows);
79 | $this->assertEquals('address1', $allRows['user1']->getAddress());
80 | $this->assertEquals('address2', $allRows['user2']->getAddress());
81 | $this->assertEquals('address3', $allRows['user3']->getAddress());
82 |
83 | /** each */
84 | $customerQuery = new ActiveQuery(Customer::class);
85 |
86 | $query = $customerQuery->orderBy('id');
87 |
88 | $allRows = [];
89 |
90 | foreach ($query->each() as $index => $row) {
91 | $allRows[$index] = $row;
92 | }
93 | $this->assertCount(3, $allRows);
94 | $this->assertEquals('user1', $allRows[0]->getName());
95 | $this->assertEquals('user2', $allRows[1]->getName());
96 | $this->assertEquals('user3', $allRows[2]->getName());
97 |
98 | /** each with key */
99 | $customerQuery = new ActiveQuery(Customer::class);
100 |
101 | $query = $customerQuery->orderBy('id')->indexBy('name');
102 |
103 | $allRows = [];
104 |
105 | foreach ($query->each() as $key => $row) {
106 | $allRows[$key] = $row;
107 | }
108 |
109 | $this->assertCount(3, $allRows);
110 | $this->assertEquals('address1', $allRows['user1']->getAddress());
111 | $this->assertEquals('address2', $allRows['user2']->getAddress());
112 | $this->assertEquals('address3', $allRows['user3']->getAddress());
113 | }
114 |
115 | public function testActiveQuery(): void
116 | {
117 | /** batch with eager loading */
118 | $customerQuery = new ActiveQuery(Customer::class);
119 |
120 | $query = $customerQuery->with('orders')->orderBy('id');
121 |
122 | $customers = $this->getAllRowsFromBatch($query->batch(2));
123 |
124 | foreach ($customers as $customer) {
125 | $this->assertTrue($customer->isRelationPopulated('orders'));
126 | }
127 |
128 | $this->assertCount(3, $customers);
129 | $this->assertCount(1, $customers[0]->getOrders());
130 | $this->assertCount(2, $customers[1]->getOrders());
131 | $this->assertCount(0, $customers[2]->getOrders());
132 | }
133 |
134 | public function testBatchWithIndexBy(): void
135 | {
136 | $customerQuery = new ActiveQuery(Customer::class);
137 |
138 | $query = $customerQuery->orderBy('id')->limit(3)->indexBy('id');
139 |
140 | $customers = $this->getAllRowsFromBatch($query->batch(2));
141 |
142 | $this->assertCount(3, $customers);
143 | $this->assertEquals('user1', $customers[0]->getName());
144 | $this->assertEquals('user2', $customers[1]->getName());
145 | $this->assertEquals('user3', $customers[2]->getName());
146 | }
147 |
148 | protected function getAllRowsFromBatch(BatchQueryResultInterface $batch): array
149 | {
150 | $allRows = [];
151 |
152 | foreach ($batch as $rows) {
153 | $allRows = array_merge($allRows, $rows);
154 | }
155 |
156 | return $allRows;
157 | }
158 |
159 | protected function getAllRowsFromEach(BatchQueryResultInterface $each): array
160 | {
161 | $allRows = [];
162 |
163 | foreach ($each as $index => $row) {
164 | $allRows[$index] = $row;
165 | }
166 |
167 | return $allRows;
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/tests/ConnectionProviderTest.php:
--------------------------------------------------------------------------------
1 | assertTrue(ConnectionProvider::has('default'));
22 | $this->assertFalse(ConnectionProvider::has('db2'));
23 |
24 | $db = ConnectionProvider::get();
25 |
26 | $this->assertTrue(ConnectionProvider::has('default'));
27 | $this->assertSame($db, ConnectionProvider::get('default'));
28 |
29 | $list = ConnectionProvider::all();
30 |
31 | $this->assertSame($list, ['default' => $db]);
32 |
33 | $db2 = $this->createConnection();
34 | ConnectionProvider::set($db2, 'db2');
35 |
36 | $this->assertTrue(ConnectionProvider::has('db2'));
37 | $this->assertSame($db2, ConnectionProvider::get('db2'));
38 |
39 | $list = ConnectionProvider::all();
40 |
41 | $this->assertSame($list, ['default' => $db, 'db2' => $db2]);
42 |
43 | ConnectionProvider::remove('db2');
44 |
45 | $this->assertFalse(ConnectionProvider::has('db2'));
46 |
47 | $list = ConnectionProvider::all();
48 |
49 | $this->assertSame($list, ['default' => $db]);
50 | }
51 |
52 | public function testConnectionProviderMiddleware(): void
53 | {
54 | $this->reloadFixtureAfterTest();
55 |
56 | ConnectionProvider::remove();
57 |
58 | $this->assertEmpty(ConnectionProvider::all());
59 | $this->assertFalse(ConnectionProvider::has('default'));
60 |
61 | $db = $this->createConnection();
62 | $container = new Container(ContainerConfig::create()->withDefinitions([ConnectionInterface::class => $db]));
63 | $request = $this->createMock(ServerRequestInterface::class);
64 | $requestHandler = $this->createMock(RequestHandlerInterface::class);
65 |
66 | $dispatcher = (new MiddlewareDispatcher(new MiddlewareFactory($container)))
67 | ->withMiddlewares([ConnectionProviderMiddleware::class]);
68 |
69 | $dispatcher->dispatch($request, $requestHandler);
70 |
71 | $this->assertTrue(ConnectionProvider::has('default'));
72 | $this->assertSame($db, ConnectionProvider::get());
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/tests/Driver/Mssql/ActiveQueryFindTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Driver/Mssql/ActiveQueryTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Driver/Mssql/ActiveRecordTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
19 | }
20 |
21 | protected function createFactory(): Factory
22 | {
23 | return (new MssqlHelper())->createFactory($this->db());
24 | }
25 |
26 | public function testSaveWithTrigger(): void
27 | {
28 | $this->reloadFixtureAfterTest();
29 |
30 | // drop trigger if exist
31 | $sql = <<db()->createCommand($sql)->execute();
38 |
39 | // create trigger
40 | $sql = <<db()->createCommand($sql)->execute();
51 |
52 | $record = new TestTrigger();
53 |
54 | $record->stringcol = 'test';
55 |
56 | $this->assertTrue($record->save());
57 | $this->assertEquals(1, $record->id);
58 |
59 | $testRecordQuery = new ActiveQuery(TestTriggerAlert::class);
60 |
61 | $this->assertEquals('test', $testRecordQuery->findByPk(1)->stringcol);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/tests/Driver/Mssql/ArrayableTraitTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Driver/Mssql/BatchQueryResultTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Driver/Mssql/ConnectionProviderTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Driver/Mssql/MagicActiveRecordTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
18 | }
19 |
20 | public function testSaveWithTrigger(): void
21 | {
22 | $this->reloadFixtureAfterTest();
23 |
24 | // drop trigger if exist
25 | $sql = <<db()->createCommand($sql)->execute();
32 |
33 | // create trigger
34 | $sql = <<db()->createCommand($sql)->execute();
45 |
46 | $record = new TestTrigger();
47 |
48 | $record->stringcol = 'test';
49 |
50 | $this->assertTrue($record->save());
51 | $this->assertEquals(1, $record->id);
52 |
53 | $testRecordQuery = new ActiveQuery(TestTriggerAlert::class);
54 |
55 | $this->assertEquals('test', $testRecordQuery->findByPk(1)->stringcol);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/tests/Driver/Mssql/RepositoryTraitTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Driver/Mysql/ActiveQueryFindTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Driver/Mysql/ActiveQueryTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Driver/Mysql/ActiveRecordTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
20 | }
21 |
22 | protected function createFactory(): Factory
23 | {
24 | return (new MysqlHelper())->createFactory($this->db());
25 | }
26 |
27 | public function testCastValues(): void
28 | {
29 | $this->reloadFixtureAfterTest();
30 |
31 | $arClass = new Type();
32 |
33 | $arClass->int_col = 123;
34 | $arClass->int_col2 = 456;
35 | $arClass->smallint_col = 42;
36 | $arClass->char_col = '1337';
37 | $arClass->char_col2 = 'test';
38 | $arClass->char_col3 = 'test123';
39 | $arClass->enum_col = 'B';
40 | $arClass->float_col = 3.742;
41 | $arClass->float_col2 = 42.1337;
42 | $arClass->bool_col = true;
43 | $arClass->bool_col2 = false;
44 | $arClass->json_col = ['a' => 'b', 'c' => null, 'd' => [1, 2, 3]];
45 |
46 | $arClass->save();
47 |
48 | /** @var $model Type */
49 | $aqClass = new ActiveQuery(Type::class);
50 | $query = $aqClass->one();
51 |
52 | $this->assertSame(123, $query->int_col);
53 | $this->assertSame(456, $query->int_col2);
54 | $this->assertSame(42, $query->smallint_col);
55 | $this->assertSame('1337', trim($query->char_col));
56 | $this->assertSame('test', $query->char_col2);
57 | $this->assertSame('test123', $query->char_col3);
58 | $this->assertSame(3.742, $query->float_col);
59 | $this->assertSame(42.1337, $query->float_col2);
60 | $this->assertEquals(true, $query->bool_col);
61 | $this->assertEquals(false, $query->bool_col2);
62 | $this->assertSame('B', $query->enum_col);
63 | $this->assertSame(['a' => 'b', 'c' => null, 'd' => [1, 2, 3]], $query->json_col);
64 | }
65 |
66 | public function testExplicitPkOnAutoIncrement(): void
67 | {
68 | $this->reloadFixtureAfterTest();
69 |
70 | $customer = new Customer();
71 |
72 | $customer->setId(1337);
73 | $customer->setEmail('user1337@example.com');
74 | $customer->setName('user1337');
75 | $customer->setAddress('address1337');
76 |
77 | $this->assertTrue($customer->isNewRecord());
78 |
79 | $customer->save();
80 |
81 | $this->assertEquals(1337, $customer->getId());
82 | $this->assertFalse($customer->isNewRecord());
83 | }
84 |
85 | /**
86 | * {@see https://github.com/yiisoft/yii2/issues/15482}
87 | */
88 | public function testEagerLoadingUsingStringIdentifiers(): void
89 | {
90 | $betaQuery = new ActiveQuery(Beta::class);
91 |
92 | $betas = $betaQuery->with('alpha')->all();
93 |
94 | $this->assertNotEmpty($betas);
95 |
96 | $alphaIdentifiers = [];
97 |
98 | /** @var Beta[] $betas */
99 | foreach ($betas as $beta) {
100 | $this->assertNotNull($beta->getAlpha());
101 | $this->assertEquals($beta->getAlphaStringIdentifier(), $beta->getAlpha()->getStringIdentifier());
102 | $alphaIdentifiers[] = $beta->getAlpha()->getStringIdentifier();
103 | }
104 |
105 | $this->assertEquals(['1', '01', '001', '001', '2', '2b', '2b', '02'], $alphaIdentifiers);
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/tests/Driver/Mysql/ArrayableTraitTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Driver/Mysql/BatchQueryResultTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Driver/Mysql/ConnectionProviderTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Driver/Mysql/MagicActiveRecordTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
18 | }
19 |
20 | public function testExplicitPkOnAutoIncrement(): void
21 | {
22 | $this->reloadFixtureAfterTest();
23 |
24 | $customer = new Customer();
25 |
26 | $customer->id = 1337;
27 | $customer->email = 'user1337@example.com';
28 | $customer->name = 'user1337';
29 | $customer->address = 'address1337';
30 |
31 | $this->assertTrue($customer->isNewRecord());
32 |
33 | $customer->save();
34 |
35 | $this->assertEquals(1337, $customer->id);
36 | $this->assertFalse($customer->isNewRecord());
37 | }
38 |
39 | /**
40 | * {@see https://github.com/yiisoft/yii2/issues/15482}
41 | */
42 | public function testEagerLoadingUsingStringIdentifiers(): void
43 | {
44 | $betaQuery = new ActiveQuery(Beta::class);
45 |
46 | $betas = $betaQuery->with('alpha')->all();
47 |
48 | $this->assertNotEmpty($betas);
49 |
50 | $alphaIdentifiers = [];
51 |
52 | /** @var Beta[] $betas */
53 | foreach ($betas as $beta) {
54 | $this->assertNotNull($beta->alpha);
55 | $this->assertEquals($beta->alpha_string_identifier, $beta->alpha->string_identifier);
56 | $alphaIdentifiers[] = $beta->alpha->string_identifier;
57 | }
58 |
59 | $this->assertEquals(['1', '01', '001', '001', '2', '2b', '2b', '02'], $alphaIdentifiers);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/tests/Driver/Mysql/RepositoryTraitTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Driver/Mysql/Stubs/Type.php:
--------------------------------------------------------------------------------
1 | createConnection();
17 | }
18 |
19 | public function testFindLimit(): void
20 | {
21 | /** one */
22 | $customerQuery = new ActiveQuery(CustomerWithRownumid::class);
23 | $customer = $customerQuery->orderBy('id')->one();
24 | $this->assertEquals('user1', $customer->getName());
25 |
26 | /** all */
27 | $customerQuery = new ActiveQuery(CustomerWithRownumid::class);
28 | $customers = $customerQuery->all();
29 | $this->assertCount(3, $customers);
30 |
31 | /** limit */
32 | $customerQuery = new ActiveQuery(CustomerWithRownumid::class);
33 | $customers = $customerQuery->orderBy('id')->limit(1)->all();
34 | $this->assertCount(1, $customers);
35 | $this->assertEquals('user1', $customers[0]->getName());
36 |
37 | $customers = $customerQuery->orderBy('id')->limit(1)->offset(1)->all();
38 | $this->assertCount(1, $customers);
39 | $this->assertEquals('user2', $customers[0]->getName());
40 |
41 | $customers = $customerQuery->orderBy('id')->limit(1)->offset(2)->all();
42 | $this->assertCount(1, $customers);
43 | $this->assertEquals('user3', $customers[0]->getName());
44 |
45 | $customers = $customerQuery->orderBy('id')->limit(2)->offset(1)->all();
46 | $this->assertCount(2, $customers);
47 | $this->assertEquals('user2', $customers[0]->getName());
48 | $this->assertEquals('user3', $customers[1]->getName());
49 |
50 | $customers = $customerQuery->limit(2)->offset(3)->all();
51 | $this->assertCount(0, $customers);
52 |
53 | /** offset */
54 | $customerQuery = new ActiveQuery(CustomerWithRownumid::class);
55 | $customer = $customerQuery->orderBy('id')->offset(0)->one();
56 | $this->assertEquals('user1', $customer->getName());
57 |
58 | $customer = $customerQuery->orderBy('id')->offset(1)->one();
59 | $this->assertEquals('user2', $customer->getName());
60 |
61 | $customer = $customerQuery->orderBy('id')->offset(2)->one();
62 | $this->assertEquals('user3', $customer->getName());
63 |
64 | $customer = $customerQuery->offset(3)->one();
65 | $this->assertNull($customer);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/tests/Driver/Oracle/ActiveRecordTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
19 | }
20 |
21 | protected function createFactory(): Factory
22 | {
23 | return (new OracleHelper())->createFactory($this->db());
24 | }
25 |
26 | public function testDefaultValues(): void
27 | {
28 | $arClass = new Type();
29 | $arClass->loadDefaultValues();
30 | $this->assertSame(1, $arClass->int_col2);
31 | $this->assertSame('something', $arClass->char_col2);
32 | $this->assertSame(1.23, $arClass->float_col2);
33 | $this->assertSame(33.22, $arClass->numeric_col);
34 | $this->assertTrue($arClass->bool_col2);
35 |
36 | // not testing $arClass->time, because oci\Schema can't read default value
37 |
38 | $arClass = new Type();
39 | $arClass->char_col2 = 'not something';
40 |
41 | $arClass->loadDefaultValues();
42 | $this->assertSame('not something', $arClass->char_col2);
43 |
44 | $arClass = new Type();
45 | $arClass->char_col2 = 'not something';
46 |
47 | $arClass->loadDefaultValues(false);
48 | $this->assertSame('something', $arClass->char_col2);
49 | }
50 |
51 | /**
52 | * Some PDO implementations (e.g. cubrid) do not support boolean values.
53 | *
54 | * Make sure this does not affect AR layer.
55 | */
56 | public function testBooleanProperty(): void
57 | {
58 | $this->reloadFixtureAfterTest();
59 |
60 | $customer = new Customer();
61 |
62 | $customer->setName('boolean customer');
63 | $customer->setEmail('mail@example.com');
64 | $customer->setBoolStatus(true);
65 |
66 | $customer->save();
67 | $customer->refresh();
68 | $this->assertTrue($customer->getBoolStatus());
69 |
70 | $customer->setBoolStatus(false);
71 | $customer->save();
72 |
73 | $customer->refresh();
74 | $this->assertFalse($customer->getBoolStatus());
75 |
76 | $customerQuery = new ActiveQuery(Customer::class);
77 | $customers = $customerQuery->where(['bool_status' => '1'])->all();
78 | $this->assertCount(2, $customers);
79 |
80 | $customerQuery = new ActiveQuery(Customer::class);
81 | $customers = $customerQuery->where(['bool_status' => '0'])->all();
82 | $this->assertCount(2, $customers);
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/tests/Driver/Oracle/ArrayableTraitTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Driver/Oracle/BatchQueryResultTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
17 | }
18 |
19 | public function testBatchWithIndexBy(): void
20 | {
21 | $customerQuery = new ActiveQuery(Customer::class);
22 |
23 | $query = $customerQuery->orderBy('id')->limit(3)->indexBy('id');
24 |
25 | $customers = $this->getAllRowsFromBatch($query->batch(2));
26 |
27 | $this->assertCount(3, $customers);
28 | $this->assertEquals('user1', $customers[0]->getName());
29 | $this->assertEquals('user2', $customers[1]->getName());
30 | $this->assertEquals('user3', $customers[2]->getName());
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/Driver/Oracle/ConnectionProviderTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Driver/Oracle/MagicActiveRecordTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
18 | }
19 |
20 | public function testDefaultValues(): void
21 | {
22 | $arClass = new Type();
23 | $arClass->loadDefaultValues();
24 | $this->assertSame(1, $arClass->int_col2);
25 | $this->assertSame('something', $arClass->char_col2);
26 | $this->assertSame(1.23, $arClass->float_col2);
27 | $this->assertSame(33.22, $arClass->numeric_col);
28 | $this->assertSame(true, $arClass->bool_col2);
29 |
30 | // not testing $arClass->time, because oci\Schema can't read default value
31 |
32 | $arClass = new Type();
33 | $arClass->char_col2 = 'not something';
34 |
35 | $arClass->loadDefaultValues();
36 | $this->assertSame('not something', $arClass->char_col2);
37 |
38 | $arClass = new Type();
39 | $arClass->char_col2 = 'not something';
40 |
41 | $arClass->loadDefaultValues(false);
42 | $this->assertSame('something', $arClass->char_col2);
43 | }
44 |
45 | /**
46 | * Some PDO implementations (e.g. cubrid) do not support boolean values.
47 | *
48 | * Make sure this does not affect AR layer.
49 | */
50 | public function testBooleanProperty(): void
51 | {
52 | $this->reloadFixtureAfterTest();
53 |
54 | $customer = new Customer();
55 |
56 | $customer->name = 'boolean customer';
57 | $customer->email = 'mail@example.com';
58 | $customer->bool_status = true;
59 |
60 | $customer->save();
61 | $customer->refresh();
62 | $this->assertTrue($customer->bool_status);
63 |
64 | $customer->bool_status = false;
65 | $customer->save();
66 |
67 | $customer->refresh();
68 | $this->assertFalse($customer->bool_status);
69 |
70 | $customerQuery = new ActiveQuery(Customer::class);
71 | $customers = $customerQuery->where(['bool_status' => '1'])->all();
72 | $this->assertCount(2, $customers);
73 |
74 | $customerQuery = new ActiveQuery(Customer::class);
75 | $customers = $customerQuery->where(['bool_status' => '0'])->all();
76 | $this->assertCount(2, $customers);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/tests/Driver/Oracle/RepositoryTraitTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Driver/Oracle/Stubs/Customer.php:
--------------------------------------------------------------------------------
1 | hasMany(Order::class, ['customer_id' => 'id'])->orderBy('{{customer}}.[[id]]');
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/Driver/Oracle/Stubs/MagicCustomer.php:
--------------------------------------------------------------------------------
1 | hasMany(Order::class, ['customer_id' => 'id'])->orderBy('{{customer}}.[[id]]');
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tests/Driver/Oracle/Stubs/MagicOrder.php:
--------------------------------------------------------------------------------
1 | hasOne(MagicCustomer::class, ['id' => 'customer_id']);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/tests/Driver/Oracle/Stubs/Order.php:
--------------------------------------------------------------------------------
1 | hasOne(Customer::class, ['id' => 'customer_id']);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/tests/Driver/Pgsql/ActiveQueryFindTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Driver/Pgsql/ActiveQueryTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
17 | }
18 |
19 | public function testBit(): void
20 | {
21 | $bitValueQuery = new ActiveQuery(BitValues::class);
22 | $falseBit = $bitValueQuery->findByPk(1);
23 | $this->assertSame(0, $falseBit->val);
24 |
25 | $bitValueQuery = new ActiveQuery(BitValues::class);
26 | $trueBit = $bitValueQuery->findByPk(2);
27 | $this->assertSame(1, $trueBit->val);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tests/Driver/Pgsql/ArrayableTraitTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Driver/Pgsql/BatchQueryResultTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Driver/Pgsql/ConnectionProviderTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Driver/Pgsql/RepositoryTraitTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Driver/Pgsql/Stubs/Item.php:
--------------------------------------------------------------------------------
1 | $this->hasMany(Promotion::class, ['array_item_ids' => 'id']),
18 | default => parent::relationQuery($name),
19 | };
20 | }
21 |
22 | /** @return Promotion[] */
23 | public function getPromotionsViaArray(): array
24 | {
25 | return $this->relation('promotionsViaArray');
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/Driver/Pgsql/Stubs/Promotion.php:
--------------------------------------------------------------------------------
1 | $this->hasMany(Item::class, ['id' => 'array_item_ids'])
20 | ->inverseOf('promotionsViaArray'),
21 | default => parent::relationQuery($name),
22 | };
23 | }
24 |
25 | /** @return Item[] */
26 | public function getItemsViaArray(): array
27 | {
28 | return $this->relation('itemsViaArray');
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/Driver/Pgsql/Stubs/Type.php:
--------------------------------------------------------------------------------
1 | createConnection();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Driver/Sqlite/ActiveQueryTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Driver/Sqlite/ActiveRecordTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
19 | }
20 |
21 | protected function createFactory(): Factory
22 | {
23 | return (new SqliteHelper())->createFactory($this->db());
24 | }
25 |
26 | public function testExplicitPkOnAutoIncrement(): void
27 | {
28 | $this->reloadFixtureAfterTest();
29 |
30 | $customer = new Customer();
31 |
32 | $customer->setId(1337);
33 | $customer->setEmail('user1337@example.com');
34 | $customer->setName('user1337');
35 | $customer->setAddress('address1337');
36 |
37 | $this->assertTrue($customer->isNewRecord());
38 | $customer->save();
39 |
40 | $this->assertEquals(1337, $customer->getId());
41 | $this->assertFalse($customer->isNewRecord());
42 | }
43 |
44 | /**
45 | * {@see https://github.com/yiisoft/yii2/issues/15482}
46 | */
47 | public function testEagerLoadingUsingStringIdentifiers(): void
48 | {
49 | $betaQuery = new ActiveQuery(Beta::class);
50 |
51 | $betas = $betaQuery->with('alpha')->all();
52 |
53 | $this->assertNotEmpty($betas);
54 |
55 | $alphaIdentifiers = [];
56 |
57 | /** @var Beta[] $betas */
58 | foreach ($betas as $beta) {
59 | $this->assertNotNull($beta->getAlpha());
60 | $this->assertEquals($beta->getAlphaStringIdentifier(), $beta->getAlpha()->getStringIdentifier());
61 | $alphaIdentifiers[] = $beta->getAlpha()->getStringIdentifier();
62 | }
63 |
64 | $this->assertEquals(['1', '01', '001', '001', '2', '2b', '2b', '02'], $alphaIdentifiers);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/tests/Driver/Sqlite/ArrayableTraitTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Driver/Sqlite/BatchQueryResultTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Driver/Sqlite/ConnectionProviderTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Driver/Sqlite/MagicActiveRecordTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
18 | }
19 |
20 | public function testExplicitPkOnAutoIncrement(): void
21 | {
22 | $this->reloadFixtureAfterTest();
23 |
24 | $customer = new Customer();
25 |
26 | $customer->id = 1337;
27 | $customer->email = 'user1337@example.com';
28 | $customer->name = 'user1337';
29 | $customer->address = 'address1337';
30 |
31 | $this->assertTrue($customer->isNewRecord());
32 | $customer->save();
33 |
34 | $this->assertEquals(1337, $customer->id);
35 | $this->assertFalse($customer->isNewRecord());
36 | }
37 |
38 | /**
39 | * {@see https://github.com/yiisoft/yii2/issues/15482}
40 | */
41 | public function testEagerLoadingUsingStringIdentifiers(): void
42 | {
43 | $betaQuery = new ActiveQuery(Beta::class);
44 |
45 | $betas = $betaQuery->with('alpha')->all();
46 |
47 | $this->assertNotEmpty($betas);
48 |
49 | $alphaIdentifiers = [];
50 |
51 | /** @var Beta[] $betas */
52 | foreach ($betas as $beta) {
53 | $this->assertNotNull($beta->alpha);
54 | $this->assertEquals($beta->alpha_string_identifier, $beta->alpha->string_identifier);
55 | $alphaIdentifiers[] = $beta->alpha->string_identifier;
56 | }
57 |
58 | $this->assertEquals(['1', '01', '001', '001', '2', '2b', '2b', '02'], $alphaIdentifiers);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/tests/Driver/Sqlite/RepositoryTraitTest.php:
--------------------------------------------------------------------------------
1 | createConnection();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/RepositoryTraitTest.php:
--------------------------------------------------------------------------------
1 | assertEquals(
18 | $customerQuery->setWhere(['id' => 1]),
19 | Customer::find(['id' => 1]),
20 | );
21 | }
22 |
23 | public function testFindOne(): void
24 | {
25 | $customerQuery = new ActiveQuery(new Customer());
26 |
27 | $this->assertEquals(
28 | $customerQuery->where(['id' => 1])->one(),
29 | Customer::findOne(['id' => 1]),
30 | );
31 |
32 | $customer = Customer::findOne(['customer.id' => 1]);
33 | $this->assertEquals(1, $customer->getId());
34 |
35 | $customer = Customer::findOne(['id' => [5, 6, 1]]);
36 | $this->assertInstanceOf(Customer::class, $customer);
37 |
38 | $customer = Customer::findOne(['id' => 2, 'name' => 'user2']);
39 | $this->assertInstanceOf(Customer::class, $customer);
40 | $this->assertEquals('user2', $customer->getName());
41 |
42 | $customer = Customer::findOne(['id' => 2, 'name' => 'user1']);
43 | $this->assertNull($customer);
44 |
45 | $customer = Customer::findOne(['name' => 'user5']);
46 | $this->assertNull($customer);
47 | }
48 |
49 | public function testFindOneOrFail(): void
50 | {
51 | $customerQuery = new ActiveQuery(new Customer());
52 |
53 | $this->assertEquals(
54 | $customerQuery->where(['id' => 1])->one(),
55 | Customer::findOneOrFail(['id' => 1]),
56 | );
57 |
58 | $this->expectException(NotFoundException::class);
59 | $this->expectExceptionMessage('No records found.');
60 |
61 | Customer::findOneOrFail(['name' => 'user5']);
62 | }
63 |
64 | public function testFindAll(): void
65 | {
66 | $customerQuery = new ActiveQuery(new Customer());
67 |
68 | $this->assertEquals(
69 | $customerQuery->all(),
70 | Customer::findAll(),
71 | );
72 |
73 | $this->assertEquals(
74 | $customerQuery->where(['id' => 1])->all(),
75 | Customer::findAll(['id' => 1]),
76 | );
77 |
78 | $this->assertCount(1, Customer::findAll(['id' => 1]));
79 | $this->assertCount(3, Customer::findAll(['id' => [1, 2, 3]]));
80 | }
81 |
82 | public function testFindAllOrFail(): void
83 | {
84 | $customerQuery = new ActiveQuery(new Customer());
85 |
86 | $this->assertEquals(
87 | $customerQuery->where(['id' => [1, 2, 3]])->all(),
88 | Customer::findAllOrFail(['id' => [1, 2, 3]]),
89 | );
90 |
91 | $this->expectException(NotFoundException::class);
92 | $this->expectExceptionMessage('No records found.');
93 |
94 | Customer::findAllOrFail(['id' => 5]);
95 | }
96 |
97 | public function testFindByPk(): void
98 | {
99 | $customerQuery = new ActiveQuery(new Customer());
100 |
101 | $this->assertEquals(
102 | $customerQuery->where(['id' => 1])->one(),
103 | Customer::findByPk(1),
104 | );
105 |
106 | $customer = Customer::findByPk(5);
107 | $this->assertNull($customer);
108 | }
109 |
110 | public function testFindByPkOrFail(): void
111 | {
112 | $customerQuery = new ActiveQuery(new Customer());
113 |
114 | $this->assertEquals(
115 | $customerQuery->where(['id' => 1])->one(),
116 | Customer::findByPkOrFail(1),
117 | );
118 |
119 | $this->expectException(NotFoundException::class);
120 | $this->expectExceptionMessage('No records found.');
121 |
122 | Customer::findByPkOrFail(5);
123 | }
124 |
125 | public function testFindBySql(): void
126 | {
127 | $customerQuery = new ActiveQuery(new Customer());
128 |
129 | $this->assertEquals(
130 | $customerQuery->sql('SELECT * FROM {{customer}}'),
131 | Customer::findBySql('SELECT * FROM {{customer}}'),
132 | );
133 |
134 | $customer = Customer::findBySql('SELECT * FROM {{customer}} ORDER BY [[id]] DESC')->one();
135 | $this->assertInstanceOf(Customer::class, $customer);
136 | $this->assertSame('user3', $customer->get('name'));
137 |
138 | $customers = Customer::findBySql('SELECT * FROM {{customer}}')->all();
139 | $this->assertCount(3, $customers);
140 |
141 | /** find with parameter binding */
142 | $customer = Customer::findBySql('SELECT * FROM {{customer}} WHERE [[id]]=:id', [':id' => 2])->one();
143 | $this->assertInstanceOf(Customer::class, $customer);
144 | $this->assertSame('user2', $customer->get('name'));
145 |
146 | /** @link https://github.com/yiisoft/yii2/issues/8593 */
147 | $query = Customer::findBySql('SELECT * FROM {{customer}}');
148 | $this->assertEquals(3, $query->count());
149 |
150 | $query = Customer::findBySql('SELECT * FROM {{customer}} WHERE [[id]]=:id', [':id' => 2]);
151 | $this->assertEquals(1, $query->count());
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/tests/Stubs/ActiveRecord/Alpha.php:
--------------------------------------------------------------------------------
1 | id;
27 | }
28 |
29 | public function getStringIdentifier(): string
30 | {
31 | return $this->string_identifier;
32 | }
33 |
34 | public function relationQuery(string $name): ActiveQueryInterface
35 | {
36 | return match ($name) {
37 | 'betas' => $this->getBetasQuery(),
38 | default => parent::relationQuery($name),
39 | };
40 | }
41 |
42 | public function getBetas(): array|null
43 | {
44 | return $this->relation('betas');
45 | }
46 |
47 | public function getBetasQuery(): ActiveQuery
48 | {
49 | return $this->hasMany(Beta::class, ['alpha_string_identifier' => 'string_identifier']);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/tests/Stubs/ActiveRecord/Animal.php:
--------------------------------------------------------------------------------
1 | type = static::class;
27 | }
28 |
29 | public function getDoes()
30 | {
31 | return $this->does;
32 | }
33 |
34 | public function setDoes(string $value): void
35 | {
36 | $this->does = $value;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tests/Stubs/ActiveRecord/ArrayAndJsonTypes.php:
--------------------------------------------------------------------------------
1 | id;
25 | }
26 |
27 | public function getAlphaStringIdentifier(): string
28 | {
29 | return $this->alpha_string_identifier;
30 | }
31 |
32 | public function relationQuery(string $name): ActiveQueryInterface
33 | {
34 | return match ($name) {
35 | 'alpha' => $this->getAlphaQuery(),
36 | default => parent::relationQuery($name),
37 | };
38 | }
39 |
40 | public function getAlpha(): Alpha|null
41 | {
42 | return $this->relation('alpha');
43 | }
44 |
45 | public function getAlphaQuery(): ActiveQuery
46 | {
47 | return $this->hasOne(Alpha::class, ['string_identifier' => 'alpha_string_identifier']);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tests/Stubs/ActiveRecord/BitValues.php:
--------------------------------------------------------------------------------
1 | setDoes('meow');
16 | }
17 |
18 | public function getException(): void
19 | {
20 | throw new Exception('no');
21 | }
22 |
23 | /**
24 | * This is to test if __isset catches the error.
25 | *
26 | * @throw DivisionByZeroError
27 | */
28 | public function getThrowable(): float|int
29 | {
30 | return 5 / 0;
31 | }
32 |
33 | public function setNonExistingProperty(string $value): void
34 | {
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tests/Stubs/ActiveRecord/Category.php:
--------------------------------------------------------------------------------
1 | $this->getItemsQuery(),
28 | 'limitedItems' => $this->getLimitedItemsQuery(),
29 | 'orderItems' => $this->getOrderItemsQuery(),
30 | 'orders' => $this->getOrdersQuery(),
31 | default => parent::relationQuery($name),
32 | };
33 | }
34 |
35 | public function getId(): int|null
36 | {
37 | return $this->id;
38 | }
39 |
40 | public function getName(): string
41 | {
42 | return $this->name;
43 | }
44 |
45 | public function setId(int|null $id): void
46 | {
47 | $this->set('id', $id);
48 | }
49 |
50 | public function getLimitedItems(): array
51 | {
52 | return $this->relation('limitedItems');
53 | }
54 |
55 | public function getLimitedItemsQuery(): ActiveQuery
56 | {
57 | return $this->hasMany(Item::class, ['category_id' => 'id'])->onCondition(['item.id' => [1, 2, 3]]);
58 | }
59 |
60 | public function getItems(): array
61 | {
62 | return $this->relation('items');
63 | }
64 |
65 | public function getItemsQuery(): ActiveQuery
66 | {
67 | return $this->hasMany(Item::class, ['category_id' => 'id']);
68 | }
69 |
70 | public function getOrderItems(): array
71 | {
72 | return $this->relation('orderItems');
73 | }
74 |
75 | public function getOrderItemsQuery(): ActiveQuery
76 | {
77 | return $this->hasMany(OrderItem::class, ['item_id' => 'id'])->via('items');
78 | }
79 |
80 | public function getOrders(): array
81 | {
82 | return $this->relation('orders');
83 | }
84 |
85 | public function getOrdersQuery(): ActiveQuery
86 | {
87 | return $this->hasMany(Order::class, ['id' => 'order_id'])->via('orderItems');
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/tests/Stubs/ActiveRecord/CustomerClosureField.php:
--------------------------------------------------------------------------------
1 | $customer->status === 1 ? 'active' : 'inactive';
32 |
33 | return $fields;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/tests/Stubs/ActiveRecord/CustomerForArrayable.php:
--------------------------------------------------------------------------------
1 | item = $item;
44 | }
45 |
46 | public function setItems(self ...$items)
47 | {
48 | $this->items = $items;
49 | }
50 |
51 | public function toArray(array $fields = [], array $expand = [], bool $recursive = true): array
52 | {
53 | $data = parent::toArray($fields, $expand, $recursive);
54 |
55 | $data['status'] = $this->status == 1 ? 'active' : 'inactive';
56 |
57 | return $data;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/tests/Stubs/ActiveRecord/CustomerQuery.php:
--------------------------------------------------------------------------------
1 | andWhere('[[status]]=1');
16 |
17 | return $this;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tests/Stubs/ActiveRecord/CustomerWithAlias.php:
--------------------------------------------------------------------------------
1 | factory = $factory;
19 | }
20 |
21 | public function relationQuery(string $name): ActiveQueryInterface
22 | {
23 | return match ($name) {
24 | 'ordersWithFactory' => $this->hasMany(OrderWithFactory::class, ['customer_id' => 'id']),
25 | default => parent::relationQuery($name),
26 | };
27 | }
28 |
29 | /** @return OrderWithFactory[] */
30 | public function getOrdersWithFactory(): array
31 | {
32 | return $this->relation('ordersWithFactory');
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tests/Stubs/ActiveRecord/DefaultPk.php:
--------------------------------------------------------------------------------
1 | $this->getEmployeesQuery(),
29 | default => parent::relationQuery($name),
30 | };
31 | }
32 |
33 | public function getEmployees(): ActiveRecordInterface
34 | {
35 | return $this->relation('employees');
36 | }
37 |
38 | public function getEmployeesQuery(): ActiveQuery
39 | {
40 | return $this->hasMany(
41 | Employee::class,
42 | [
43 | 'department_id' => 'id',
44 | ]
45 | )->inverseOf('department');
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/tests/Stubs/ActiveRecord/Document.php:
--------------------------------------------------------------------------------
1 | setDoes('bark');
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/tests/Stubs/ActiveRecord/Dossier.php:
--------------------------------------------------------------------------------
1 | id;
29 | }
30 |
31 | public function getDepartmentId(): int
32 | {
33 | return $this->department_id;
34 | }
35 |
36 | public function getEmployeeId(): int
37 | {
38 | return $this->employee_id;
39 | }
40 |
41 | public function getSummary(): string
42 | {
43 | return $this->summary;
44 | }
45 |
46 | public function setId(int $id): void
47 | {
48 | $this->id = $id;
49 | }
50 |
51 | public function setDepartmentId(int $departmentId): void
52 | {
53 | $this->set('department_id', $departmentId);
54 | }
55 |
56 | public function setEmployeeId(int $employeeId): void
57 | {
58 | $this->set('employee_id', $employeeId);
59 | }
60 |
61 | public function setSummary(string $summary): void
62 | {
63 | $this->summary = $summary;
64 | }
65 |
66 | public function relationQuery(string $name): ActiveQueryInterface
67 | {
68 | return match ($name) {
69 | 'employee' => $this->getEmployeeQuery(),
70 | default => parent::relationQuery($name),
71 | };
72 | }
73 |
74 | public function getEmployee(): Employee|null
75 | {
76 | return $this->relation('employee');
77 | }
78 |
79 | public function getEmployeeQuery(): ActiveQuery
80 | {
81 | return $this->hasOne(
82 | Employee::class,
83 | [
84 | 'department_id' => 'department_id',
85 | 'id' => 'employee_id',
86 | ]
87 | )->inverseOf('dossier');
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/tests/Stubs/ActiveRecord/Employee.php:
--------------------------------------------------------------------------------
1 | $this->getDepartmentQuery(),
30 | 'dossier' => $this->getDossierQuery(),
31 | default => parent::relationQuery($name),
32 | };
33 | }
34 |
35 | public function getFullName(): string
36 | {
37 | return $this->first_name . ' ' . $this->last_name;
38 | }
39 |
40 | public function getDepartment(): Department
41 | {
42 | return $this->relation('department');
43 | }
44 |
45 | public function getDepartmentQuery(): ActiveQuery
46 | {
47 | return $this
48 | ->hasOne(Department::class, [
49 | 'id' => 'department_id',
50 | ])
51 | ->inverseOf('employees')
52 | ;
53 | }
54 |
55 | public function getDossier(): Dossier
56 | {
57 | return $this->relation('dossier');
58 | }
59 |
60 | public function getDossierQuery(): ActiveQuery
61 | {
62 | return $this->hasOne(
63 | Dossier::class,
64 | [
65 | 'department_id' => 'department_id',
66 | 'employee_id' => 'id',
67 | ]
68 | )->inverseOf('employee');
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/tests/Stubs/ActiveRecord/Item.php:
--------------------------------------------------------------------------------
1 | $this->getCategoryQuery(),
29 | 'promotionsViaJson' => $this->hasMany(Promotion::class, ['json_item_ids' => 'id']),
30 | default => parent::relationQuery($name),
31 | };
32 | }
33 |
34 | public function getId(): int
35 | {
36 | return $this->id;
37 | }
38 |
39 | public function getName(): string
40 | {
41 | return $this->name;
42 | }
43 |
44 | public function getCategoryId(): int
45 | {
46 | return $this->category_id;
47 | }
48 |
49 | public function getCategory(): Category
50 | {
51 | return $this->relation('category');
52 | }
53 |
54 | public function getCategoryQuery(): ActiveQuery
55 | {
56 | return $this->hasOne(Category::class, ['id' => 'category_id']);
57 | }
58 |
59 | /** @return Promotion[] */
60 | public function getPromotionsViaJson(): array
61 | {
62 | return $this->relation('promotionsViaJson');
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/tests/Stubs/ActiveRecord/NoExist.php:
--------------------------------------------------------------------------------
1 | tableName ??= 'order_item';
27 | }
28 |
29 | public function getOrderId(): int
30 | {
31 | return $this->order_id;
32 | }
33 |
34 | public function getItemId(): int
35 | {
36 | return $this->item_id;
37 | }
38 |
39 | public function getQuantity(): int
40 | {
41 | return $this->quantity;
42 | }
43 |
44 | public function getSubtotal(): float
45 | {
46 | return $this->subtotal;
47 | }
48 |
49 | public function setOrderId(int $orderId): void
50 | {
51 | $this->set('order_id', $orderId);
52 | }
53 |
54 | public function setItemId(int $itemId): void
55 | {
56 | $this->set('item_id', $itemId);
57 | }
58 |
59 | public function setQuantity(int $quantity): void
60 | {
61 | $this->quantity = $quantity;
62 | }
63 |
64 | public function setSubtotal(float $subtotal): void
65 | {
66 | $this->subtotal = $subtotal;
67 | }
68 |
69 | public function relationQuery(string $name): ActiveQueryInterface
70 | {
71 | return match ($name) {
72 | 'order' => $this->getOrderQuery(),
73 | 'item' => $this->getItemQuery(),
74 | 'orderItemCompositeWithJoin' => $this->getOrderItemCompositeWithJoinQuery(),
75 | 'orderItemCompositeNoJoin' => $this->getOrderItemCompositeNoJoinQuery(),
76 | 'custom' => $this->getCustomQuery(),
77 | default => parent::relationQuery($name),
78 | };
79 | }
80 |
81 | public function getOrder(): Order|null
82 | {
83 | return $this->relation('order');
84 | }
85 |
86 | public function getOrderQuery(): ActiveQuery
87 | {
88 | return $this->hasOne(Order::class, ['id' => 'order_id']);
89 | }
90 |
91 | public function getItem(): Item|null
92 | {
93 | return $this->relation('item');
94 | }
95 |
96 | public function getItemQuery(): ActiveQuery
97 | {
98 | return $this->hasOne(Item::class, ['id' => 'item_id']);
99 | }
100 |
101 | public function getOrderItemCompositeWithJoin(): self|null
102 | {
103 | return $this->relation('orderItemCompositeWithJoin');
104 | }
105 |
106 | public function getOrderItemCompositeWithJoinQuery(): ActiveQuery
107 | {
108 | /** relations used by testFindCompositeWithJoin() */
109 | return $this->hasOne(self::class, ['item_id' => 'item_id', 'order_id' => 'order_id' ])->joinWith('item');
110 | }
111 |
112 | public function getOrderItemCompositeNoJoin(): self|null
113 | {
114 | return $this->relation('orderItemCompositeNoJoin');
115 | }
116 |
117 | public function getOrderItemCompositeNoJoinQuery(): ActiveQuery
118 | {
119 | return $this->hasOne(self::class, ['item_id' => 'item_id', 'order_id' => 'order_id' ]);
120 | }
121 |
122 | public function getCustom(): Order|null
123 | {
124 | return $this->relation('custom');
125 | }
126 |
127 | public function getCustomQuery(): ActiveQuery
128 | {
129 | return new ActiveQuery(Order::class);
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/tests/Stubs/ActiveRecord/OrderItemWithNullFK.php:
--------------------------------------------------------------------------------
1 | $this->hasOne(CustomerWithFactory::class, ['id' => 'customer_id']),
18 | 'customerWithFactoryClosure' => $this->hasOne(
19 | fn () => $this->factory->create(CustomerWithFactory::class),
20 | ['id' => 'customer_id']
21 | ),
22 | 'customerWithFactoryInstance' => $this->hasOne(
23 | $this->factory->create(CustomerWithFactory::class),
24 | ['id' => 'customer_id']
25 | ),
26 | default => parent::relationQuery($name),
27 | };
28 | }
29 |
30 | public function getCustomerWithFactory(): CustomerWithFactory|null
31 | {
32 | return $this->relation('customerWithFactory');
33 | }
34 |
35 | public function getCustomerWithFactoryClosure(): CustomerWithFactory|null
36 | {
37 | return $this->relation('customerWithFactoryClosure');
38 | }
39 |
40 | public function getCustomerWithFactoryInstance(): CustomerWithFactory|null
41 | {
42 | return $this->relation('customerWithFactoryInstance');
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/tests/Stubs/ActiveRecord/OrderWithNullFK.php:
--------------------------------------------------------------------------------
1 | id;
27 | }
28 |
29 | public function getCustomerId(): int|null
30 | {
31 | return $this->customer_id;
32 | }
33 |
34 | public function getCreatedAt(): int
35 | {
36 | return $this->created_at;
37 | }
38 |
39 | public function getTotal(): float
40 | {
41 | return $this->total;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/tests/Stubs/ActiveRecord/Profile.php:
--------------------------------------------------------------------------------
1 | $this->hasMany(Item::class, ['id' => 'json_item_ids'])
28 | ->inverseOf('promotionsViaJson'),
29 | default => parent::relationQuery($name),
30 | };
31 | }
32 |
33 | /** @return Item[] */
34 | public function getItemsViaJson(): array
35 | {
36 | return $this->relation('itemsViaJson');
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tests/Stubs/ActiveRecord/TestTrigger.php:
--------------------------------------------------------------------------------
1 | hasMany(Beta::class, ['alpha_string_identifier' => 'string_identifier']);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/Stubs/MagicActiveRecord/Animal.php:
--------------------------------------------------------------------------------
1 | type = static::class;
27 | }
28 |
29 | public function getDoes()
30 | {
31 | return $this->does;
32 | }
33 |
34 | public function setDoes(string $value): void
35 | {
36 | $this->does = $value;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tests/Stubs/MagicActiveRecord/ArrayAndJsonTypes.php:
--------------------------------------------------------------------------------
1 | hasOne(Alpha::class, ['string_identifier' => 'alpha_string_identifier']);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/Stubs/MagicActiveRecord/BitValues.php:
--------------------------------------------------------------------------------
1 | setDoes('meow');
16 | }
17 |
18 | public function getException(): void
19 | {
20 | throw new Exception('no');
21 | }
22 |
23 | /**
24 | * This is to test if __isset catches the error.
25 | *
26 | * @throw DivisionByZeroError
27 | */
28 | public function getThrowable(): float|int
29 | {
30 | return 5 / 0;
31 | }
32 |
33 | public function setNonExistingProperty(string $value): void
34 | {
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tests/Stubs/MagicActiveRecord/Category.php:
--------------------------------------------------------------------------------
1 | hasMany(Item::class, ['category_id' => 'id'])->onCondition(['item.id' => [1, 2, 3]]);
26 | }
27 |
28 | public function getItemsQuery(): ActiveQuery
29 | {
30 | return $this->hasMany(Item::class, ['category_id' => 'id']);
31 | }
32 |
33 | public function getOrderItemsQuery(): ActiveQuery
34 | {
35 | return $this->hasMany(OrderItem::class, ['item_id' => 'id'])->via('items');
36 | }
37 |
38 | public function getOrdersQuery(): ActiveQuery
39 | {
40 | return $this->hasMany(Order::class, ['id' => 'order_id'])->via('orderItems');
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tests/Stubs/MagicActiveRecord/Customer.php:
--------------------------------------------------------------------------------
1 | hasOne(Profile::class, ['id' => 'profile_id']);
45 | }
46 |
47 | public function getOrdersPlainQuery(): ActiveQuery
48 | {
49 | return $this->hasMany(Order::class, ['customer_id' => 'id']);
50 | }
51 |
52 | public function getOrdersQuery(): ActiveQuery
53 | {
54 | return $this->hasMany(Order::class, ['customer_id' => 'id'])->orderBy('[[id]]');
55 | }
56 |
57 | public function getOrdersNoOrderQuery(): ActiveQuery
58 | {
59 | return $this->hasMany(Order::class, ['customer_id' => 'id']);
60 | }
61 |
62 | public function getExpensiveOrdersQuery(): ActiveQuery
63 | {
64 | return $this->hasMany(Order::class, ['customer_id' => 'id'])->andWhere('[[total]] > 50')->orderBy('id');
65 | }
66 |
67 | public function getItemQuery(): void
68 | {
69 | }
70 |
71 | public function getOrdersWithItemsQuery(): ActiveQuery
72 | {
73 | return $this->hasMany(Order::class, ['customer_id' => 'id'])->with('orderItems');
74 | }
75 |
76 | public function getExpensiveOrdersWithNullFKQuery(): ActiveQuery
77 | {
78 | return $this->hasMany(
79 | OrderWithNullFK::class,
80 | ['customer_id' => 'id']
81 | )->andWhere('[[total]] > 50')->orderBy('id');
82 | }
83 |
84 | public function getOrdersWithNullFKQuery(): ActiveQuery
85 | {
86 | return $this->hasMany(OrderWithNullFK::class, ['customer_id' => 'id'])->orderBy('id');
87 | }
88 |
89 | public function getOrders2Query(): ActiveQuery
90 | {
91 | return $this->hasMany(Order::class, ['customer_id' => 'id'])->inverseOf('customer2')->orderBy('id');
92 | }
93 |
94 | /** deeply nested table relation */
95 | public function getOrderItemsQuery(): ActiveQuery
96 | {
97 | $rel = $this->hasMany(Item::class, ['id' => 'item_id']);
98 |
99 | return $rel->viaTable('order_item', ['order_id' => 'id'], function ($q) {
100 | /* @var $q ActiveQuery */
101 | $q->viaTable('order', ['customer_id' => 'id']);
102 | })->orderBy('id');
103 | }
104 |
105 | public function setOrdersReadOnly(): void
106 | {
107 | }
108 |
109 | public function getOrderItems2Query(): ActiveQuery
110 | {
111 | return $this->hasMany(OrderItem::class, ['order_id' => 'id'])
112 | ->via('ordersNoOrder');
113 | }
114 |
115 | public function getItems2Query(): ActiveQuery
116 | {
117 | return $this->hasMany(Item::class, ['id' => 'item_id'])
118 | ->via('orderItems2');
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/tests/Stubs/MagicActiveRecord/CustomerQuery.php:
--------------------------------------------------------------------------------
1 | andWhere('[[status]]=1');
16 |
17 | return $this;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tests/Stubs/MagicActiveRecord/CustomerWithAlias.php:
--------------------------------------------------------------------------------
1 | id;
28 | }
29 |
30 | public function getEmail(): string
31 | {
32 | return $this->email;
33 | }
34 |
35 | public function getName(): string|null
36 | {
37 | return $this->name;
38 | }
39 |
40 | public function getAddress(): string|null
41 | {
42 | return $this->address;
43 | }
44 |
45 | public function getStatus(): int|null
46 | {
47 | return $this->get('status');
48 | }
49 |
50 | public function getProfileQuery(): ActiveQuery
51 | {
52 | return $this->hasOne(Profile::class, ['id' => 'profile_id']);
53 | }
54 |
55 | public function getOrdersQuery(): ActiveQuery
56 | {
57 | return $this->hasMany(Order::class, ['customer_id' => 'id'])->orderBy('[[id]]');
58 | }
59 |
60 | public function setEmail(string $email): void
61 | {
62 | $this->email = $email;
63 | }
64 |
65 | public function setName(string|null $name): void
66 | {
67 | $this->name = $name;
68 | }
69 |
70 | public function setAddress(string|null $address): void
71 | {
72 | $this->address = $address;
73 | }
74 |
75 | public function setStatus(int|null $status): void
76 | {
77 | $this->set('status', $status);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/tests/Stubs/MagicActiveRecord/DefaultPk.php:
--------------------------------------------------------------------------------
1 | hasMany(
27 | Employee::class,
28 | [
29 | 'department_id' => 'id',
30 | ]
31 | )->inverseOf('department');
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tests/Stubs/MagicActiveRecord/Document.php:
--------------------------------------------------------------------------------
1 | setDoes('bark');
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/tests/Stubs/MagicActiveRecord/Dossier.php:
--------------------------------------------------------------------------------
1 | hasOne(
29 | Employee::class,
30 | [
31 | 'department_id' => 'department_id',
32 | 'id' => 'employee_id',
33 | ]
34 | )->inverseOf('dossier');
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tests/Stubs/MagicActiveRecord/Employee.php:
--------------------------------------------------------------------------------
1 | first_name . ' ' . $this->last_name;
31 | }
32 |
33 | public function getDepartmentQuery(): ActiveQuery
34 | {
35 | return $this
36 | ->hasOne(Department::class, [
37 | 'id' => 'department_id',
38 | ])
39 | ->inverseOf('employees')
40 | ;
41 | }
42 |
43 | public function getDossierQuery(): ActiveQuery
44 | {
45 | return $this->hasOne(
46 | Dossier::class,
47 | [
48 | 'department_id' => 'department_id',
49 | 'employee_id' => 'id',
50 | ]
51 | )->inverseOf('employee');
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/tests/Stubs/MagicActiveRecord/Item.php:
--------------------------------------------------------------------------------
1 | hasOne(Category::class, ['id' => 'category_id']);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/Stubs/MagicActiveRecord/NoExist.php:
--------------------------------------------------------------------------------
1 | get('created_at'));
34 | }
35 |
36 | public function setCreated_at(DateTimeInterface|int $createdAt): void
37 | {
38 | $this->set('created_at', $createdAt instanceof DateTimeInterface
39 | ? $createdAt->getTimestamp()
40 | : $createdAt);
41 | }
42 |
43 | public function setVirtualCustomerId(string|int|null $virtualCustomerId = null): void
44 | {
45 | $this->virtualCustomerId = $virtualCustomerId;
46 | }
47 |
48 | public function getVirtualCustomerQuery()
49 | {
50 | return $this->hasOne(Customer::class, ['id' => 'virtualCustomerId']);
51 | }
52 |
53 | public function getCustomerQuery(): ActiveQuery
54 | {
55 | return $this->hasOne(Customer::class, ['id' => 'customer_id']);
56 | }
57 |
58 | public function getCustomerJoinedWithProfileQuery(): ActiveQuery
59 | {
60 | return $this->hasOne(Customer::class, ['id' => 'customer_id'])->joinWith('profile');
61 | }
62 |
63 | public function getCustomerJoinedWithProfileIndexOrderedQuery(): ActiveQuery
64 | {
65 | return $this->hasMany(
66 | Customer::class,
67 | ['id' => 'customer_id']
68 | )->joinWith('profile')->orderBy(['profile.description' => SORT_ASC])->indexBy('name');
69 | }
70 |
71 | public function getCustomer2Query(): ActiveQuery
72 | {
73 | return $this->hasOne(Customer::class, ['id' => 'customer_id'])->inverseOf('orders2');
74 | }
75 |
76 | public function getOrderItemsQuery(): ActiveQuery
77 | {
78 | return $this->hasMany(OrderItem::class, ['order_id' => 'id']);
79 | }
80 |
81 | public function getOrderItems2Query(): ActiveQuery
82 | {
83 | return $this->hasMany(OrderItem::class, ['order_id' => 'id'])->indexBy('item_id');
84 | }
85 |
86 | public function getOrderItems3Query(): ActiveQuery
87 | {
88 | return $this->hasMany(
89 | OrderItem::class,
90 | ['order_id' => 'id']
91 | )->indexBy(fn ($row) => $row['order_id'] . '_' . $row['item_id']);
92 | }
93 |
94 | public function getOrderItemsWithNullFKQuery(): ActiveQuery
95 | {
96 | return $this->hasMany(OrderItemWithNullFK::class, ['order_id' => 'id']);
97 | }
98 |
99 | public function getItemsQuery(): ActiveQuery
100 | {
101 | return $this->hasMany(Item::class, ['id' => 'item_id'])->via('orderItems', static function ($q) {
102 | // additional query configuration
103 | })->orderBy('item.id');
104 | }
105 |
106 | public function getItemsIndexedQuery(): ActiveQuery
107 | {
108 | return $this->hasMany(Item::class, ['id' => 'item_id'])->via('orderItems')->indexBy('id');
109 | }
110 |
111 | public function getItemsWithNullFKQuery(): ActiveQuery
112 | {
113 | return $this->hasMany(
114 | Item::class,
115 | ['id' => 'item_id']
116 | )->viaTable('order_item_with_null_fk', ['order_id' => 'id']);
117 | }
118 |
119 | public function getItemsInOrder1Query(): ActiveQuery
120 | {
121 | return $this->hasMany(Item::class, ['id' => 'item_id'])->via('orderItems', static function ($q) {
122 | $q->orderBy(['subtotal' => SORT_ASC]);
123 | })->orderBy('name');
124 | }
125 |
126 | public function getItemsInOrder2Query(): ActiveQuery
127 | {
128 | return $this->hasMany(Item::class, ['id' => 'item_id'])->via('orderItems', static function ($q) {
129 | $q->orderBy(['subtotal' => SORT_DESC]);
130 | })->orderBy('name');
131 | }
132 |
133 | public function getBooksQuery(): ActiveQuery
134 | {
135 | return $this->hasMany(Item::class, ['id' => 'item_id'])->via('orderItems')->where(['category_id' => 1]);
136 | }
137 |
138 | public function getBooksWithNullFKQuery(): ActiveQuery
139 | {
140 | return $this->hasMany(
141 | Item::class,
142 | ['id' => 'item_id']
143 | )->via('orderItemsWithNullFK')->where(['category_id' => 1]);
144 | }
145 |
146 | public function getBooksViaTableQuery(): ActiveQuery
147 | {
148 | return $this->hasMany(
149 | Item::class,
150 | ['id' => 'item_id']
151 | )->viaTable('order_item', ['order_id' => 'id'])->where(['category_id' => 1]);
152 | }
153 |
154 | public function getBooksWithNullFKViaTableQuery(): ActiveQuery
155 | {
156 | return $this->hasMany(
157 | Item::class,
158 | ['id' => 'item_id']
159 | )->viaTable('order_item_with_null_fk', ['order_id' => 'id'])->where(['category_id' => 1]);
160 | }
161 |
162 | public function getBooks2Query(): ActiveQuery
163 | {
164 | return $this->hasMany(
165 | Item::class,
166 | ['id' => 'item_id']
167 | )->onCondition(['category_id' => 1])->viaTable('order_item', ['order_id' => 'id']);
168 | }
169 |
170 | public function getBooksExplicitQuery(): ActiveQuery
171 | {
172 | return $this->hasMany(
173 | Item::class,
174 | ['id' => 'item_id']
175 | )->onCondition(['category_id' => 1])->viaTable('order_item', ['order_id' => 'id']);
176 | }
177 |
178 | public function getBooksExplicitAQuery(): ActiveQuery
179 | {
180 | return $this->hasMany(
181 | Item::class,
182 | ['id' => 'item_id']
183 | )->alias('bo')->onCondition(['bo.category_id' => 1])->viaTable('order_item', ['order_id' => 'id']);
184 | }
185 |
186 | public function getBookItemsQuery(): ActiveQuery
187 | {
188 | return $this->hasMany(
189 | Item::class,
190 | ['id' => 'item_id']
191 | )->alias('books')->onCondition(['books.category_id' => 1])->viaTable('order_item', ['order_id' => 'id']);
192 | }
193 |
194 | public function getMovieItemsQuery(): ActiveQuery
195 | {
196 | return $this->hasMany(
197 | Item::class,
198 | ['id' => 'item_id']
199 | )->alias('movies')->onCondition(['movies.category_id' => 2])->viaTable('order_item', ['order_id' => 'id']);
200 | }
201 |
202 | public function getLimitedItemsQuery(): ActiveQuery
203 | {
204 | return $this->hasMany(Item::class, ['id' => 'item_id'])->onCondition(['item.id' => [3, 5]])->via('orderItems');
205 | }
206 |
207 | public function getExpensiveItemsUsingViaWithCallableQuery(): ActiveQuery
208 | {
209 | return $this->hasMany(Item::class, ['id' => 'item_id'])->via('orderItems', function (ActiveQuery $q) {
210 | $q->where(['>=', 'subtotal', 10]);
211 | });
212 | }
213 |
214 | public function getCheapItemsUsingViaWithCallableQuery(): ActiveQuery
215 | {
216 | return $this->hasMany(Item::class, ['id' => 'item_id'])->via('orderItems', function (ActiveQuery $q) {
217 | $q->where(['<', 'subtotal', 10]);
218 | });
219 | }
220 |
221 | public function getOrderItemsFor8Query(): ActiveQuery
222 | {
223 | return $this->hasMany(OrderItemWithNullFK::class, ['order_id' => 'id'])->andOnCondition(['subtotal' => 8.0]);
224 | }
225 |
226 | public function getItemsFor8Query(): ActiveQuery
227 | {
228 | return $this->hasMany(Item::class, ['id' => 'item_id'])->via('orderItemsFor8');
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/tests/Stubs/MagicActiveRecord/OrderItem.php:
--------------------------------------------------------------------------------
1 | get('order_id');
30 | $fields['item_id'] = $this->get('item_id');
31 | $fields['price'] = $this->get('subtotal') / $this->get('quantity');
32 | $fields['quantity'] = $this->get('quantity');
33 | $fields['subtotal'] = $this->get('subtotal');
34 |
35 | return $fields;
36 | }
37 |
38 | public function getOrderQuery(): ActiveQuery
39 | {
40 | return $this->hasOne(Order::class, ['id' => 'order_id']);
41 | }
42 |
43 | public function getItemQuery(): ActiveQuery
44 | {
45 | return $this->hasOne(Item::class, ['id' => 'item_id']);
46 | }
47 |
48 | public function getOrderItemCompositeWithJoinQuery(): ActiveQuery
49 | {
50 | /** relations used by testFindCompositeWithJoin() */
51 | return $this->hasOne(self::class, ['item_id' => 'item_id', 'order_id' => 'order_id' ])->joinWith('item');
52 | }
53 |
54 | public function getOrderItemCompositeNoJoinQuery(): ActiveQuery
55 | {
56 | return $this->hasOne(self::class, ['item_id' => 'item_id', 'order_id' => 'order_id' ]);
57 | }
58 |
59 | public function getCustomQuery(): ActiveQuery
60 | {
61 | return new ActiveQuery(Order::class);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/tests/Stubs/MagicActiveRecord/OrderItemWithNullFK.php:
--------------------------------------------------------------------------------
1 | getProperty($propertyName);
46 |
47 | $property->setAccessible(true);
48 |
49 | /** @psalm-var mixed $result */
50 | $result = $property->getValue($object);
51 | }
52 |
53 | return $result;
54 | }
55 |
56 | /**
57 | * Invokes an inaccessible method.
58 | *
59 | * @param object $object The object to invoke the method on.
60 | * @param string $method The name of the method to invoke.
61 | * @param array $args The arguments to pass to the method.
62 | */
63 | public static function invokeMethod(object $object, string $method, array $args = []): mixed
64 | {
65 | $reflection = new ReflectionObject($object);
66 |
67 | $result = null;
68 |
69 | if ($method !== '') {
70 | $method = $reflection->getMethod($method);
71 |
72 | $method->setAccessible(true);
73 |
74 | /** @psalm-var mixed $result */
75 | $result = $method->invokeArgs($object, $args);
76 | }
77 |
78 | return $result;
79 | }
80 |
81 | /**
82 | * Sets an inaccessible object property to a designated value.
83 | */
84 | public static function setInaccessibleProperty(
85 | object $object,
86 | string $propertyName,
87 | mixed $value
88 | ): void {
89 | $class = new ReflectionClass($object);
90 |
91 | if ($propertyName !== '') {
92 | $property = $class->getProperty($propertyName);
93 | $property->setValue($object, $value);
94 | }
95 |
96 | unset($class, $property);
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/tests/Support/ConnectionHelper.php:
--------------------------------------------------------------------------------
1 | withDefinitions([ConnectionInterface::class => $db]));
24 | return new Factory($container, [ConnectionInterface::class => $db]);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/Support/DbHelper.php:
--------------------------------------------------------------------------------
1 | getDriverName();
28 |
29 | $fixture = match ($driverName) {
30 | 'mysql' => dirname(__DIR__) . '/data/mysql.sql',
31 | 'oci' => dirname(__DIR__) . '/data/oci.sql',
32 | 'pgsql' => dirname(__DIR__) . '/data/pgsql.sql',
33 | 'sqlite' => dirname(__DIR__) . '/data/sqlite.sql',
34 | 'sqlsrv' => dirname(__DIR__) . '/data/mssql.sql',
35 | };
36 |
37 | if ($db->isActive()) {
38 | $db->close();
39 | }
40 |
41 | $db->open();
42 |
43 | if ($driverName === 'oci') {
44 | [$drops, $creates] = explode('/* STATEMENTS */', file_get_contents($fixture), 2);
45 | [$statements, $triggers, $data] = explode('/* TRIGGERS */', $creates, 3);
46 | $lines = array_merge(
47 | explode('--', $drops),
48 | explode(';', $statements),
49 | explode('/', $triggers),
50 | explode(';', $data)
51 | );
52 | } else {
53 | $lines = explode(';', file_get_contents($fixture));
54 | }
55 |
56 | foreach ($lines as $line) {
57 | if (trim($line) !== '') {
58 | $db->getPDO()->exec($line);
59 | }
60 | }
61 | }
62 |
63 | /**
64 | * Adjust dbms specific escaping.
65 | *
66 | * @param string $sql string SQL statement to adjust.
67 | * @param string $driverName string DBMS name.
68 | *
69 | * @return mixed
70 | */
71 | public static function replaceQuotes(string $sql, string $driverName): string
72 | {
73 | return match ($driverName) {
74 | 'mysql', 'sqlite' => str_replace(['[[', ']]'], '`', $sql),
75 | 'oci' => str_replace(['[[', ']]'], '"', $sql),
76 | 'pgsql' => str_replace(['\\[', '\\]'], ['[', ']'], preg_replace('/(\[\[)|((? str_replace(['[[', ']]'], ['[', ']'], $sql),
78 | default => $sql,
79 | };
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/tests/Support/ModelFactory.php:
--------------------------------------------------------------------------------
1 | populateRecord($row);
18 |
19 | $models[] = $model;
20 | }
21 |
22 | return $models;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tests/Support/MssqlHelper.php:
--------------------------------------------------------------------------------
1 | dsn, $this->username, $this->password);
21 | $pdoDriver->charset($this->charset);
22 |
23 | return new Connection($pdoDriver, $this->createSchemaCache());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tests/Support/MysqlHelper.php:
--------------------------------------------------------------------------------
1 | dsn, $this->username, $this->password);
21 | $pdoDriver->charset($this->charset);
22 |
23 | return new Connection($pdoDriver, $this->createSchemaCache());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tests/Support/OracleHelper.php:
--------------------------------------------------------------------------------
1 | dsn, $this->username, $this->password);
22 | $pdoDriver->charset($this->charset);
23 | $pdoDriver->attributes([PDO::ATTR_STRINGIFY_FETCHES => true]);
24 |
25 | return new Connection($pdoDriver, $this->createSchemaCache());
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/Support/PgsqlHelper.php:
--------------------------------------------------------------------------------
1 | dsn, $this->username, $this->password);
21 | $pdoDriver->charset($this->charset);
22 |
23 | return new Connection($pdoDriver, $this->createSchemaCache());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tests/Support/SqliteHelper.php:
--------------------------------------------------------------------------------
1 | charset($this->charset);
19 |
20 | return new Connection($pdoDriver, $this->createSchemaCache());
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | shouldReloadFixture = true;
23 | }
24 |
25 | protected static function reloadFixture(): void
26 | {
27 | ConnectionProvider::get()->close();
28 |
29 | $db = static::createConnection();
30 | ConnectionProvider::set($db);
31 |
32 | DbHelper::loadFixture($db);
33 | }
34 |
35 | protected static function db(): ConnectionInterface
36 | {
37 | return ConnectionProvider::get();
38 | }
39 |
40 | public static function setUpBeforeClass(): void
41 | {
42 | parent::setUpBeforeClass();
43 |
44 | $db = static::createConnection();
45 | ConnectionProvider::set($db);
46 | DbHelper::loadFixture($db);
47 | }
48 |
49 | protected function tearDown(): void
50 | {
51 | if ($this->shouldReloadFixture) {
52 | $this->reloadFixture();
53 | $this->shouldReloadFixture = false;
54 | }
55 |
56 | parent::tearDown();
57 | }
58 |
59 | public static function tearDownAfterClass(): void
60 | {
61 | ConnectionProvider::get()->close();
62 | ConnectionProvider::remove();
63 |
64 | parent::tearDownAfterClass();
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/tests/data/runtime/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yiisoft/active-record/4d39f1fc1eafff7c3b7adbbc77d137adc6ad5733/tests/data/runtime/.gitignore
--------------------------------------------------------------------------------