Skip to content

Commit

Permalink
Merge branch 'release/1.1.13'
Browse files Browse the repository at this point in the history
  • Loading branch information
romaninsh committed Apr 10, 2017
2 parents 0a924af + 0bef7ef commit 751739e
Show file tree
Hide file tree
Showing 12 changed files with 418 additions and 161 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
## 1.1.13

- Add $required property for fields #225
- Fix ID-less iteration of Arrays #222
- When persistence lacks field data, previous record value should be reset #221
- Fix caption for hasOne(.. ['caption'=>'blah']). #226
- Added release script

## 1.1.12

- hasMany->addField() now correctly pass additional options to expression field.
- Update README.

## 1.1.11

Added support for OR conditions.
Expand Down
175 changes: 118 additions & 57 deletions README.md

Large diffs are not rendered by default.

224 changes: 140 additions & 84 deletions src/Field.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,24 @@ class Field
public $ui = [];

/**
* Is field mandatory? By default fields are not mandatory.
* Mandatory field must not be null. The value must be set, even if
* it's an empty value.
*
* Can contain error message for UI.
*
* @var bool|string
*/
public $mandatory = false;

/**
* Required field must have non-empty value. A null value is considered empty too.
*
* Can contain error message for UI.
*
* @var bool|string
*/
public $required = false;

/**
* Should we use typecasting when saving/loading data to/from persistence.
*
Expand Down Expand Up @@ -194,7 +205,9 @@ public function __construct($defaults = [])

/**
* Depending on the type of a current field, this will perform
* some normalization for strict types.
* some normalization for strict types. This method must also make
* sure that $f->required is respected when setting the value, e.g.
* you can't set value to '' if type=string and required=true.
*
* @param mixed $value
*
Expand All @@ -204,96 +217,139 @@ public function __construct($defaults = [])
*/
public function normalize($value)
{
if (!$this->owner->strict_types) {
return $value;
}
if ($value === null) {
return;
}
$f = $this;
try {
if (!$this->owner->strict_types) {
return $value;
}

// only string type fields can use empty string as legit value, for all
// other field types empty value is the same as no-value, nothing or null
if ($f->type && $f->type != 'string' && $value === '') {
return;
}
if ($value === null) {
if ($this->required) {
throw new Exception('may not be null');
}

switch ($f->type) {
case 'string':
if (!is_scalar($value)) {
throw new Exception('Field value must be a string');
return;
}
$value = trim($value);
break;
case 'integer':
if (!is_numeric($value)) {
throw new Exception('Field value must be an integer');
}
$value = (int) $value;
break;
case 'float':
if (!is_numeric($value)) {
throw new Exception('Field value must be a float');
}
$value = (float) $value;
break;
case 'money':
if (!is_numeric($value)) {
throw new Exception('Field value must be numeric');

$f = $this;

// only string type fields can use empty string as legit value, for all
// other field types empty value is the same as no-value, nothing or null
if ($f->type && $f->type != 'string' && $value === '') {
if ($this->required && empty($value)) {
throw new Exception('may not be a empty');
}

return;
}
$value = round($value, 4);
break;
case 'boolean':
if (is_bool($value)) {

switch ($f->type) {
case null: // loose comparison, but is OK here
if ($this->required && empty($value)) {
throw new Exception('must not be empty');
}
break;
}
if (isset($f->enum) && is_array($f->enum)) {
if (isset($f->enum[0]) && $value === $f->enum[0]) {
$value = false;
} elseif (isset($f->enum[1]) && $value === $f->enum[1]) {
$value = true;
case 'string':
if (!is_scalar($value)) {
throw new Exception('must be a string');
}
} elseif (is_numeric($value)) {
$value = (bool) $value;
}
if (!is_bool($value)) {
throw new Exception('Field value must be a boolean');
}
break;
case 'date':
case 'datetime':
case 'time':
$class = isset($f->dateTimeClass) ? $f->dateTimeClass : 'DateTime';

if (is_numeric($value)) {
$value = new $class('@'.$value);
} elseif (is_string($value)) {
$value = new $class($value);
} elseif (!$value instanceof $class) {
throw new Exception(['Field value must be a '.$f->type, 'class' => $class, 'value class' => get_class($value)]);
}
break;
case 'array':
if (!is_array($value)) {
throw new Exception('Field value must be a array');
}
break;
case 'object':
if (!is_object($value)) {
throw new Exception('Field value must be a object');
$value = trim($value);
if ($this->required && empty($value)) {
throw new Exception('must not be empty');
}
break;
case 'integer':
// we clear out thousand separator, but will change to
// http://php.net/manual/en/numberformatter.parse.php
// in the future with the introduction of locale
$value = preg_replace('/[^0-9.-]/', '', $value);
if (!is_numeric($value)) {
throw new Exception('must be numeric');
}
$value = (int) $value;
if ($this->required && empty($value)) {
throw new Exception('may not be a zero');
}
break;
case 'float':
$value = str_replace(',', '', $value);
if (!is_numeric($value)) {
throw new Exception('must be numeric');
}
$value = (float) $value;
if ($this->required && empty($value)) {
throw new Exception('may not be a zero');
}
break;
case 'money':
$value = preg_replace('/[^0-9.-]/', '', $value);
if (!is_numeric($value)) {
throw new Exception('must be numeric');
}
$value = round($value, 4);
if ($this->required && empty($value)) {
throw new Exception('may not be a zero');
}
break;
case 'boolean':
if (is_bool($value)) {
break;
}
if (isset($f->enum) && is_array($f->enum)) {
if (isset($f->enum[0]) && $value === $f->enum[0]) {
$value = false;
} elseif (isset($f->enum[1]) && $value === $f->enum[1]) {
$value = true;
}
} elseif (is_numeric($value)) {
$value = (bool) $value;
}
if (!is_bool($value)) {
throw new Exception('must be a boolean');
}
if ($this->required && empty($value)) {
throw new Exception('must be selected');
}
break;
case 'date':
case 'datetime':
case 'time':

// we allow http://php.net/manual/en/datetime.formats.relative.php
$class = isset($f->dateTimeClass) ? $f->dateTimeClass : 'DateTime';

if (is_numeric($value)) {
$value = new $class('@'.$value);
} elseif (is_string($value)) {
$value = new $class($value);
} elseif (!$value instanceof $class) {
throw new Exception(['must be a '.$f->type, 'class' => $class, 'value class' => get_class($value)]);
}
break;
case 'array':
if (!is_array($value)) {
throw new Exception('must be an array');
}
break;
case 'object':
if (!is_object($value)) {
throw new Exception('must be an object');
}
break;
case 'int':
case 'str':
case 'bool':
throw new Exception([
'Use of obsolete field type abbreviation. Use "integer", "string", "boolean" etc.',
'type' => $f->type,
]);
break;
}
break;
case 'int':
case 'str':
case 'bool':
throw new Exception([
'Use of obsolete field type abbreviation. Use "integer", "string", "boolean" etc.',
'type' => $f->type,
]);
break;
}

return $value;
return $value;
} catch (Exception $e) {
$e->addMoreInfo('field', $this);
throw $e;
}
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -1397,7 +1397,7 @@ public function getIterator()
foreach ($this->persistence->prepareIterator($this) as $data) {
$this->data = $this->persistence->typecastLoadRow($this, $data);
if ($this->id_field) {
$this->id = $data[$this->id_field];
$this->id = isset($data[$this->id_field]) ? $data[$this->id_field] : null;
}
$this->hook('afterLoad');
yield $this->id => $this;
Expand Down
4 changes: 1 addition & 3 deletions src/Persistence.php
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ public function typecastLoadRow(Model $m, $row)

// ignore null values
if ($value === null) {
$result[$key] = $value;
continue;
}

Expand Down Expand Up @@ -260,9 +261,6 @@ public function typecastSaveField(Field $f, $value)
return $t($value, $f, $this);
}

// normalize value
$value = $f->normalize($value);

// we respect null values
if ($value === null) {
return;
Expand Down
2 changes: 1 addition & 1 deletion src/Persistence_SQL.php
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@ public function _typecastLoadField(Field $f, $value)
$v = new $dt_class('@'.$v);
} elseif (is_string($v)) {
// ! symbol in date format is essential here to remove time part of DateTime - don't remove, this is not a bug
$format = ['date' => '+!Y-m-d', 'datetime' => 'Y-m-d H:i:s', 'time' => 'H:i:s'];
$format = ['date' => '+!Y-m-d', 'datetime' => '+!Y-m-d H:i:s', 'time' => '+!H:i:s'];
$format = $f->persist_format ?: $format[$f->type];

// datetime only - set from persisting timezone
Expand Down
9 changes: 6 additions & 3 deletions src/Reference_Many.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,12 @@ public function addField($n, $defaults = [])

$field = isset($defaults['field']) ? $defaults['field'] : $n;

$e = $this->owner->addExpression($n, function () use ($defaults, $field) {
return $this->refLink()->action('fx0', [$defaults['aggregate'], $field]);
});
$e = $this->owner->addExpression($n, array_merge([
function () use ($defaults, $field) {
return $this->refLink()->action('fx0', [$defaults['aggregate'], $field]);
}, ],
$defaults
));

if (isset($defaults['type'])) {
$e->type = $defaults['type'];
Expand Down
10 changes: 10 additions & 0 deletions src/Reference_One.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ class Reference_One extends Reference
*/
public $read_only = false;

/**
* Defines a label to go along with this field. Use getCaption() which
* will always return meaningfull label (even if caption is null). Set
* this property to any string.
*
* @var string
*/
public $caption = null;

/**
* Array with UI flags like editable, visible and hidden.
*
Expand Down Expand Up @@ -151,6 +160,7 @@ public function init()
'default' => $this->default,
'never_persist' => $this->never_persist,
'read_only' => $this->read_only,
'caption' => $this->caption,
'ui' => $this->ui,
'mandatory' => $this->mandatory,
'typecast' => $this->typecast,
Expand Down

0 comments on commit 751739e

Please sign in to comment.