diff --git a/docs/persistence.md b/docs/persistence.md index 009ea3c8f..70a57c756 100644 --- a/docs/persistence.md +++ b/docs/persistence.md @@ -298,7 +298,7 @@ protected function init(): void $this->getOwner()->hasOne( $f . '_currency_id', [ - $this->currency_model ?? new Currency(), + 'model' => $this->currency_model ?? new Currency(), 'system' => true, ] ); diff --git a/src/Field.php b/src/Field.php index 220c513e4..0f8b7c8b1 100644 --- a/src/Field.php +++ b/src/Field.php @@ -38,7 +38,7 @@ public function __construct(array $defaults = []) { $this->setDefaults($defaults); - if (!(new \ReflectionProperty($this, 'type'))->isInitialized($this)) { + if (($this->type ?? null) === null) { $this->type = 'string'; } } diff --git a/src/Model.php b/src/Model.php index 8c9f51184..a4ba9d38b 100644 --- a/src/Model.php +++ b/src/Model.php @@ -123,8 +123,7 @@ class Model implements \IteratorAggregate /** @var string|null */ public $tableAlias; - /** @var Persistence|null */ - private $_persistence; + private ?Persistence $_persistence = null; /** @var array|null Persistence store some custom information in here that may be useful for them. */ public ?array $persistenceData = null; @@ -534,6 +533,12 @@ public function addField(string $name, $seed = []): Field { $this->assertIsModel(); + if ($this->hasField($name)) { + throw (new Exception('Field with such name already exists')) + ->addMoreInfo('name', $name) + ->addMoreInfo('seed', $seed); + } + if (is_object($seed)) { $field = $seed; } else { diff --git a/src/Model/ReferencesTrait.php b/src/Model/ReferencesTrait.php index 6ba7cd4b9..8f75e7756 100644 --- a/src/Model/ReferencesTrait.php +++ b/src/Model/ReferencesTrait.php @@ -155,14 +155,14 @@ public function ref(string $link, array $defaults = []): Model } /** - * Traverse reference and create their model but keep condition not materialized (for subquery actions). + * Traverse reference and create their model but keep reference condition not materialized (for subquery actions). * * @param array $defaults */ public function refLink(string $link, array $defaults = []): Model { - $reference = $this->getModel(true)->getReference($link); + $reference = $this->getReference($link); - return $reference->refLink($this, $defaults); + return $reference->refLink($defaults); } } diff --git a/src/Model/Scope/Condition.php b/src/Model/Scope/Condition.php index 2816500ce..6da32fcf4 100644 --- a/src/Model/Scope/Condition.php +++ b/src/Model/Scope/Condition.php @@ -287,12 +287,14 @@ public function negate(): self #[\Override] public function toWords(Model $model = null): string { - if ($model === null) { + if ($model !== null) { + $model->assertIsModel(); + } else { $model = $this->getModel(); - } - if ($model === null) { - throw new Exception('Condition must be associated with Model to convert to words'); + if ($model === null) { + throw new Exception('Condition must be associated with model to convert to words'); + } } $field = $this->fieldToWords($model); @@ -405,9 +407,7 @@ protected function valueToWords(Model $model, $value): string $title = null; if ($field instanceof Field && $field->hasReference()) { // make sure we set the value in the Model - $entity = $model->isEntity() - ? clone $model - : $model->createEntity(); + $entity = $model->createEntity(); $entity->set($field->shortName, $value); // then take the title diff --git a/src/Model/UserActionsTrait.php b/src/Model/UserActionsTrait.php index 0353184d5..36503f5db 100644 --- a/src/Model/UserActionsTrait.php +++ b/src/Model/UserActionsTrait.php @@ -27,6 +27,12 @@ public function addUserAction(string $name, $seed = []): UserAction { $this->assertIsModel(); + if ($this->hasUserAction($name)) { + throw (new Exception('User action with such name already exists')) + ->addMoreInfo('name', $name) + ->addMoreInfo('seed', $seed); + } + if ($seed instanceof \Closure) { $seed = ['callback' => $seed]; } diff --git a/src/Reference.php b/src/Reference.php index d01aee8ed..5798d2e8f 100644 --- a/src/Reference.php +++ b/src/Reference.php @@ -109,12 +109,12 @@ protected function assertReferenceValueNotNull($value): void public function getOurFieldName(): string { return $this->ourField - ?? $this->getOurModel(null)->idField; + ?? $this->getOurModel()->idField; } final protected function getOurField(): Field { - return $this->getOurModel(null)->getField($this->getOurFieldName()); + return $this->getOurModel()->getField($this->getOurFieldName()); } /** @@ -141,7 +141,7 @@ protected function onHookToOurModel(string $spot, \Closure $fx, array $args = [] { $name = $this->shortName; // use static function to allow this object to be GCed - return $this->getOurModel(null)->onHookDynamic( + return $this->getOurModel()->onHookDynamic( $spot, static function (Model $modelOrEntity) use ($name): self { /** @var self */ @@ -164,7 +164,7 @@ protected function onHookToTheirModel(Model $theirModel, string $spot, \Closure { $theirModel->assertIsModel(); - $ourModel = $this->getOurModel(null); + $ourModel = $this->getOurModel(); $name = $this->shortName; // use static function to allow this object to be GCed return $theirModel->onHookDynamic( @@ -203,14 +203,10 @@ public function assertOurModelOrEntity(Model $ourModelOrEntity): void ->assertIsModel($ourModelOrEntity->getModel(true)); } - public function getOurModel(?Model $ourModelOrEntity): Model + public function getOurModel(): Model { - $ourModel = $ourModelOrEntity !== null - ? $ourModelOrEntity->getModel(true) - : $this->getOwner(); - - $this->getOwner() - ->assertIsModel($ourModel); + $ourModel = $this->getOwner(); + $ourModel->assertIsModel(); return $ourModel; } @@ -218,7 +214,7 @@ public function getOurModel(?Model $ourModelOrEntity): Model protected function initTableAlias(): void { if (!$this->tableAlias) { - $ourModel = $this->getOurModel(null); + $ourModel = $this->getOurModel(); $aliasFull = $this->link; $alias = preg_replace('~_(' . preg_quote($ourModel->idField !== false ? $ourModel->idField : '', '~') . '|id)$~', '', $aliasFull); @@ -238,7 +234,7 @@ protected function initTableAlias(): void */ protected function getDefaultPersistence(Model $theirModel) { - $ourModel = $this->getOurModel(null); + $ourModel = $this->getOurModel(); // this is useful for ContainsOne/Many implementation in case when you have // SQL_Model->containsOne()->hasOne() structure to get back to SQL persistence @@ -261,7 +257,7 @@ protected function createTheirModelBeforeInit(array $defaults): Model // if model is Closure, then call the closure and it should return a model if ($this->model instanceof \Closure) { - $m = ($this->model)($this->getOurModel(null), $this, $defaults); + $m = ($this->model)($this->getOurModel(), $this, $defaults); } else { $m = $this->model; } diff --git a/src/Reference/ContainsBase.php b/src/Reference/ContainsBase.php index 0abf27c53..157369636 100644 --- a/src/Reference/ContainsBase.php +++ b/src/Reference/ContainsBase.php @@ -31,11 +31,11 @@ protected function init(): void { parent::init(); - if (!$this->ourField) { + if ($this->ourField === null) { $this->ourField = $this->link; } - $ourModel = $this->getOurModel(null); + $ourModel = $this->getOurModel(); $ourField = $this->getOurFieldName(); if (!$ourModel->hasField($ourField)) { diff --git a/src/Reference/ContainsMany.php b/src/Reference/ContainsMany.php index 5f6c8fc51..9a00339a6 100644 --- a/src/Reference/ContainsMany.php +++ b/src/Reference/ContainsMany.php @@ -32,8 +32,7 @@ public function ref(Model $ourModelOrEntity, array $defaults = []): Model $this->assertOurModelOrEntity($ourEntity); $ourEntity->assertIsEntity(); - /** @var Persistence\Array_ */ - $persistence = $theirEntity->getModel()->getPersistence(); + $persistence = Persistence\Array_::assertInstanceOf($theirEntity->getModel()->getPersistence()); $rows = $persistence->getRawDataByTable($theirEntity->getModel(), $this->tableAlias); // @phpstan-ignore-line $ourEntity->save([$this->getOurFieldName() => $rows !== [] ? $rows : null]); }); diff --git a/src/Reference/ContainsOne.php b/src/Reference/ContainsOne.php index ead5a34a2..b689f0300 100644 --- a/src/Reference/ContainsOne.php +++ b/src/Reference/ContainsOne.php @@ -32,8 +32,7 @@ public function ref(Model $ourModelOrEntity, array $defaults = []): Model $this->assertOurModelOrEntity($ourEntity); $ourEntity->assertIsEntity(); - /** @var Persistence\Array_ */ - $persistence = $theirEntity->getModel()->getPersistence(); + $persistence = Persistence\Array_::assertInstanceOf($theirEntity->getModel()->getPersistence()); $row = $persistence->getRawDataByTable($theirEntity->getModel(), $this->tableAlias); // @phpstan-ignore-line $row = $row ? array_shift($row) : null; // get first and only one record from array persistence $ourEntity->save([$this->getOurFieldName() => $row]); diff --git a/src/Reference/HasMany.php b/src/Reference/HasMany.php index 567459789..be137c3f1 100644 --- a/src/Reference/HasMany.php +++ b/src/Reference/HasMany.php @@ -24,17 +24,17 @@ private function getModelTableString(Model $model): string #[\Override] public function getTheirFieldName(Model $theirModel = null): string { - if ($this->theirField) { + if ($this->theirField !== null) { return $this->theirField; } // this is pure guess, verify if such field exist, otherwise throw // TODO probably remove completely in the future - $ourModel = $this->getOurModel(null); + $ourModel = $this->getOurModel(); $theirFieldName = preg_replace('~^.+\.~s', '', $this->getModelTableString($ourModel)) . '_' . $ourModel->idField; if (!($theirModel ?? $this->createTheirModel())->hasField($theirFieldName)) { - throw (new Exception('Their model does not contain fallback field')) - ->addMoreInfo('their_fallback_field', $theirFieldName); + throw (new Exception('Their model does not contain implicit field')) + ->addMoreInfo('theirImplicitField', $theirFieldName); } return $theirFieldName; @@ -50,7 +50,7 @@ protected function getOurFieldValueForRefCondition(Model $ourModelOrEntity) $this->assertOurModelOrEntity($ourModelOrEntity); if ($ourModelOrEntity->isEntity()) { - $res = $this->ourField + $res = $this->ourField !== null ? $ourModelOrEntity->get($this->ourField) : $ourModelOrEntity->getId(); $this->assertReferenceValueNotNull($res); @@ -71,7 +71,7 @@ protected function referenceOurValue(): Field { // TODO horrible hack to render the field with a table prefix, // find a solution how to wrap the field inside custom Field (without owner?) - $ourModelCloned = clone $this->getOurModel(null); + $ourModelCloned = clone $this->getOurModel(); $ourModelCloned->persistenceData['use_table_prefixes'] = true; return $ourModelCloned->getReference($this->link)->getOurField(); @@ -96,10 +96,8 @@ public function ref(Model $ourModelOrEntity, array $defaults = []): Model * * @param array $defaults */ - public function refLink(?Model $ourModel, array $defaults = []): Model + public function refLink(array $defaults = []): Model { - $this->getOurModel($ourModel); // or should $this->assertOurModelOrEntity($ourModelOrEntity); be here? What is exactly the difference between ref and refLink? - $theirModelLinked = $this->createTheirModel($defaults)->addCondition( $this->getTheirFieldName(), $this->referenceOurValue() @@ -134,7 +132,7 @@ public function addField(string $fieldName, array $defaults = []): Field if (isset($defaults['expr'])) { $fx = function () use ($defaults, $alias) { - $theirModelLinked = $this->refLink(null); + $theirModelLinked = $this->refLink(); return $theirModelLinked->action('field', [$theirModelLinked->expr( $defaults['expr'], @@ -144,15 +142,15 @@ public function addField(string $fieldName, array $defaults = []): Field unset($defaults['args']); } elseif (is_object($defaults['aggregate'])) { $fx = function () use ($defaults, $alias) { - return $this->refLink(null)->action('field', [$defaults['aggregate'], 'alias' => $alias]); + return $this->refLink()->action('field', [$defaults['aggregate'], 'alias' => $alias]); }; } elseif ($defaults['aggregate'] === 'count' && !isset($defaults['field'])) { $fx = function () use ($alias) { - return $this->refLink(null)->action('count', ['alias' => $alias]); + return $this->refLink()->action('count', ['alias' => $alias]); }; } elseif (in_array($defaults['aggregate'], ['sum', 'avg', 'min', 'max', 'count'], true)) { $fx = function () use ($defaults, $field) { - return $this->refLink(null)->action('fx0', [$defaults['aggregate'], $field]); + return $this->refLink()->action('fx0', [$defaults['aggregate'], $field]); }; } else { $fx = function () use ($defaults, $field) { @@ -161,11 +159,11 @@ public function addField(string $fieldName, array $defaults = []): Field $args['concatSeparator'] = $defaults['concatSeparator']; } - return $this->refLink(null)->action('fx', $args); + return $this->refLink()->action('fx', $args); }; } - return $this->getOurModel(null)->addExpression($fieldName, array_merge($defaults, ['expr' => $fx])); + return $this->getOurModel()->addExpression($fieldName, array_merge($defaults, ['expr' => $fx])); } /** diff --git a/src/Reference/HasOne.php b/src/Reference/HasOne.php index 5173afef9..e2a4b673b 100644 --- a/src/Reference/HasOne.php +++ b/src/Reference/HasOne.php @@ -18,12 +18,12 @@ protected function init(): void { parent::init(); - if (!$this->ourField) { + if ($this->ourField === null) { $this->ourField = $this->link; } // for references use "integer" as a default type - if (!(new \ReflectionProperty($this, 'type'))->isInitialized($this)) { + if (($this->type ?? null) === null) { $this->type = 'integer'; } @@ -32,7 +32,7 @@ protected function init(): void $fieldPropsRefl = (new \ReflectionClass(Model\FieldPropertiesTrait::class))->getProperties(); $fieldPropsRefl[] = (new \ReflectionClass(Model\JoinLinkTrait::class))->getProperty('joinName'); - $ourModel = $this->getOurModel(null); + $ourModel = $this->getOurModel(); if (!$ourModel->hasField($this->ourField)) { $fieldSeed = []; foreach ($fieldPropsRefl as $fieldPropRefl) { @@ -63,7 +63,7 @@ protected function referenceOurValue(): Field { // TODO horrible hack to render the field with a table prefix, // find a solution how to wrap the field inside custom Field (without owner?) - $ourModelCloned = clone $this->getOurModel(null); + $ourModelCloned = clone $this->getOurModel(); $ourModelCloned->persistenceData['use_table_prefixes'] = true; return $ourModelCloned->getReference($this->link)->getOurField(); @@ -84,7 +84,7 @@ public function ref(Model $ourModelOrEntity, array $defaults = []): Model if ($ourModelOrEntity->isEntity()) { $this->onHookToTheirModel($theirModel, Model::HOOK_AFTER_SAVE, function (Model $theirEntity) use ($ourModelOrEntity) { - $theirValue = $this->theirField + $theirValue = $this->theirField !== null ? $theirEntity->get($this->theirField) : $theirEntity->getId(); diff --git a/src/Reference/HasOneSql.php b/src/Reference/HasOneSql.php index dbbeb7458..39bada075 100644 --- a/src/Reference/HasOneSql.php +++ b/src/Reference/HasOneSql.php @@ -16,7 +16,7 @@ class HasOneSql extends HasOne */ private function _addField(string $fieldName, bool $theirFieldIsTitle, ?string $theirFieldName, array $defaults): SqlExpressionField { - $ourModel = $this->getOurModel(null); + $ourModel = $this->getOurModel(); $fieldExpression = $ourModel->addExpression($fieldName, array_merge([ 'expr' => function (Model $ourModel) use ($theirFieldIsTitle, $theirFieldName) { @@ -76,9 +76,9 @@ public function addField(string $fieldName, string $theirFieldName = null, array $theirFieldName = $fieldName; } - $ourModel = $this->getOurModel(null); + $ourModel = $this->getOurModel(); - // if caption/type is not defined in $defaults -> get it directly from the linked model field $theirFieldName + // if caption/type is not defined in $defaults then infer it from their field $refModel = $ourModel->getReference($this->link)->createTheirModel(); $refModelField = $refModel->getField($theirFieldName); $defaults['type'] ??= $refModelField->type; @@ -122,20 +122,6 @@ public function addFields(array $fields = [], array $defaults = []) return $this; } - /** - * Creates model that can be used for generating sub-query actions. - * - * @param array $defaults - */ - public function refLink(Model $ourModel, array $defaults = []): Model - { - $theirModel = $this->createTheirModel($defaults); - - $theirModel->addCondition($this->getTheirFieldName($theirModel), $this->referenceOurValue()); - - return $theirModel; - } - #[\Override] public function ref(Model $ourModelOrEntity, array $defaults = []): Model { @@ -156,6 +142,20 @@ public function ref(Model $ourModelOrEntity, array $defaults = []): Model return $theirModel; } + /** + * Creates model that can be used for generating sub-query actions. + * + * @param array $defaults + */ + public function refLink(array $defaults = []): Model + { + $theirModel = $this->createTheirModel($defaults); + + $theirModel->addCondition($this->getTheirFieldName($theirModel), $this->referenceOurValue()); + + return $theirModel; + } + /** * Add a title of related entity as expression to our field. * @@ -167,7 +167,7 @@ public function ref(Model $ourModelOrEntity, array $defaults = []): Model */ public function addTitle(array $defaults = []): SqlExpressionField { - $ourModel = $this->getOurModel(null); + $ourModel = $this->getOurModel(); $fieldName = $defaults['field'] ?? preg_replace('~_(' . preg_quote($ourModel->idField, '~') . '|id)$~', '', $this->link); diff --git a/tests/ContainsOne/Address.php b/tests/ContainsOne/Address.php index afe899b8b..d24ba8c9d 100644 --- a/tests/ContainsOne/Address.php +++ b/tests/ContainsOne/Address.php @@ -20,7 +20,7 @@ protected function init(): void { parent::init(); - $this->hasOne($this->fieldName()->country_id, ['model' => [Country::class], 'type' => 'integer']); + $this->hasOne($this->fieldName()->country_id, ['model' => [Country::class]]); $this->addField($this->fieldName()->address); $this->addField($this->fieldName()->built_date, ['type' => 'datetime']); diff --git a/tests/FieldTest.php b/tests/FieldTest.php index d27555093..a338cf137 100644 --- a/tests/FieldTest.php +++ b/tests/FieldTest.php @@ -767,11 +767,22 @@ public function testNormalizeObjectException(): void $m->set('foo', 'ABC'); } - public function testAddFieldDirectly(): void + public function testAddFieldDuplicateNameException(): void + { + $m = new Model(); + $m->addField('foo'); + + $this->expectException(Exception::class); + $this->expectExceptionMessage('Field with such name already exists'); + $m->addField('foo'); + } + + public function testAddFieldDirectlyException(): void { $model = new Model(); $this->expectException(Exception::class); + $this->expectExceptionMessage('Field can be added using addField() method only'); $model->add(new Field()); } diff --git a/tests/JoinArrayTest.php b/tests/JoinArrayTest.php index 2f2521bec..dd3a370b6 100644 --- a/tests/JoinArrayTest.php +++ b/tests/JoinArrayTest.php @@ -57,22 +57,35 @@ public function testDirection(): void self::assertSame('test_id', $this->getProtected($j, 'masterField')); self::assertSame('id', $this->getProtected($j, 'foreignField')); - $this->expectException(Exception::class); // TODO not implemented yet, see https://github.com/atk4/data/issues/803 + $this->expectException(Exception::class); + $this->expectExceptionMessage('Joining tables on non-id fields is not implemented yet'); $j = $m->join('contact4.foo_id', ['masterField' => 'test_id', 'reverse' => true]); // self::assertTrue($j->reverse); // self::assertSame('test_id', $this->getProtected($j, 'masterField')); // self::assertSame('foo_id', $this->getProtected($j, 'foreignField')); } - public function testJoinException(): void + public function testDirectionException(): void { $db = new Persistence\Array_(['user' => [], 'contact' => []]); $m = new Model($db, ['table' => 'user']); $this->expectException(Exception::class); + $this->expectExceptionMessage('Joining tables on non-id fields is not implemented yet'); $m->join('contact.foo_id', ['masterField' => 'test_id']); } + public function testAddJoinDuplicateNameException(): void + { + $db = new Persistence\Array_(); + $m = new Model($db); + $m->join('foo'); + + $this->expectException(Exception::class); + $this->expectExceptionMessage('Join with such name already exists'); + $m->join('foo'); + } + public function testJoinSaving1(): void { $db = new Persistence\Array_(['user' => [], 'contact' => []]); @@ -212,15 +225,16 @@ public function testJoinSaving3(): void ], $this->getInternalPersistenceData($db)); } - /* Joining tables on non-id fields is not implemented yet public function testJoinSaving4(): void { $db = new Persistence\Array_(['user' => [], 'contact' => []]); $user = new Model($db, ['table' => 'user']); $user->addField('name'); $user->addField('code'); + $this->expectException(Exception::class); + $this->expectExceptionMessage('Joining tables on non-id fields is not implemented yet'); $j = $user->join('contact.code', ['masterField' => 'code']); - $j->addField('contact_phone'); + /* $j->addField('contact_phone'); $user = $user->createEntity(); $user->set('name', 'John'); @@ -236,9 +250,8 @@ public function testJoinSaving4(): void 'contact' => [ 1 => ['id' => 1, 'code' => 'C28', 'contact_phone' => '+123'], ], - ], $this->getInternalPersistenceData($db)); + ], $this->getInternalPersistenceData($db)); */ } - */ public function testJoinLoading(): void { diff --git a/tests/LimitOrderTest.php b/tests/LimitOrderTest.php index 2749fd9c1..0359e742d 100644 --- a/tests/LimitOrderTest.php +++ b/tests/LimitOrderTest.php @@ -224,10 +224,34 @@ public function testLimit(): void $i = new Model($this->db, ['table' => 'invoice', 'idField' => false]); $i->addField('total_net', ['type' => 'integer']); - $i->addField('total_vat', ['type' => 'integer']); - $i->addExpression('total_gross', ['expr' => '[total_net] + [total_vat]', 'type' => 'integer']); - $i->setOrder('total_net'); + + self::assertSame(10, $i->loadAny()->get('total_net')); + $i->setLimit(2); + self::assertSame(10, $i->loadAny()->get('total_net')); + + $i->setLimit(2, 2); + self::assertSame(20, $i->loadAny()->get('total_net')); + self::assertSame(20, $i->loadOne()->get('total_net')); + + $i->setLimit(1); + self::assertSame(10, $i->loadAny()->get('total_net')); + self::assertSame(10, $i->loadOne()->get('total_net')); + $i->setLimit(1, 1); + self::assertSame(15, $i->loadAny()->get('total_net')); + self::assertSame(15, $i->loadOne()->get('total_net')); + $i->setLimit(1, 2); + self::assertSame(20, $i->loadAny()->get('total_net')); + self::assertSame(20, $i->loadOne()->get('total_net')); + $i->setLimit(1, 3); + self::assertNull($i->tryLoadAny()); + self::assertNull($i->tryLoadOne()); + + $i->setLimit(0); + self::assertNull($i->tryLoadAny()); + self::assertNull($i->tryLoadOne()); + + $i->setLimit(); $i->setOnlyFields(['total_net']); self::assertSame([ ['total_net' => 10], @@ -257,44 +281,4 @@ public function testLimit(): void ['total_net' => 20], ], $i->export()); } - - public function testLimitBug1010(): void - { - $this->setDb([ - 'invoice' => [ - ['total_net' => 10], - ['total_net' => 20], - ['total_net' => 15], - ], - ]); - - $i = new Model($this->db, ['table' => 'invoice']); - $i->addField('total_net', ['type' => 'integer']); - $i->setOrder('total_net'); - - self::assertSame(10, $i->loadAny()->get('total_net')); - $i->setLimit(2); - self::assertSame(10, $i->loadAny()->get('total_net')); - - $i->setLimit(2, 2); - self::assertSame(20, $i->loadAny()->get('total_net')); - self::assertSame(20, $i->loadOne()->get('total_net')); - - $i->setLimit(1); - self::assertSame(10, $i->loadAny()->get('total_net')); - self::assertSame(10, $i->loadOne()->get('total_net')); - $i->setLimit(1, 1); - self::assertSame(15, $i->loadAny()->get('total_net')); - self::assertSame(15, $i->loadOne()->get('total_net')); - $i->setLimit(1, 2); - self::assertSame(20, $i->loadAny()->get('total_net')); - self::assertSame(20, $i->loadOne()->get('total_net')); - $i->setLimit(1, 3); - self::assertNull($i->tryLoadAny()); - self::assertNull($i->tryLoadOne()); - - $i->setLimit(0); - self::assertNull($i->tryLoadAny()); - self::assertNull($i->tryLoadOne()); - } } diff --git a/tests/Persistence/Sql/QueryTest.php b/tests/Persistence/Sql/QueryTest.php index 5ac4c2320..23b91fae4 100644 --- a/tests/Persistence/Sql/QueryTest.php +++ b/tests/Persistence/Sql/QueryTest.php @@ -37,7 +37,7 @@ public function __construct($defaults = [], array $arguments = []) } }; - if (!(new \ReflectionProperty($query, 'connection'))->isInitialized($query)) { + if (($query->connection ?? null) === null) { $query->connection = \Closure::bind(static function () use ($query) { $connection = new Persistence\Sql\Sqlite\Connection(); $connection->expressionClass = \Closure::bind(static fn () => $query->expressionClass, null, Query::class)(); diff --git a/tests/RandomTest.php b/tests/RandomTest.php index 3fd726e42..aa5a02228 100644 --- a/tests/RandomTest.php +++ b/tests/RandomTest.php @@ -4,7 +4,6 @@ namespace Atk4\Data\Tests; -use Atk4\Core\Exception as CoreException; use Atk4\Data\Exception; use Atk4\Data\Field; use Atk4\Data\Model; @@ -355,12 +354,12 @@ public function testHookBreakers(): void $m->delete(); } - public function testIssue220(): void + public function testAddTitleDuplicateNameException(): void { $m = new Model_Item($this->db); - $this->expectException(CoreException::class); - $this->expectExceptionMessage('already exist'); + $this->expectException(Exception::class); + $this->expectExceptionMessage('Field with such name already exists'); $m->hasOne('foo', ['model' => [Model_Item::class]])->addTitle(); } diff --git a/tests/ReferenceSqlTest.php b/tests/ReferenceSqlTest.php index 9f9b5a5eb..b98adb75f 100644 --- a/tests/ReferenceSqlTest.php +++ b/tests/ReferenceSqlTest.php @@ -68,10 +68,7 @@ public function testBasic(): void ); } - /** - * Tests to make sure refLink properly generates field links. - */ - public function testLink(): void + public function testRefLink(): void { $u = new Model($this->db, ['table' => 'user']); $u->addField('name'); @@ -88,6 +85,24 @@ public function testLink(): void ); } + public function testRefLink2(): void + { + $u = new Model($this->db, ['table' => 'user']); + $u->addField('name'); + $u->addField('currency_code'); + + $c = new Model($this->db, ['table' => 'currency']); + $c->addField('code'); + $c->addField('name'); + + $u->hasMany('cur', ['model' => $c, 'ourField' => 'currency_code', 'theirField' => 'code']); + + $this->assertSameSql( + 'select `id`, `code`, `name` from `currency` `_c_b5fddf1ef601` where `code` = `user`.`currency_code`', + $u->refLink('cur')->action('select')->render()[0] + ); + } + public function testBasic2(): void { $this->setDb([ @@ -123,24 +138,6 @@ public function testBasic2(): void self::assertSame('Pound', $cc->get('name')); } - public function testLink2(): void - { - $u = new Model($this->db, ['table' => 'user']); - $u->addField('name'); - $u->addField('currency_code'); - - $c = new Model($this->db, ['table' => 'currency']); - $c->addField('code'); - $c->addField('name'); - - $u->hasMany('cur', ['model' => $c, 'ourField' => 'currency_code', 'theirField' => 'code']); - - $this->assertSameSql( - 'select `id`, `code`, `name` from `currency` `_c_b5fddf1ef601` where `code` = `user`.`currency_code`', - $u->refLink('cur')->action('select')->render()[0] - ); - } - /** * Tests that condition defined on the parent model is retained when traversing through hasMany. */ diff --git a/tests/ReferenceTest.php b/tests/ReferenceTest.php index 6490f45e8..5907bfc2c 100644 --- a/tests/ReferenceTest.php +++ b/tests/ReferenceTest.php @@ -79,7 +79,17 @@ public function testModelProperty(): void self::assertSame('order', $o->getModel()->table); } - public function testRefName1(): void + public function testRefLinkEntityException(): void + { + $user = new Model($this->db, ['table' => 'user']); + $user = $user->createEntity(); + + $this->expectException(\TypeError::class); + $this->expectExceptionMessage('Expected model, but instance is an entity'); + $user->refLink('order'); + } + + public function testHasManyDuplicateNameException(): void { $user = new Model(null, ['table' => 'user']); $order = new Model(); @@ -88,10 +98,11 @@ public function testRefName1(): void $user->hasMany('Orders', ['model' => $order]); $this->expectException(Exception::class); + $this->expectExceptionMessage('Reference with such name already exists'); $user->hasMany('Orders', ['model' => $order]); } - public function testRefName2(): void + public function testHasOneDuplicateNameException(): void { $order = new Model(null, ['table' => 'order']); $user = new Model(null, ['table' => 'user']); @@ -99,6 +110,7 @@ public function testRefName2(): void $user->hasOne('user_id', ['model' => $user]); $this->expectException(Exception::class); + $this->expectExceptionMessage('Reference with such name already exists'); $user->hasOne('user_id', ['model' => $user]); } @@ -157,5 +169,7 @@ public function testRefTypeMismatchWithDisabledCheck(): void $order->hasOne('placed_by', ['model' => $user, 'ourField' => 'placed_by_user_id', 'checkTheirType' => false]); self::assertSame('user', $order->ref('placed_by')->table); + self::assertSame('string', $order->getField('placed_by_user_id')->type); + self::assertSame('integer', $order->ref('placed_by')->getIdField()->type); } } diff --git a/tests/ScopeTest.php b/tests/ScopeTest.php index 5dfcd3812..974dc118e 100644 --- a/tests/ScopeTest.php +++ b/tests/ScopeTest.php @@ -216,7 +216,7 @@ public function testConditionUnsupportedToWords(): void $condition = new Condition('name', 'abc'); $this->expectException(Exception::class); - $this->expectExceptionMessage('Condition must be associated with Model to convert to words'); + $this->expectExceptionMessage('Condition must be associated with model to convert to words'); $condition->toWords(); } diff --git a/tests/UserActionTest.php b/tests/UserActionTest.php index d59e02927..fe0576e42 100644 --- a/tests/UserActionTest.php +++ b/tests/UserActionTest.php @@ -159,6 +159,16 @@ public function testPreview(): void self::assertSame('Also Backup UaClient', $client->getUserAction('also_backup')->getDescription()); } + public function testAddUserActionDuplicateNameException(): void + { + $m = new Model(); + $m->addUserAction('foo', static fn () => 1); + + $this->expectException(Exception::class); + $this->expectExceptionMessage('User action with such name already exists'); + $m->addUserAction('foo', static fn () => 1); + } + public function testAppliesToSingleRecordNotEntityException(): void { $client = new UaClient($this->pers);