Skip to content

Commit

Permalink
Make Reference::$model property protected (#1180)
Browse files Browse the repository at this point in the history
  • Loading branch information
mvorisek authored Mar 7, 2024
1 parent 0ae77bb commit 5ef70ad
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 92 deletions.
134 changes: 68 additions & 66 deletions src/Reference.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@
use Atk4\Core\TrackableTrait;

/**
* Reference implements a link between one model and another. The basic components for
* a reference is ability to generate the destination model, which is returned through
* getModel() and that's pretty much it.
* Reference implements a link between our model and their model..
*
* It's possible to extend the basic reference with more meaningful references.
*
Expand All @@ -29,7 +27,7 @@ class Reference
}

/**
* Use this alias for related entity by default. This can help you
* Use this alias for their model by default. This can help you
* if you create sub-queries or joins to separate this from main
* table. The tableAlias will be uniquely generated.
*
Expand All @@ -47,14 +45,12 @@ class Reference
public $link;

/**
* Definition of the destination their model, that can be either an object, a
* callback or a string. This can be defined during initialization and
* then used inside getModel() to fully populate and associate with
* persistence.
* Seed of their model. If it is a Model instance, self::createTheirModel() must
* always clone it to return a new instance.
*
* @var Model|\Closure(object, static, array<string, mixed>): Model|array<mixed>
*/
public $model;
protected $model;

/**
* This is an optional property which can be used by your implementation
Expand Down Expand Up @@ -221,15 +217,47 @@ public function getOurModel(?Model $ourModelOrEntity): Model
return $ourModel;
}

protected function initTableAlias(): void
{
if (!$this->tableAlias) {
$ourModel = $this->getOurModel(null);

$aliasFull = $this->link;
$alias = preg_replace('~_(' . preg_quote($ourModel->idField !== false ? $ourModel->idField : '', '~') . '|id)$~', '', $aliasFull);
$alias = preg_replace('~([0-9a-z]?)[0-9a-z]*[^0-9a-z]*~i', '$1', $alias);
if ($ourModel->tableAlias !== null) {
$aliasFull = $ourModel->tableAlias . '_' . $aliasFull;
$alias = preg_replace('~^_(.+)_[0-9a-f]{12}$~', '$1', $ourModel->tableAlias) . '_' . $alias;
}
$this->tableAlias = '_' . $alias . '_' . substr(md5($aliasFull), 0, 12);
}
}

/**
* Create destination model that is linked through this reference. Will apply
* necessary conditions.
*
* IMPORTANT: the returned model must be a fresh clone or freshly built from a seed
* Returns default persistence for their model.
*
* @return Persistence|false
*/
protected function getDefaultPersistence(Model $theirModel)
{
$ourModel = $this->getOurModel(null);

// this is useful for ContainsOne/Many implementation in case when you have
// SQL_Model->containsOne()->hasOne() structure to get back to SQL persistence
// from Array persistence used in ContainsOne model
if ($ourModel->containedInEntity !== null && $ourModel->containedInEntity->getModel()->issetPersistence()) {
return $ourModel->containedInEntity->getModel()->getPersistence();
}

return $ourModel->issetPersistence()
? $ourModel->getPersistence()
: false;
}

/**
* @param array<string, mixed> $defaults
*/
public function createTheirModel(array $defaults = []): Model
protected function createTheirModelBeforeInit(array $defaults): Model
{
$defaults['tableAlias'] ??= $this->tableAlias;

Expand All @@ -251,79 +279,53 @@ public function createTheirModel(array $defaults = []): Model
$theirModel = Factory::factory($theirModelSeed, $defaults);
}

$this->addToPersistence($theirModel, $defaults);

if ($this->checkTheirType) {
$ourField = $this->getOurField();
$theirField = $theirModel->getField($this->getTheirFieldName($theirModel));
if ($theirField->type !== $ourField->type) {
throw (new Exception('Reference type mismatch'))
->addMoreInfo('ourField', $ourField)
->addMoreInfo('ourFieldType', $ourField->type)
->addMoreInfo('theirField', $theirField)
->addMoreInfo('theirFieldType', $theirField->type);
}
}

return $theirModel;
}

protected function initTableAlias(): void
{
if (!$this->tableAlias) {
$ourModel = $this->getOurModel(null);

$aliasFull = $this->link;
$alias = preg_replace('~_(' . preg_quote($ourModel->idField !== false ? $ourModel->idField : '', '~') . '|id)$~', '', $aliasFull);
$alias = preg_replace('~([0-9a-z]?)[0-9a-z]*[^0-9a-z]*~i', '$1', $alias);
if ($ourModel->tableAlias !== null) {
$aliasFull = $ourModel->tableAlias . '_' . $aliasFull;
$alias = preg_replace('~^_(.+)_[0-9a-f]{12}$~', '$1', $ourModel->tableAlias) . '_' . $alias;
}
$this->tableAlias = '_' . $alias . '_' . substr(md5($aliasFull), 0, 12);
}
}

/**
* @param array<string, mixed> $defaults
*/
protected function addToPersistence(Model $theirModel, array $defaults = []): void
protected function createTheirModelSetPersistence(Model $theirModel): void
{
if (!$theirModel->issetPersistence()) {
$persistence = $this->getDefaultPersistence($theirModel);
if ($persistence !== false) {
$theirModel->setDefaults($defaults);
$theirModel->setPersistence($persistence);
}
} elseif ($defaults !== []) {
// TODO this seems dangerous
}
}

// set model caption
protected function createTheirModelAfterInit(Model $theirModel): void
{
if ($this->caption !== null) {
$theirModel->caption = $this->caption;
}

if ($this->checkTheirType) {
$ourField = $this->getOurField();
$theirField = $theirModel->getField($this->getTheirFieldName($theirModel));
if ($theirField->type !== $ourField->type) {
throw (new Exception('Reference type mismatch'))
->addMoreInfo('ourField', $ourField)
->addMoreInfo('ourFieldType', $ourField->type)
->addMoreInfo('theirField', $theirField)
->addMoreInfo('theirFieldType', $theirField->type);
}
}
}

/**
* Returns default persistence for theirModel.
* Create their model that is linked through this reference. Will apply
* necessary conditions.
*
* @return Persistence|false
* IMPORTANT: the returned model must be a fresh clone or freshly built from a seed
*
* @param array<string, mixed> $defaults
*/
protected function getDefaultPersistence(Model $theirModel)
final public function createTheirModel(array $defaults = []): Model
{
$ourModel = $this->getOurModel(null);

// this is useful for ContainsOne/Many implementation in case when you have
// SQL_Model->containsOne()->hasOne() structure to get back to SQL persistence
// from Array persistence used in ContainsOne model
if ($ourModel->containedInEntity !== null && $ourModel->containedInEntity->getModel()->issetPersistence()) {
return $ourModel->containedInEntity->getModel()->getPersistence();
}
$theirModel = $this->createTheirModelBeforeInit($defaults);
$this->createTheirModelSetPersistence($theirModel);
$this->createTheirModelAfterInit($theirModel);

return $ourModel->issetPersistence()
? $ourModel->getPersistence()
: false;
return $theirModel;
}

/**
Expand Down
10 changes: 3 additions & 7 deletions src/Schema/Migrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -317,14 +317,10 @@ protected function getReferenceField(Field $field): ?Field
{
$reference = $field->getReference();
if ($reference instanceof HasOne) {
$referenceField = $reference->getTheirFieldName($reference->createTheirModel());
$theirModel = $reference->createTheirModel();
$referenceField = $reference->getTheirFieldName($theirModel);

$modelSeed = is_array($reference->model)
? $reference->model
: clone $reference->model;
$referenceModel = Model::fromSeed($modelSeed, [new Persistence\Sql($this->getConnection())]);

return $referenceModel->getField($referenceField);
return $theirModel->getField($referenceField);
}

return null;
Expand Down
16 changes: 8 additions & 8 deletions tests/LocalObjectTest.php → tests/Type/LocalObjectTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace Atk4\Data\Tests;
namespace Atk4\Data\Tests\Type;

use Atk4\Data\Exception;
use Atk4\Data\Model;
Expand All @@ -11,7 +11,7 @@
use Atk4\Data\Type\LocalObjectType;
use Doctrine\DBAL\Types as DbalTypes;

class LocalObjectTest extends TestCase
class LocalObjectTypeTest extends TestCase
{
/**
* @return \WeakMap<object, LocalObjectHandle>
Expand Down Expand Up @@ -51,7 +51,7 @@ protected function tearDown(): void
parent::tearDown();
}

public function testTypeBasic(): void
public function testBasic(): void
{
$t1 = new LocalObjectType();
$t2 = new LocalObjectType();
Expand Down Expand Up @@ -93,15 +93,15 @@ public function testTypeBasic(): void
self::assertNotSame($v4, $v3);
}

public function testTypeCloneException(): void
public function testCloneException(): void
{
$t = new LocalObjectType();

$this->expectException(\Error::class);
clone $t;
}

public function testTypeDifferentInstanceException(): void
public function testDifferentInstanceException(): void
{
$t1 = new LocalObjectType();
$t2 = new LocalObjectType();
Expand All @@ -116,7 +116,7 @@ public function testTypeDifferentInstanceException(): void
$t2->convertToPHPValue($v, $platform);
}

public function testTypeReleasedException(): void
public function testReleasedException(): void
{
$t = new LocalObjectType();
$platform = $this->getDatabasePlatform();
Expand Down Expand Up @@ -185,8 +185,8 @@ public function testDatabaseValueLengthIsLimited(): void
self::assertLessThan(250, strlen($v1));
self::assertLessThan(250, strlen($v2));

self::assertSame('Atk4\Data\Tests\LocalObjectDummyClassWithLongNameAWithLongNameBWithLongNameCWith...eFWithLongNameGWithLongNameHWithLongNameIWithLongNameJWithLongNameKWithLongNameL', explode('-', $v1)[0]);
self::assertSame('Atk4\Data\Tests\LocalObjectDummyClassWithLongNameAWithLongNameBWithLongNameCWith...NameGWithLongNameHWithLongNameIWithLongNameJWithLongNameKWithLongNameL@anonymous', explode('-', $v2)[0]);
self::assertSame('Atk4\Data\Tests\Type\LocalObjectDummyClassWithLongNameAWithLongNameBWithLongName...eFWithLongNameGWithLongNameHWithLongNameIWithLongNameJWithLongNameKWithLongNameL', explode('-', $v1)[0]);
self::assertSame('Atk4\Data\Tests\Type\LocalObjectDummyClassWithLongNameAWithLongNameBWithLongName...NameGWithLongNameHWithLongNameIWithLongNameJWithLongNameKWithLongNameL@anonymous', explode('-', $v2)[0]);
}
}

Expand Down
25 changes: 14 additions & 11 deletions tests/Util/DeepCopyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Atk4\Data\Tests\Util;

use Atk4\Data\Model;
use Atk4\Data\Reference;
use Atk4\Data\Schema\TestCase;
use Atk4\Data\Util\DeepCopy;
use Atk4\Data\Util\DeepCopyException;
Expand Down Expand Up @@ -343,17 +344,19 @@ public function testDeepNestedException(): void
{
$quote = $this->createTestQuote();

$quote->getModel()->getReference('client_id')->model = [get_class(new class() extends DcClient {
#[\Override]
protected function init(): void
{
parent::init();

$this->onHook(DeepCopy::HOOK_AFTER_COPY, static function (Model $entity) {
throw new \Exception('test ex');
});
}
})];
\Closure::bind(function () use ($quote) {
$quote->getModel()->getReference('client_id')->model = [get_class(new class() extends DcClient {
#[\Override]
protected function init(): void
{
parent::init();

$this->onHook(DeepCopy::HOOK_AFTER_COPY, static function (Model $entity) {
throw new \Exception('test ex');
});
}
})];
}, null, Reference::class)();

$dc = new DeepCopy();

Expand Down

0 comments on commit 5ef70ad

Please sign in to comment.