diff --git a/skeleton/EntityGenerate/DeleteFormContext.php b/skeleton/EntityGenerate/DeleteFormContext.php index 2a5df04..27baa44 100644 --- a/skeleton/EntityGenerate/DeleteFormContext.php +++ b/skeleton/EntityGenerate/DeleteFormContext.php @@ -13,7 +13,7 @@ class %DELETE_FORM_CONTEXT_CLASSNAME% extends \Lean\Api\Lib\FormContext public function valid(? %ENTITY_NS%\%CLASSNAME% $entity = null) : bool { - if ( ! $this->getEntity()->isLoaded() ) { + if ( $entity === null ) { $this->pushErrorMessage('lean.api.form.delete.error.entity'); } diff --git a/src/Attribute/ContextField.php b/src/Attribute/ContextField.php index 7305f86..be963af 100644 --- a/src/Attribute/ContextField.php +++ b/src/Attribute/ContextField.php @@ -17,8 +17,8 @@ class ContextField public ?int $maxLength = null, public ?string $regexFormat = null, public ?string $example = null, - public bool $hidden = false, public mixed $default = null, + public bool $hidden = false, ) {} public function assertValueSpecs(mixed $value, bool $isNew) : void @@ -39,10 +39,7 @@ class ContextField } if ($this->regexFormat) { - if (! is_string($value)) { - throw new RegexFormatException("Property type must be a string if a regexFormat is provided."); - } - elseif (! preg_match($this->regexFormat, $value)) { + if (! preg_match($this->regexFormat, $value)) { throw new RegexFormatException("Regex format {$this->regexFormat} is not valid for value {$value}."); } } diff --git a/src/Entity/Error.php b/src/Entity/Error.php deleted file mode 100644 index 2dc2468..0000000 --- a/src/Entity/Error.php +++ /dev/null @@ -1,79 +0,0 @@ -message) > 255 ? mb_substr($this->message, 0, 255) . "[...]" : $this->message; - } - - public static function searchRequest(...$arguments) : SearchRequestInterface - { - return new #[SearchRequest\Attribute\SearchRequestParameter(Error::class)] class(...$arguments) extends SearchRequest\SearchRequest { - #[SearchRequest\Attribute\SearchWhere(source: SearchRequest\Attribute\PropertyValueSource::RequestAttribute)] - public int $id; - - #[SearchWhere(method: SearchMethodEnum::Like)] - public string $message; - - #[SearchOrderBy(parameter: "created_at",)] - public string $createdAt = SearchOrderBy::DESCENDING; - }; - } -} \ No newline at end of file diff --git a/src/Entity/Field/Date.php b/src/Entity/Field/Date.php new file mode 100644 index 0000000..7064d3f --- /dev/null +++ b/src/Entity/Field/Date.php @@ -0,0 +1,8 @@ +getTypes(); + if ($property->value ?? false) { + $default = $property->value instanceof \BackedEnum ? $property->value->value : $property->value; + } + else { + $default = $field->object->attributes['default'] ?? null; + } + $fields[] = [ 'name' => $property->name, 'description' => $entityField->object->description ?? "", @@ -45,6 +52,7 @@ class EntityDescriptor 'allowNulls' => $property->allowsNull(), 'length' => $field->object->length ?? null, 'readonly' => $field->object->readonly, + 'default' => $default, ]; } } @@ -57,8 +65,15 @@ class EntityDescriptor $field = $property->getAttribute(SearchParameter::class); if ($field) { + $possibleValues = null; $types = $property->getTypes(); + foreach($types as $type) { + if (! $type->builtIn && enum_exists($type->type)) { + $possibleValues = implode(', ', array_map(fn($e) => $e->value, $type->type::cases())); + } + } + $searchFields[] = [ 'name' => $property->name, 'description' => $field->object->description, @@ -66,6 +81,7 @@ class EntityDescriptor 'tag' => $field->tag, 'type' => implode(' | ', array_map(fn($e) => $e->type, $types)), 'allowNulls' => $property->allowsNull(), + 'possibleValues' => $possibleValues, 'default' => $this->displayValue($property->value ?? "") ]; } diff --git a/src/Form/Delete.php b/src/Form/Delete.php index 39af55c..8163b21 100644 --- a/src/Form/Delete.php +++ b/src/Form/Delete.php @@ -8,13 +8,26 @@ use Ulmus\Entity\EntityInterface; abstract class Delete extends Form implements \Picea\Ui\Method\FormInterface { - public function execute(FormContextInterface $context) : void + public function validate(FormContextInterface $context) : bool + { + if ( ! $this->getEntity()->isLoaded() ) { + $context->pushMessage($this->message::generateError( + $this->lang('lean.api.form.delete.error.entity') + )); + } + + return $context->valid($this->getEntity()->isLoaded() ? $this->getEntity() : null); + } + + public function execute(FormContextInterface $context) : mixed { try { if ( $this->getEntity()::repository()->destroy($this->getEntity()) ) { $context->pushMessage($this->message::generateSuccess( $this->lang('lean.api.form.delete.success.save') )); + + return true; } else { throw new \InvalidArgumentException($this->lang('lean.api.form.delete.error.save')); diff --git a/src/Form/Save.php b/src/Form/Save.php index dff63ba..f7a0415 100644 --- a/src/Form/Save.php +++ b/src/Form/Save.php @@ -3,7 +3,7 @@ namespace Lean\Api\Form; use CSLSJ\Lean\Form\Session\EmailContext; -use Picea\Ui\Method\{ FormContextInterface, }; +use Picea\Ui\Method\{FormContext, FormContextInterface}; use Lean\Api\Attribute\ContextField; use Lean\Api\Exception\MandatoryFieldException; @@ -19,6 +19,8 @@ abstract class Save extends Form implements \Picea\Ui\Method\FormInterface { protected array $reflectedProperties; + protected string $contextClass = FormContext::class; + public function initialize(FormContextInterface $context) : void { if ( ! $this->getEntity()->isLoaded() ) { @@ -46,12 +48,14 @@ abstract class Save extends Form implements \Picea\Ui\Method\FormInterface { if ($entity->isLoaded()) { if (property_exists($entity, 'updatedAt') && $entity->repository()->generateDatasetDiff($entity) ) { - $entity->updatedAt = new Datetime(); + $cls = $entity::resolveEntity()->field('updatedAt')->type->type; + $entity->updatedAt = new $cls(); } } else { if (property_exists($entity, 'createdAt') && empty($entity->createdAt)) { - $entity->createdAt = new Datetime(); + $cls = $entity::resolveEntity()->field('createdAt')->type->type; + $entity->createdAt = new $cls(); } } @@ -82,7 +86,7 @@ abstract class Save extends Form implements \Picea\Ui\Method\FormInterface { if ($attribute) { try { - $attribute->object->assertValueSpecs($context->$name ?? null, $this->getEntity()->isLoaded()); + $attribute->object->assertValueSpecs($context->$name ?? null, ! $this->getEntity()->isLoaded()); } catch (MandatoryFieldException $e) { throw new MandatoryFieldException("An error occured with field '$name': " . $e->getMessage()); } diff --git a/src/FormDescriptor.php b/src/FormDescriptor.php index fad6fda..409f702 100644 --- a/src/FormDescriptor.php +++ b/src/FormDescriptor.php @@ -28,6 +28,7 @@ class FormDescriptor $fields[] = [ 'name' => $property->name, + 'mandatory' => $field->object->mandatory, 'description' => $field->object->description, 'type' => $field->object->type ?? $types, 'allowNulls' => $property->allowsNull(), @@ -35,6 +36,7 @@ class FormDescriptor 'minLength' => $field->object->minLength ?? null, 'maxLength' => $field->object->maxLength ?? null, 'example' => $field->object->example ?? null, + 'default' => isset($field->object->default) ? json_encode($field->object->default) : null, 'values' => $this->generateExampleValues($types), ]; } diff --git a/src/LeanApiTrait.php b/src/LeanApiTrait.php index 80b8299..ea79fde 100644 --- a/src/LeanApiTrait.php +++ b/src/LeanApiTrait.php @@ -80,29 +80,31 @@ trait LeanApiTrait $entity = $entityType::repository()->filterServerRequest($search)->loadOne() ?? false; if (! $entity ) { - throw new \InvalidArgumentException(sprintf("L'entré pour l'entité demandé (%s) est introuvable avec le ou les arguments fournis '%s'", $entityType, json_encode($request->getAttributes()))); + throw new \InvalidArgumentException(sprintf("L'entré pour l'entité demandé (%s) est introuvable avec le ou les arguments fournis '%s'", $entityType, json_encode($request->getAttributes(), \JSON_PARTIAL_OUTPUT_ON_ERROR))); } - return $request->withAttribute("lean.searchRequest", - [ - $resultKey ?: $entityType => new class($search, $entity) implements ApiSearchRequestInterface { - public function __construct( - public readonly SearchRequestInterface $search, - public readonly EntityInterface $entity, - ) {} + if ($request->getAttribute("lean.searchRequest") === null) { + $request = $request->withAttribute("lean.searchRequest", $this->searchEntityResult()); + } - public function getSearch(): SearchRequestInterface - { - return $this->search; - } + $request->getAttribute("lean.searchRequest")[$resultKey ?: $entityType] = new class($search, $entity) implements ApiSearchRequestInterface { + public function __construct( + public readonly SearchRequestInterface $search, + public readonly EntityInterface $entity, + ) {} - public function getResult(): EntityCollection|EntityInterface - { - return $this->entity; - } - } - ] + $request->getAttribute("lean.searchRequest", []) - ); + public function getSearch(): SearchRequestInterface + { + return $this->search; + } + + public function getResult(): EntityCollection|EntityInterface + { + return $this->entity; + } + }; + + return $request; } #[Ignore] @@ -115,25 +117,50 @@ trait LeanApiTrait throw new \InvalidArgumentException(sprintf("L'entré pour l'entité demandé (%s) est introuvable avec le ou les arguments fournis '%s'", $entityType, json_encode($request->getAttributes()))); } - return $request->withAttribute("lean.searchRequest", - [ - $resultKey ?: $entityType => new class($search, $entity) implements ApiSearchRequestInterface { - public function __construct( - public readonly SearchRequestInterface $search, - public readonly EntityCollection $collection, - ) {} + if ($request->getAttribute("lean.searchRequest") === null) { + $request = $request->withAttribute("lean.searchRequest", $this->searchEntityResult()); + } - public function getSearch(): SearchRequestInterface - { - return $this->search; - } + $request->getAttribute("lean.searchRequest")[$resultKey ?: $entityType] = new class($search, $entity) implements ApiSearchRequestInterface { + public function __construct( + public readonly SearchRequestInterface $search, + public readonly EntityCollection $collection, + ) {} - public function getResult(): EntityCollection|EntityInterface - { - return $this->collection; - } + public function getSearch(): SearchRequestInterface + { + return $this->search; + } + + public function getResult(): EntityCollection|EntityInterface + { + return $this->collection; + } + }; + + return $request; + } + + #[Ignore] + protected function searchEntityResult() :\ArrayObject { + return new class()extends \ArrayObject implements ApiSearchRequestInterface { + + public function getSearch(): SearchRequestInterface + { + if ($this->count() !== 1) { + throw new \LogicException("Search results contains more than one results. Impossible to continue without precising which one you needs."); } - ] + $request->getAttribute("lean.searchRequest", []) - ); + + return $this[array_key_last($this->getArrayCopy())]->getSearch(); + } + + public function getResult() : EntityInterface|EntityCollection { + if ($this->count() !== 1) { + throw new \LogicException("Search results contains more than one results. Impossible to continue without precising which one you needs."); + } + + return $this[array_key_last($this->getArrayCopy())]->getResult(); + } + }; } } \ No newline at end of file diff --git a/src/Middleware/ApiRenderer.php b/src/Middleware/ApiRenderer.php index e6c3ef2..90b9857 100644 --- a/src/Middleware/ApiRenderer.php +++ b/src/Middleware/ApiRenderer.php @@ -24,7 +24,7 @@ class ApiRenderer implements MiddlewareInterface { $response = $handler->handle($request); } catch(\Throwable $ex) { - # $errorId = $this->saveError($request, $ex)->id; + if (static::awaitingJson($request)) { return HttpFactory::createJsonResponse([ diff --git a/view/lean-api/entity_descriptor.phtml b/view/lean-api/entity_descriptor.phtml index 60331b6..8144456 100644 --- a/view/lean-api/entity_descriptor.phtml +++ b/view/lean-api/entity_descriptor.phtml @@ -10,30 +10,39 @@