Skip to content

Commit

Permalink
Fix object value to be implied from definitive condition (#1090)
Browse files Browse the repository at this point in the history
  • Loading branch information
mvorisek authored Feb 9, 2023
1 parent 9c7addb commit e4cb17b
Show file tree
Hide file tree
Showing 29 changed files with 544 additions and 375 deletions.
2 changes: 1 addition & 1 deletion docs/conditions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ Scope can be created using new Scope() statement from an array or joining Condit
// $condition1 will be used as nested condition
$condition1 = new Condition('name', 'like', 'ABC%');

// $condition2 will converted to Condtion object and used as nested condition
// $condition2 will converted to Condition object and used as nested condition
$condition2 = ['country', 'US'];

// $scope1 is created using AND as junction and $condition1 and $condition2 as nested conditions
Expand Down
16 changes: 8 additions & 8 deletions docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ to do so.
It might be handy to use in-line definition of a model. Try the following
inside console::

$m = new \Atk4\Data\Model($db, 'contact_info');
$m = new Model($db, 'contact_info');
$m->addField('address_1');
$m->addField('address_2');
$m->addCondition('address_1', '!=', null);
Expand All @@ -112,7 +112,7 @@ inside console::
Next, exit and create file `src/Model_ContactInfo.php`::

<?php
class Model_ContactInfo extends \Atk4\Data\Model
class Model_ContactInfo extends Model
{
public $table = 'contact_info';

Expand Down Expand Up @@ -171,7 +171,7 @@ it cannot be removed for safety reasons.
Suppose you have a method that converts DataSet into JSON. Ability to add
conditions is your way to specify which records to operate on::

public function myexport(\Atk4\Data\Model $m, $fields)
public function myexport(\Atk4\Data\Model $m, array $fields = null)
{
return json_encode($m->export($fields));
}
Expand Down Expand Up @@ -243,7 +243,7 @@ It's time to create the first Model. Open `src/Model_User.php` which should look
like this::

<?php
class Model_User extends \Atk4\Data\Model
class Model_User extends Model
{
public $table = 'user';

Expand Down Expand Up @@ -350,7 +350,7 @@ Understanding Persistence
To make things simple, console has already created persistence inside variable
`$db`. Load up `console.php` in your editor to look at how persistence is set up::

$app->db = \Atk4\Data\Persistence::connect($dsn, $user, $pass);
$app->db = Persistence::connect($dsn, $user, $pass);

The `$dsn` can also be using the PEAR-style DSN format, such as:
"mysql://user:pass@db/host", in which case you do not need to specify $user and $pass.
Expand All @@ -361,8 +361,8 @@ For some persistence classes, you should use constructor directly::
$array[1] = ['name' => 'John'];
$array[2] = ['name' => 'Peter'];

$db = new \Atk4\Data\Persistence\Array_($array);
$m = new \Atk4\Data\Model($db);
$db = new Persistence\Array_($array);
$m = new Model($db);
$m->addField('name');
$m = $m->load(2);
echo $m->get('name'); // Peter
Expand All @@ -371,7 +371,7 @@ There are several Persistence classes that deal with different data sources.
Lets load up our console and try out a different persistence::

$a = ['user' => [], 'contact_info' => []];
$ar = new \Atk4\Data\Persistence\Array_($a);
$ar = new Persistence\Array_($a);
$m = new Model_User($ar);
$m->set('username', 'test');
$m->set('address_1', 'street');
Expand Down
2 changes: 1 addition & 1 deletion docs/types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Field configuration
===================

Fields can be further configured. For numeric fields it's possible to provide
precision. For instance, when user specifies `type=money` it is represented
precision. For instance, when user specifies `'type' => 'atk4_money'` it is represented
as `['Number', 'precision' => 2, 'prefix' => '€']`

Not only this allows us make a flexible and re-usable functionality for fields,
Expand Down
4 changes: 1 addition & 3 deletions src/Field.php
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,6 @@ public function useAlias(): bool
public function getQueryArguments($operator, $value): array
{
$typecastField = $this;
$allowArray = true;
if (in_array($operator, [
Scope\Condition::OPERATOR_LIKE,
Scope\Condition::OPERATOR_NOT_LIKE,
Expand All @@ -421,12 +420,11 @@ public function getQueryArguments($operator, $value): array
$typecastField = new self(['type' => 'string']);
$typecastField->setOwner(new Model($this->getOwner()->getPersistence(), ['table' => false]));
$typecastField->shortName = $this->shortName;
$allowArray = false;
}

if ($value instanceof Persistence\Array_\Action) { // needed to pass hintable tests
$v = $value;
} elseif (is_array($value) && $allowArray) {
} elseif (is_array($value)) {
$v = array_map(fn ($value) => $typecastField->typecastSaveField($value), $value);
} else {
$v = $typecastField->typecastSaveField($value);
Expand Down
15 changes: 10 additions & 5 deletions src/Model/Scope/Condition.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Atk4\Data\Exception;
use Atk4\Data\Field;
use Atk4\Data\Model;
use Atk4\Data\Persistence;
use Atk4\Data\Persistence\Sql\Expression;
use Atk4\Data\Persistence\Sql\Expressionable;
use Atk4\Data\Persistence\Sql\Sqlite\Expression as SqliteExpression;
Expand Down Expand Up @@ -150,11 +151,13 @@ protected function onChangeModel(): void
{
$model = $this->getModel();
if ($model !== null) {
// if we have a definitive scalar value for a field
// sets it as default value for field and locks it
// if we have a definitive equal condition set the value as default value for field
// new records will automatically get this value assigned for the field
// @todo: consider this when condition is part of OR scope
if ($this->operator === self::OPERATOR_EQUALS && !is_object($this->value) && !is_array($this->value)) {
// TODO: fix when condition is part of OR scope
if ($this->operator === self::OPERATOR_EQUALS && !is_array($this->value)
&& !$this->value instanceof Expressionable
&& !$this->value instanceof Persistence\Array_\Action // needed to pass hintable tests
) {
// key containing '/' means chained references and it is handled in toQueryArguments method
$field = $this->key;
if (is_string($field) && !str_contains($field, '/')) {
Expand All @@ -166,7 +169,9 @@ protected function onChangeModel(): void
// for now, do not set default at least for PK/ID
if ($field instanceof Field && $field->shortName !== $field->getOwner()->idField) {
$field->system = true;
$field->default = $this->value;
$fakePersistence = new Persistence\Array_();
$valueCloned = $fakePersistence->typecastLoadField($field, $fakePersistence->typecastSaveField($field, $this->value));
$field->default = $valueCloned;
}
}
}
Expand Down
6 changes: 5 additions & 1 deletion src/Model/UserActionsTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,11 @@ protected function initUserActions(): void
'fields' => true,
'modifier' => UserAction::MODIFIER_UPDATE,
'appliesTo' => UserAction::APPLIES_TO_SINGLE_RECORD,
'callback' => 'save',
'callback' => function (Model $entity) {
$entity->assertIsLoaded();

return $entity->save();
},
]);

$this->addUserAction('delete', [
Expand Down
4 changes: 2 additions & 2 deletions src/Persistence/Sql.php
Original file line number Diff line number Diff line change
Expand Up @@ -229,10 +229,10 @@ public function initField(Query $query, Field $field): void
*
* @param array<int, string>|null $fields
*/
public function initQueryFields(Model $model, Query $query, $fields = null): void
public function initQueryFields(Model $model, Query $query, array $fields = null): void
{
// init fields
if (is_array($fields)) {
if ($fields !== null) {
// Set of fields is strictly defined for purposes of export,
// so we will ignore even system fields.
foreach ($fields as $fieldName) {
Expand Down
112 changes: 55 additions & 57 deletions src/Persistence/Static_.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,99 +27,97 @@ class Static_ extends Array_
*/
public function __construct(array $data = [])
{
// chomp off first row, we will use it to deduct fields
$row1 = reset($data);

if (!is_array($row1)) {
// convert array of strings into array of hashes
$allKeysInt = true;
foreach ($data as $k => $str) {
$data[$k] = ['name' => $str];

if (!is_int($k)) {
$allKeysInt = false;
}
if (count($data) > 0 && !is_array(reset($data))) {
$dataOrig = $data;
$data = [];
foreach ($dataOrig as $k => $v) {
$data[] = ['id' => $k, 'name' => $v];
}
unset($str);
}

$this->titleFieldForModel = 'name';
$this->fieldsForModel = [
'id' => ['type' => $allKeysInt ? 'integer' : 'string'],
'name' => ['type' => 'string'], // TODO type should be guessed as well
];
// detect types from values
$fieldTypes = [];
foreach ($data as $row) {
foreach ($row as $k => $v) {
if (isset($fieldTypes[$k])) {
continue;
}

parent::__construct($data);
if (is_bool($v)) {
$fieldType = 'boolean';
} elseif (is_int($v)) {
$fieldType = 'integer';
} elseif (is_float($v)) {
$fieldType = 'float';
} elseif ($v instanceof \DateTimeInterface) {
$fieldType = 'datetime';
} elseif (is_array($v)) {
$fieldType = 'json';
} elseif (is_object($v)) {
$fieldType = 'object';
} elseif ($v !== null) {
$fieldType = 'string';
} else {
$fieldType = null;
}

return;
$fieldTypes[$k] = $fieldType;
}
}
foreach ($fieldTypes as $k => $fieldType) {
if ($fieldType === null) {
$fieldTypes[$k] = 'string';
}
}

if (isset($row1['name'])) {
if (isset($fieldTypes['name'])) {
$this->titleFieldForModel = 'name';
} elseif (isset($row1['title'])) {
} elseif (isset($fieldTypes['title'])) {
$this->titleFieldForModel = 'title';
}

$keyOverride = [];
$defTypes = [];
$keyOverride = [];
$mustOverride = false;
foreach ($fieldTypes as $k => $fieldType) {
$defTypes[$k] = ['type' => $fieldType];

foreach ($row1 as $key => $value) {
// id information present, use it instead
if ($key === 'id') {
if ($k === 'id') {
$mustOverride = true;
}

// try to detect type of field by its value
if (is_bool($value)) {
$defTypes[] = ['type' => 'boolean'];
} elseif (is_int($value)) {
$defTypes[] = ['type' => 'integer'];
} elseif (is_float($value)) {
$defTypes[] = ['type' => 'float'];
} elseif ($value instanceof \DateTimeInterface) {
$defTypes[] = ['type' => 'datetime'];
} elseif (is_array($value)) {
$defTypes[] = ['type' => 'json'];
} elseif (is_object($value)) {
$defTypes[] = ['type' => 'object'];
} else {
$defTypes[] = ['type' => 'string'];
}

// if title is not set, use first key
if (!$this->titleFieldForModel) {
if (is_int($key)) {
$keyOverride[] = 'name';
if (is_int($k)) {
$keyOverride[$k] = 'name';
$this->titleFieldForModel = 'name';
$mustOverride = true;

continue;
}

$this->titleFieldForModel = $key;
$this->titleFieldForModel = $k;
}

if (is_int($key)) {
$keyOverride[] = 'field' . $key;
if (is_int($k)) {
$keyOverride[$k] = 'field' . $k;
$mustOverride = true;

continue;
} else {
$keyOverride[$k] = $k;
}

$keyOverride[] = $key;
}

if ($mustOverride) {
$data2 = [];

foreach ($data as $key => $row) {
$dataOrig = $data;
$data = [];
foreach ($dataOrig as $k => $row) {
$row = array_combine($keyOverride, $row);
if (isset($row['id'])) {
$key = $row['id'];
$k = $row['id'];
}
$data2[$key] = $row;
$data[$k] = $row;
}
$data = $data2;
}

$this->fieldsForModel = array_combine($keyOverride, $defTypes);
Expand Down
2 changes: 1 addition & 1 deletion src/Reference/HasMany.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public function getTheirFieldName(Model $theirModel = null): string
// TODO probably remove completely in the future
$ourModel = $this->getOurModel(null);
$theirFieldName = preg_replace('~^.+\.~s', '', $this->getModelTableString($ourModel)) . '_' . $ourModel->idField;
if (!$this->createTheirModel()->hasField($theirFieldName)) {
if (!($theirModel ?? $this->createTheirModel())->hasField($theirFieldName)) {
throw (new Exception('Their model does not contain fallback field'))
->addMoreInfo('their_fallback_field', $theirFieldName);
}
Expand Down
9 changes: 6 additions & 3 deletions src/Schema/Migrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -266,13 +266,16 @@ public function field(string $fieldName, array $options = []): self
return $this;
}

public function id(string $name = 'id'): self
/**
* @param array<string, mixed> $options
*/
public function id(string $name = 'id', array $options = []): self
{
$options = [
$options = array_merge([
'type' => 'integer',
'ref_type' => self::REF_TYPE_PRIMARY,
'nullable' => false,
];
], $options);

$this->field($name, $options);

Expand Down
Loading

0 comments on commit e4cb17b

Please sign in to comment.