Skip to content

Commit

Permalink
Deduplicate model properties and Field/Join from entity (#912)
Browse files Browse the repository at this point in the history
  • Loading branch information
mvorisek authored Nov 11, 2021
1 parent f7805b6 commit 322a232
Show file tree
Hide file tree
Showing 50 changed files with 829 additions and 797 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/test-unit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -198,13 +198,15 @@ jobs:
php -d opcache.enable_cli=1 vendor/bin/phpunit --exclude-group none $(if [ -n "$LOG_COVERAGE" ]; then echo --coverage-text; else echo --no-coverage; fi) -v
if [ -n "$LOG_COVERAGE" ]; then mv coverage/phpunit.cov coverage/phpunit-mssql.cov; fi
- name: "Run tests: Oracle"
- name: "Run tests: Oracle (only for coverage or cron)"
if: env.LOG_COVERAGE || github.event_name == 'schedule'
env:
DB_DSN: "oci:dbname=oracle/xe;charset=UTF8"
DB_USER: system
DB_PASSWORD: oracle
run: |
php -d opcache.enable_cli=1 vendor/bin/phpunit --exclude-group none $(if [ -n "$LOG_COVERAGE" ]; then echo --coverage-text; else echo --no-coverage; fi) -v
php -d opcache.enable_cli=1 vendor/bin/phpunit --exclude-group none $(if [ -n "$LOG_COVERAGE" ]; then echo --coverage-text; else echo --no-coverage; fi) -v \
|| php -d opcache.enable_cli=1 vendor/bin/phpunit --exclude-group none $(if [ -n "$LOG_COVERAGE" ]; then echo --coverage-text; else echo --no-coverage; fi) -v
if [ -n "$LOG_COVERAGE" ]; then mv coverage/phpunit.cov coverage/phpunit-oracle.cov; fi
- name: Upload coverage logs 1/2 (only for latest Phpunit)
Expand Down
12 changes: 6 additions & 6 deletions docs/advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ To implement the above, I'll create a new class::
class Controller_Audit {

use \Atk4\Core\InitializerTrait {
init as _init;
init as private _init;
}
use \Atk4\Core\TrackableTrait;
use \Atk4\Core\AppScopeTrait;
Expand Down Expand Up @@ -216,7 +216,7 @@ Start by creating a class::
class Controller_SoftDelete {

use \Atk4\Core\InitializerTrait {
init as _init;
init as private _init;
}
use \Atk4\Core\TrackableTrait;

Expand Down Expand Up @@ -325,7 +325,7 @@ before and just slightly modifying it::
class Controller_SoftDelete {

use \Atk4\Core\InitializerTrait {
init as _init;
init as private _init;
}
use \Atk4\Core\TrackableTrait;

Expand Down Expand Up @@ -359,7 +359,7 @@ before and just slightly modifying it::
$m->save(['is_deleted' => true])->unload();
$m->reload_after_save = $rs;

$m->hook(Model::HOOK_AFTER_DELETE, [$id]);
$m->hook(Model::HOOK_AFTER_DELETE);

$m->breakHook(false); // this will cancel original delete()
}
Expand Down Expand Up @@ -411,7 +411,7 @@ inside your model are unique::

class Controller_UniqueFields {
use \Atk4\Core\InitializerTrait {
init as _init;
init as private _init;
}
use \Atk4\Core\TrackableTrait;

Expand Down Expand Up @@ -517,7 +517,7 @@ Next we need to define reference. Inside Model_Invoice add::
$j->hasOne('invoice_id', 'Model_Invoice');
}, 'their_field' => 'invoice_id']);

$this->onHookShort(Model::HOOK_BEFORE_DELETE, function(){
$this->onHookShort(Model::HOOK_BEFORE_DELETE, function() {
$this->ref('InvoicePayment')->action('delete')->execute();

// If you have important per-row hooks in InvoicePayment
Expand Down
2 changes: 1 addition & 1 deletion docs/hooks.rst
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ The delete has only "before" hook:

- beforeDeleteQuery($m, $dsql_query)

Finally for queries there is hook ``initSelectQuery($m, $query, $type)``.
Finally for queries there is hook ``initSelectQuery($model, $query, $type)``.
It can be used to enhance queries generated by "action" for:

- "count"
Expand Down
2 changes: 1 addition & 1 deletion docs/model.rst
Original file line number Diff line number Diff line change
Expand Up @@ -701,7 +701,7 @@ Title Field
you can specify that in the property. If title_field is not needed, set it
to false or point towards a non-existent field.

See: :php:meth::`hasOne::addTitle()` and :php:meth::`hasOne::withTitle()`
See: :php:meth::`hasOne::addTitle()`

.. php:method:: public getTitle
Expand Down
14 changes: 7 additions & 7 deletions docs/persistence/sql/queries.rst
Original file line number Diff line number Diff line change
Expand Up @@ -839,7 +839,7 @@ Other Methods
You can pass operand as parameter to create SQL like
CASE <operand> WHEN <expression> THEN <expression> END type of SQL statement.

.. php:method:: when($when, $then)
.. php:method:: caseWhen($when, $then)
Set WHEN condition and THEN expression for CASE statement.

Expand All @@ -851,18 +851,18 @@ Other Methods

.. code-block:: php
$s = $this->q()->caseExpr()
->when(['status','New'], 't2.expose_new')
->when(['status', 'like', '%Used%'], 't2.expose_used')
->otherwise(null);
->caseWhen(['status','New'], 't2.expose_new')
->caseWhen(['status', 'like', '%Used%'], 't2.expose_used')
->caseElse(null);

.. code-block:: sql
case when "status" = 'New' then "t2"."expose_new" when "status" like '%Used%' then "t2"."expose_used" else null end

.. code-block:: php
$s = $this->q()->caseExpr('status')
->when('New', 't2.expose_new')
->when('Used', 't2.expose_used')
->otherwise(null);
->caseWhen('New', 't2.expose_new')
->caseWhen('Used', 't2.expose_used')
->caseElse(null);

.. code-block:: sql
case "status" when 'New' then "t2"."expose_new" when 'Used' then "t2"."expose_used" else null end
Expand Down
4 changes: 0 additions & 4 deletions docs/sql.rst
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,6 @@ SQL Reference

Returns new field object.

.. php:method:: withTitle
Similar to addTitle, but returns $this.

Expressions
-----------

Expand Down
2 changes: 0 additions & 2 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,6 @@ parameters:
- '~^Call to an undefined method Atk4\\Data\\Reference\\HasOne::addTitle\(\)\.$~'
# for tests/JoinSqlTest.php
- '~^Call to an undefined method Atk4\\Data\\Reference\\HasOne::addField\(\)\.$~'
# for tests/LookupSqlTest.php
- '~^Call to an undefined method Atk4\\Data\\Reference\\HasOne::withTitle\(\)\.$~'
# for tests/ReferenceSqlTest.php
- '~^Call to an undefined method Atk4\\Data\\Reference\\HasOne::addFields\(\)\.$~'
- '~^Call to an undefined method Atk4\\Data\\Reference::addTitle\(\)\.$~'
Expand Down
92 changes: 48 additions & 44 deletions src/Field.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,17 @@
class Field implements Expressionable
{
use DiContainerTrait {
setDefaults as _setDefaults;
setDefaults as private _setDefaults;
}
use Model\FieldPropertiesTrait;
use Model\JoinLinkTrait;
use ReadableCaptionTrait;
use TrackableTrait;
use TrackableTrait {
setOwner as private _setOwner;
}

// {{{ Core functionality

/**
* Constructor. You can pass field properties as array.
*/
public function __construct(array $defaults = [])
{
foreach ($defaults as $key => $val) {
Expand All @@ -41,6 +40,18 @@ public function __construct(array $defaults = [])
}
}

/**
* @param Model $owner
*
* @return $this
*/
public function setOwner(object $owner)
{
$owner->assertIsModel();

return $this->_setOwner($owner);
}

public function setDefaults(array $properties, bool $passively = false): self
{
$this->_setDefaults($properties, $passively);
Expand All @@ -59,14 +70,17 @@ public function getTypeObject(): Type
return Type::getType($this->type ?? 'string');
}

protected function onHookShortToOwner(string $spot, \Closure $fx, array $args = [], int $priority = 5): int
protected function onHookToOwnerEntity(string $spot, \Closure $fx, array $args = [], int $priority = 5): int
{
$name = $this->short_name; // use static function to allow this object to be GCed

return $this->getOwner()->onHookDynamicShort(
return $this->getOwner()->onHookDynamic(
$spot,
static function (Model $owner) use ($name) {
return $owner->getField($name);
static function (Model $entity) use ($name): self {
$obj = $entity->getModel()->getField($name);
$entity->assertIsEntity($obj->getOwner());

return $obj;
},
$fx,
$args,
Expand Down Expand Up @@ -272,15 +286,10 @@ public function normalize($value)
/**
* Casts field value to string.
*
* @param mixed $value Optional value
* @param mixed $value
*/
public function toString($value = null): string
public function toString($value): string
{
$value = ($value === null /* why not func_num_args() === 1 */ ? $this->get() : $this->normalize($value));
if (is_bool($value)) {
$value = $value ? '1' : '0';
}

return (string) $this->typecastSaveField($value, true);
}

Expand All @@ -289,29 +298,35 @@ public function toString($value = null): string
*
* @return mixed
*/
public function get()
final public function get(Model $entity)
{
return $this->getOwner()->get($this->short_name);
$entity->assertIsEntity($this->getOwner());

return $entity->get($this->short_name);
}

/**
* Sets field value.
*
* @param mixed $value
*/
public function set($value): self
final public function set(Model $entity, $value): self
{
$this->getOwner()->set($this->short_name, $value);
$entity->assertIsEntity($this->getOwner());

$entity->set($this->short_name, $value);

return $this;
}

/**
* Unset field value even if null value is not allowed.
*/
public function setNull(): self
final public function setNull(Model $entity): self
{
$this->getOwner()->setNull($this->short_name);
$entity->assertIsEntity($this->getOwner());

$entity->setNull($this->short_name);

return $this;
}
Expand Down Expand Up @@ -353,29 +368,25 @@ private function getValueForCompare($value): ?string

/**
* Compare new value of the field with existing one without retrieving.
* In the trivial case it's same as ($value == $model->get($name)) but this method can be used for:
* - comparing values that can't be received - passwords, encrypted data
* - comparing images
* - if get() is expensive (e.g. retrieve object).
*
* @param mixed $value
* @param mixed|void $value2
* @param mixed $value
* @param mixed $value2
*/
public function compare($value, $value2 = null): bool
public function compare($value, $value2): bool
{
if (func_num_args() === 1) {
$value2 = $this->get();
}

// TODO, see https://stackoverflow.com/questions/48382457/mysql-json-column-change-array-order-after-saving
// at least MySQL sorts the JSON keys if stored natively
return $this->getValueForCompare($value) === $this->getValueForCompare($value2);
}

public function getReference(): ?Reference
public function getReference(Model $entity = null): ?Reference
{
if ($entity !== null) {
$entity->assertIsEntity($this->getOwner());
}

return $this->referenceLink !== null
? $this->getOwner()->getRef($this->referenceLink)
? ($entity ?? $this->getOwner())->getRef($this->referenceLink)
: null;
}

Expand Down Expand Up @@ -481,20 +492,15 @@ public function getDsqlExpression(Expression $expression): Expression
return $this->getOwner()->persistence->getFieldSqlExpression($this, $expression);
}

// {{{ Debug Methods

/**
* Returns array with useful debug info for var_dump.
*/
public function __debugInfo(): array
{
$arr = [
'short_name' => $this->short_name,
'value' => $this->get(),
'type' => $this->type,
];

foreach ([
'type', 'system', 'never_persist', 'never_save', 'read_only', 'ui', 'joinName',
'system', 'never_persist', 'never_save', 'read_only', 'ui', 'joinName',
] as $key) {
if ($this->{$key} !== null) {
$arr[$key] = $this->{$key};
Expand All @@ -503,6 +509,4 @@ public function __debugInfo(): array

return $arr;
}

// }}}
}
10 changes: 4 additions & 6 deletions src/Field/Callback.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@
class Callback extends \Atk4\Data\Field
{
use InitializerTrait {
init as _init;
init as private _init;
}

/** @var bool Expressions are always read_only. */
public $read_only = true;
/** @var bool Never persist this field. */
public $never_persist = true;

/** @var \Closure Method to execute for evaluation. */
/** @var \Closure(Model): mixed */
public $expr;

protected function init(): void
Expand All @@ -30,10 +30,8 @@ protected function init(): void

$this->ui['table']['sortable'] = false;

$this->onHookShortToOwner(Model::HOOK_AFTER_LOAD, function () {
$model = $this->getOwner();

$model->getDataRef()[$this->short_name] = ($this->expr)($model);
$this->onHookToOwnerEntity(Model::HOOK_AFTER_LOAD, function (Model $entity) {
$entity->getDataRef()[$this->short_name] = ($this->expr)($entity);
});
}
}
6 changes: 3 additions & 3 deletions src/FieldSqlExpression.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
class FieldSqlExpression extends FieldSql
{
use InitializerTrait {
init as _init;
init as private _init;
}

/**
Expand Down Expand Up @@ -67,15 +67,15 @@ protected function init(): void
}

if ($this->concat) {
$this->onHookShortToOwner(Model::HOOK_AFTER_SAVE, \Closure::fromCallable([$this, 'afterSave']));
$this->onHookToOwnerEntity(Model::HOOK_AFTER_SAVE, \Closure::fromCallable([$this, 'afterSave']));
}
}

/**
* Possibly that user will attempt to insert values here. If that is the case, then
* we would need to inject it into related hasMany relationship.
*/
public function afterSave(): void
public function afterSave(Model $entity): void
{
}

Expand Down
Loading

0 comments on commit 322a232

Please sign in to comment.