- Big WIP on forms component
This commit is contained in:
parent
c0b0681fee
commit
3d50cfaccb
@ -3,6 +3,7 @@
|
||||
namespace Lean\Api\ApplicationStrategy;
|
||||
|
||||
use DI\Attribute\Inject;
|
||||
use Lean\Api\Middleware\ApiRenderer;
|
||||
use Lean\Factory\HttpFactoryInterface;
|
||||
use Picea\Picea;
|
||||
use Psr\Container\ContainerInterface;
|
||||
@ -15,14 +16,14 @@ class NotFoundDecorator extends \Lean\ApplicationStrategy\NotFoundDecorator
|
||||
{
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
if (php_sapi_name() !== 'cli' and ! defined('STDIN')) {
|
||||
return $this->throw404($request);
|
||||
if (php_sapi_name() !== 'cli' and ! defined('STDIN') and ApiRenderer::awaitingJson($request)) {
|
||||
return $this->throw404api($request);
|
||||
}
|
||||
|
||||
return parent::process($request, $handler);
|
||||
}
|
||||
|
||||
public function throw404(ServerRequestInterface $request) : ResponseInterface
|
||||
public function throw404api(ServerRequestInterface $request) : ResponseInterface
|
||||
{
|
||||
return $this->checkAssetTrigger($request) ?: $this->httpFactory->createJsonResponse([
|
||||
'status' => 'error',
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
|
||||
namespace Lean\Api\Attribute;
|
||||
|
||||
use Lean\Api\Exception\MandatoryFieldException;
|
||||
use Lean\Api\Exception\MaximumLengthException;
|
||||
use Lean\Api\Exception\MinimumLengthException;
|
||||
use Lean\Api\Exception\RegexFormatException;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_CLASS)]
|
||||
class ContextField
|
||||
{
|
||||
@ -13,5 +18,34 @@ class ContextField
|
||||
public ?string $regexFormat = null,
|
||||
public ?string $example = null,
|
||||
public bool $hidden = false,
|
||||
public mixed $default = false,
|
||||
) {}
|
||||
|
||||
public function assertValueSpecs(mixed $value, bool $isNew) : void
|
||||
{
|
||||
if ($isNew && ( $this->mandatory && $value === null )) {
|
||||
throw new MandatoryFieldException("Mandatory field must be provided a value.");
|
||||
}
|
||||
elseif ($value !== null) {
|
||||
if ($this->minLength || $this->maxLength) {
|
||||
if (is_string($value)) {
|
||||
if (mb_strlen($value) < $this->minLength) {
|
||||
throw new MinimumLengthException("Minimum {$this->minLength} string length is required.");
|
||||
}
|
||||
elseif (mb_strlen($value) > $this->maxLength) {
|
||||
throw new MaximumLengthException("Maximum {$this->maxLength} string length has been exceeded.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)) {
|
||||
throw new RegexFormatException("Regex format {$this->regexFormat} is not valid for value {$value}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
5
src/Exception/MandatoryFieldException.php
Normal file
5
src/Exception/MandatoryFieldException.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace Lean\Api\Exception;
|
||||
|
||||
class MandatoryFieldException extends \InvalidArgumentException {}
|
||||
5
src/Exception/MaximumLengthException.php
Normal file
5
src/Exception/MaximumLengthException.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace Lean\Api\Exception;
|
||||
|
||||
class MaximumLengthException extends \InvalidArgumentException {}
|
||||
5
src/Exception/MinimumLengthException.php
Normal file
5
src/Exception/MinimumLengthException.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace Lean\Api\Exception;
|
||||
|
||||
class MinimumLengthException extends \InvalidArgumentException {}
|
||||
5
src/Exception/RegexFormatException.php
Normal file
5
src/Exception/RegexFormatException.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace Lean\Api\Exception;
|
||||
|
||||
class RegexFormatException extends \InvalidArgumentException {}
|
||||
@ -6,12 +6,11 @@ use CSSLSJ\ExamenFga\Api\{Lib};
|
||||
use Picea\Ui\Method\{FormContextInterface};
|
||||
use Ulmus\Entity\EntityInterface;
|
||||
|
||||
abstract class Delete implements \Picea\Ui\Method\FormInterface {
|
||||
use FormTrait;
|
||||
abstract class Delete extends Form implements \Picea\Ui\Method\FormInterface {
|
||||
|
||||
public function validate(FormContextInterface $context) : bool
|
||||
{
|
||||
if ( ! $this->entity->isLoaded() ) {
|
||||
if ( ! $this->getEntity()->isLoaded() ) {
|
||||
$context->pushMessage($this->message::generateError(
|
||||
$this->lang('lean.api.form.delete.error.entity')
|
||||
));
|
||||
|
||||
@ -8,7 +8,7 @@ use Picea\Ui\Method\FormContextInterface;
|
||||
use Ulmus\Entity\EntityInterface;
|
||||
use Ulmus\EntityCollection;
|
||||
|
||||
trait FormTrait
|
||||
abstract class Form
|
||||
{
|
||||
public function __construct(
|
||||
protected LanguageHandler $languageHandler,
|
||||
@ -5,20 +5,38 @@ namespace Lean\Api\Form;
|
||||
use CSLSJ\Lean\Form\Session\EmailContext;
|
||||
use Picea\Ui\Method\{ FormContextInterface, };
|
||||
|
||||
use Lean\Api\Attribute\ContextField;
|
||||
use Lean\Api\Exception\MandatoryFieldException;
|
||||
use Notes\ObjectReflection;
|
||||
use Ulmus\Attribute\Property\Field;
|
||||
use Lean\Api\Attribute\EntityField;
|
||||
|
||||
use Ulmus\Entity\EntityInterface;
|
||||
use Ulmus\Entity\Field\Datetime;
|
||||
|
||||
abstract class Save implements \Picea\Ui\Method\FormInterface {
|
||||
use FormTrait;
|
||||
abstract class Save extends Form implements \Picea\Ui\Method\FormInterface {
|
||||
|
||||
protected string $contextClass;
|
||||
|
||||
public function initialize(FormContextInterface $context) : void
|
||||
{
|
||||
if ( ! $this->getEntity()->isLoaded() || $context->formSent() ) {
|
||||
if (method_exists($context, 'initializeEntity')) {
|
||||
$context->initializeEntity($this->getEntity());
|
||||
}
|
||||
else {
|
||||
$this->assignContextToEntity($context);
|
||||
}
|
||||
}
|
||||
|
||||
parent::initialize($context);
|
||||
}
|
||||
|
||||
public function validate(FormContextInterface $context) : bool
|
||||
{
|
||||
# Context is validating inputs
|
||||
# Context validates ContextField attributes containing properties on empty entity
|
||||
$this->validateContextFields($context);
|
||||
|
||||
return $context->valid($this->getEntity()->isLoaded() ? $this->getEntity() : null);
|
||||
}
|
||||
|
||||
@ -61,13 +79,30 @@ abstract class Save implements \Picea\Ui\Method\FormInterface {
|
||||
return $saved;
|
||||
}
|
||||
|
||||
protected function validateContextFields(FormContextInterface $context) : void
|
||||
{
|
||||
foreach (ObjectReflection::fromClass($context::class)->reflectProperties(\ReflectionProperty::IS_PUBLIC) as $name => $property) {
|
||||
$attribute = $property->getAttribute(ContextField::class);
|
||||
|
||||
if ($attribute) {
|
||||
try {
|
||||
$attribute->object->assertValueSpecs($context->$name ?? null, $this->getEntity()->isLoaded());
|
||||
} catch (MandatoryFieldException $e) {
|
||||
throw new MandatoryFieldException("An error occured with field '$name': " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function assignContextToEntity(FormContextInterface $context) : void
|
||||
{
|
||||
$entity = $this->getEntity();
|
||||
|
||||
foreach($entity::resolveEntity()->fieldList() as $key => $property) {
|
||||
$field = $property->getAttribute(Field::class)->object;
|
||||
|
||||
if (! $field->readonly || ! $entity->isLoaded()) {
|
||||
|
||||
$apiField = $property->getAttribute(EntityField::class)->object ?? null;
|
||||
|
||||
if ($apiField) {
|
||||
|
||||
@ -74,7 +74,7 @@ trait LeanApiTrait
|
||||
}
|
||||
|
||||
#[Ignore]
|
||||
protected function searchEntityFromRequest(ServerRequestInterface $request, string $entityType, ? string $resultKey = null) : ServerRequestInterface
|
||||
protected function searchEntityFromRequest(ServerRequestInterface $request, string $entityType, ? string $resultKey = null) : ServerRequestInterface
|
||||
{
|
||||
$search = $entityType::searchRequest()->fromRequest($request);
|
||||
$entity = $entityType::repository()->filterServerRequest($search)->loadOne() ?? false;
|
||||
|
||||
@ -5,6 +5,7 @@ namespace Lean\Api\Lib;
|
||||
use Lean\Api\Factory\MessageFactoryInterface;
|
||||
use Lean\LanguageHandler;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Ulmus\Entity\EntityInterface;
|
||||
|
||||
class FormContext extends \Picea\Ui\Method\FormContext {
|
||||
|
||||
@ -18,6 +19,8 @@ class FormContext extends \Picea\Ui\Method\FormContext {
|
||||
parent::__construct($request, $formName);
|
||||
}
|
||||
|
||||
# public function initializeEntity(EntityInterface $entity) : void {}
|
||||
|
||||
public function lang(string $key, array $variables = [])
|
||||
{
|
||||
return $this->languageHandler->languageFromKey($key, $variables);
|
||||
|
||||
@ -26,7 +26,7 @@ class ApiRenderer implements MiddlewareInterface {
|
||||
catch(\Throwable $ex) {
|
||||
# $errorId = $this->saveError($request, $ex)->id;
|
||||
|
||||
if ($this->awaitingJson($request)) {
|
||||
if (static::awaitingJson($request)) {
|
||||
return HttpFactory::createJsonResponse([
|
||||
'status' => 'failed',
|
||||
'ts' => time(),
|
||||
@ -80,7 +80,7 @@ class ApiRenderer implements MiddlewareInterface {
|
||||
return $entity;
|
||||
}
|
||||
|
||||
protected function awaitingJson(ServerRequestInterface $request) : bool
|
||||
public static function awaitingJson(ServerRequestInterface $request) : bool
|
||||
{
|
||||
return str_contains(strtolower($request->getHeaderLine('content-type')), 'json');
|
||||
}
|
||||
|
||||
@ -35,13 +35,13 @@
|
||||
</div>
|
||||
|
||||
<div class="request-response hide">
|
||||
<hr>
|
||||
<pre id="response" class="code" ace-theme="ace/theme/cloud9_day"></pre>
|
||||
|
||||
<div class="response-head">
|
||||
<span class="response-code"></span>
|
||||
<span class="response-message"></span>
|
||||
</div>
|
||||
<hr>
|
||||
<pre id="response" class="code" ace-theme="ace/theme/cloud9_day"></pre>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -55,8 +55,8 @@
|
||||
.request-head .url button {font-size: 80%;width: 80px;border-radius:0;border: 1px solid #ccc;height:26px;padding: 0;background:#f9f9f9;color: #6f6f6f;cursor: pointer;}
|
||||
.request-head .url button:active {filter:contrast(85%);}
|
||||
|
||||
.response-head {display: flex;border:1px solid #9f9f9f;margin-bottom: 5px;}
|
||||
.response-head .response-code {background:#dfdfdf;padding:0 5px;font-weight: bold;line-height: 29px;font-size: 80%;}
|
||||
.response-head {display: flex;border:1px solid #ececec;margin-bottom: 5px;border-top:0}
|
||||
.response-head .response-code {background:#dfdfdf;padding:0 8px;font-weight: bold;line-height: 29px;font-size: 80%;}
|
||||
.response-head .response-message {padding:0 10px;background:#fbfbfb;line-height: 30px;font-size: 80%;}
|
||||
|
||||
.request-compose {position:relative;}
|
||||
|
||||
@ -10,8 +10,10 @@
|
||||
|
||||
<span class="route-link">
|
||||
<a href="{{ $route['route'] }}" title="{{ $route['path'] }}">{{ $route['cleaned'] }}</a>
|
||||
<span>-</span>
|
||||
<span>{{= $route['description'] }}</span>
|
||||
{% if ! empty($route['description']) %}
|
||||
<span>-</span>
|
||||
<span>{{= $route['description'] }}</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
|
||||
<small class="route-name">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user