Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion formwork/defaults.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@
'setHandlers' => true
],
'fields' => [
'path' => FORMWORK_PATH . 'fields' . DS
'path' => FORMWORK_PATH . 'fields' . DS,
'dynamic' => [
'vars' => [
'file' => FORMWORK_PATH . 'fields' . DS . 'dynamic' . DS . 'vars.php'
]
]
],
'files' => [
'allowedExtensions' => [
Expand Down
21 changes: 21 additions & 0 deletions formwork/fields/dynamic/vars.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

use Formwork\Formwork;
use Formwork\Languages\LanguageCodes;
use Formwork\Panel\Utils\DateFormats;

return [
'formwork' => Formwork::instance(),

'site' => Formwork::instance()->site(),

'dateFormats' => [
'date' => DateFormats::date(),
'hour' => DateFormats::hour(),
'timezones' => DateFormats::timezones()
],

'languages' => [
'names' => LanguageCodes::names()
]
];
9 changes: 9 additions & 0 deletions formwork/src/Exceptions/RecursionException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace Formwork\Exceptions;

use RuntimeException;

class RecursionException extends RuntimeException
{
}
129 changes: 129 additions & 0 deletions formwork/src/Fields/Dynamic/DynamicFieldValue.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<?php

namespace Formwork\Fields\Dynamic;

use Formwork\Exceptions\RecursionException;
use Formwork\Fields\Field;
use Formwork\Formwork;
use Formwork\Interpolator\Interpolator;
use Formwork\Parsers\PHP;

class DynamicFieldValue
{
/**
* Dynamic value computation status
*/
protected bool $computed = false;

/**
* Whether the dynamic value is being computed
*/
protected bool $computing = false;

/**
* Dynamic value key
*/
protected string $key;

/**
* Uncomputed value
*/
protected string $uncomputedValue;

/**
* Field to which the value belongs
*/
protected Field $field;

/**
* Computed value
*/
protected $value;

public function __construct(string $key, string $uncomputedValue, Field $field)
{
$this->key = $key;
$this->uncomputedValue = $uncomputedValue;
$this->field = $field;
}

/**
* Create an instance with an already computed value
* (used by field validation)
*
* @internal
*/
public static function withComputed(string $value, self $dynamic): self
{
$instance = clone $dynamic;
$instance->computed = true;
$instance->value = $value;
return $instance;
}

/**
* Compute dynamic field value
*/
public function compute(): void
{
if ($this->computed) {
return;
}

if ($this->computing) {
throw new RecursionException(sprintf('Recursion in the computation of dynamic property "%s" of field "%s". Trying to compute "%s"', $this->key, $this->field->name(), $this->uncomputedValue));
}

$this->computing = true;

$this->value = Interpolator::interpolate($this->uncomputedValue, array_merge($this->defaults(), ['this' => $this->field]));

$this->computed = true;

$this->computing = false;
}

/**
* Get the key associated to the dynamic value
*/
public function key()
{
return $this->key;
}

/**
* Get the computed value
*/
public function value()
{
if (!$this->computed) {
$this->compute();
}

return $this->value;
}

/**
* Return whether the dynamic value has been computed
*/
public function isComputed(): bool
{
return $this->computed;
}

/**
* Return the field to which the dynamic value belongs
*/
public function field(): Field
{
return $this->field;
}

/**
* Default vars used in the computation
*/
protected function defaults(): array
{
return PHP::parseFile(Formwork::instance()->config()->get('fields.dynamic.vars.file'));
}
}
71 changes: 43 additions & 28 deletions formwork/src/Fields/Field.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
use Formwork\Data\Traits\DataArrayable;
use Formwork\Data\Traits\DataMultipleGetter;
use Formwork\Data\Traits\DataMultipleSetter;
use Formwork\Exceptions\RecursionException;
use Formwork\Fields\Dynamic\DynamicFieldValue;
use Formwork\Fields\Exceptions\ValidationException;
use Formwork\Formwork;
use Formwork\Traits\Methods;
Expand All @@ -27,7 +29,7 @@ class Field implements Arrayable
}
use Methods;

protected const UNTRANSLATABLE_KEYS = ['name', 'type', 'value', 'default', 'translate', 'import'];
protected const UNTRANSLATABLE_KEYS = ['name', 'type', 'value', 'default', 'translate'];

/**
* Field name
Expand All @@ -37,28 +39,29 @@ class Field implements Arrayable
/**
* Parent field collection
*/
protected FieldCollection $parent;
protected ?FieldCollection $parent;

/**
* Field validation status
*/
protected bool $validated = false;

/**
* Whether the field is being validated
*/
protected bool $validating = false;

/**
* Create a new Field instance
*/
public function __construct(string $name, array $data = [], FieldCollection $parent)
public function __construct(string $name, array $data = [], ?FieldCollection $parent = null)
{
$this->name = $name;

$this->parent = $parent;

$this->setMultiple($data);

if ($this->has('import')) {
$this->importData();
}

if ($this->has('fields')) {
throw new UnexpectedValueException('Fields may not have other fields inside');
}
Expand Down Expand Up @@ -86,7 +89,7 @@ public function name(): string
/**
* Return the parent field collection
*/
public function parent(): FieldCollection
public function parent(): ?FieldCollection
{
return $this->parent;
}
Expand Down Expand Up @@ -184,19 +187,38 @@ public function isHidden(): bool
*/
public function validate(): static
{
if ($this->validating) {
throw new RecursionException(sprintf('Recursion in the validation of field "%s" of type "%s"', $this->name(), $this->type()));
}

$this->validating = true;

$value = $this->value();

$dynamic = $value instanceof DynamicFieldValue ? $value : null;

if ($dynamic !== null) {
$value = $value->value();
}

if ($this->isRequired() && Constraint::isEmpty($value)) {
throw new ValidationException(sprintf('Required field "%s" of type "%s" cannot be empty', $this->name(), $this->type()));
}

if ($this->hasMethod('validate')) {
$value = $this->callMethod('validate', [$value]);

if ($dynamic !== null) {
$value = DynamicFieldValue::withComputed($value, $dynamic);
}

$this->set('value', $value);
}

$this->validated = true;

$this->validating = false;

return $this;
}

Expand Down Expand Up @@ -233,6 +255,14 @@ public function get(string $key, $default = null)
{
$value = $this->baseGet($key, $default);

if ($value instanceof DynamicFieldValue) {
if ($key === 'value' && !$value->isComputed()) {
$this->validated = false;
}

$value = $value->value();
}

if ($this->isTranslatable($key)) {
$value = $this->translate($value);
}
Expand All @@ -242,6 +272,11 @@ public function get(string $key, $default = null)

public function set(string $key, $value): void
{
if (Str::endsWith($key, '@')) {
$key = Str::beforeLast($key, '@');
$value = new DynamicFieldValue($key, $value, $this);
}

if ($key === 'value') {
$this->validated = false;
}
Expand Down Expand Up @@ -270,26 +305,6 @@ protected function loadMethods(): void
}
}

/**
* Import data helper
*/
protected function importData(): void
{
foreach ((array) $this->data['import'] as $key => $value) {
if ($key === 'import') {
throw new UnexpectedValueException('Invalid key for import');
}

$callback = explode('::', $value, 2);

if (!is_callable($callback)) {
throw new UnexpectedValueException(sprintf('Invalid import callback "%s"', $value));
}

$this->data[$key] = $callback();
}
}

/**
* Return whether a field key is translatable
*/
Expand Down
9 changes: 9 additions & 0 deletions formwork/src/Interpolator/Errors/SyntaxError.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace Formwork\Interpolator\Errors;

use Error;

class SyntaxError extends Error
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace Formwork\Interpolator\Exceptions;

use RuntimeException;

class InterpolationException extends RuntimeException
{
}
15 changes: 15 additions & 0 deletions formwork/src/Interpolator/Interpolator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Formwork\Interpolator;

class Interpolator
{
/**
* Interpolate the given string
*/
public static function interpolate(string $string, array $vars)
{
$interpolator = new NodeInterpolator(Parser::parseTokenStream(Tokenizer::tokenizeString($string)), $vars);
return $interpolator->interpolate();
}
}
Loading