diff --git a/config/app.neon b/config/app.neon index d7beb45..be43069 100644 --- a/config/app.neon +++ b/config/app.neon @@ -18,7 +18,9 @@ services: - Megio\Http\Kernel\App - Megio\Storage\Storage - Megio\Debugger\ResponseFormatter - - Megio\Database\CrudHelper\CrudHelper + + - Megio\Database\EntityFinder + - Megio\Collection\RecipeFinder - Megio\Security\JWT\JWTResolver - Megio\Security\JWT\ClaimsFormatter diff --git a/src/Database/CrudHelper/CrudException.php b/src/Collection/CollectionException.php similarity index 67% rename from src/Database/CrudHelper/CrudException.php rename to src/Collection/CollectionException.php index 0232f99..42a712f 100644 --- a/src/Database/CrudHelper/CrudException.php +++ b/src/Collection/CollectionException.php @@ -5,8 +5,8 @@ */ declare(strict_types=1); -namespace Megio\Database\CrudHelper; +namespace Megio\Collection; -class CrudException extends \Exception +class CollectionException extends \Exception { } \ No newline at end of file diff --git a/src/Collection/CollectionPropType.php b/src/Collection/CollectionPropType.php new file mode 100644 index 0000000..3e4df16 --- /dev/null +++ b/src/Collection/CollectionPropType.php @@ -0,0 +1,38 @@ + array_merge(['id'], $recipe->invisibleColumns()), + self::SHOW_ONE => array_merge(['id'], $recipe->showOneColumns()), + self::SHOW_ALL => array_merge(['id'], $recipe->showAllColumns()), + self::NONE => [], + }; + } + + /** + * @param array{maxLength: int|null, name: string, nullable: bool, type: string}[] $schema + * @param \Megio\Collection\ICollectionRecipe $recipe + * @return array{maxLength: int|null, name: string, nullable: bool, type: string}[] $schema + */ + public function getAllowedPropNames(array $schema, ICollectionRecipe $recipe): array + { + $propNames = $this->getPropNames($recipe); + $props = array_filter($schema, fn($field) => in_array($field['name'], $propNames)); + + return array_values($props); + } +} diff --git a/src/Collection/CollectionRecipe.php b/src/Collection/CollectionRecipe.php new file mode 100644 index 0000000..6ea9512 --- /dev/null +++ b/src/Collection/CollectionRecipe.php @@ -0,0 +1,51 @@ +source(), ICrudable::class)) { + throw new CollectionException("Entity '{$this->source()}' does not implement ICrudable"); + } + + $rf = new \ReflectionClass($this->source()); + $attr = $rf->getAttributes(Table::class); + + if (count($attr) === 0) { + throw new CollectionException("Entity '{$this->source()}' is missing Table attribute"); + } + + /** @var Table $attrInstance */ + $attrInstance = $attr[0]->newInstance(); + + if ($attrInstance->name === null) { + throw new CollectionException("Entity '{$this->source()}' has Table attribute without name"); + } + + $tableName = str_replace('`', '', $attrInstance->name); + $metadata = new RecipeEntityMetadata($this, $rf, $type, $tableName); + + if (count($metadata->getSchema()['props']) === 1) { + throw new CollectionException("Collection '{$this->name()}' has no visible columns"); + } + + return $metadata; + } +} \ No newline at end of file diff --git a/src/Collection/ICollectionRecipe.php b/src/Collection/ICollectionRecipe.php new file mode 100644 index 0000000..904b8b0 --- /dev/null +++ b/src/Collection/ICollectionRecipe.php @@ -0,0 +1,28 @@ + $data + * @return \Megio\Database\Interface\ICrudable + * @throws \Megio\Collection\CollectionException + */ + public static function create(ICollectionRecipe $recipe, RecipeEntityMetadata $metadata, array $data): ICrudable + { + $className = $recipe->source(); + + /** @var \Megio\Database\Interface\ICrudable $entity */ + $entity = new $className(); + + return self::update($metadata, $entity, $data); + } + + /** + * @param \Megio\Collection\RecipeEntityMetadata $metadata + * @param \Megio\Database\Interface\ICrudable $entity + * @param array $data + * @return \Megio\Database\Interface\ICrudable + * @throws \Megio\Collection\CollectionException + */ + public static function update(RecipeEntityMetadata $metadata, ICrudable $entity, array $data): ICrudable + { + $ref = $metadata->getReflection(); + $methods = array_map(fn($method) => $method->name, $ref->getMethods()); + + foreach ($data as $key => $value) { + try { + $methodName = 'set' . ucfirst($key); + if (in_array($methodName, $methods)) { + $m = $ref->getMethod($methodName)->name; + $entity->$m($value); + } else { + $ref->getProperty($key)->setValue($entity, $value); + } + } catch (\ReflectionException) { + throw new CollectionException("Property '{$key}' does not exist"); + } + } + + return $entity; + } +} \ No newline at end of file diff --git a/src/Collection/RecipeEntityMetadata.php b/src/Collection/RecipeEntityMetadata.php new file mode 100644 index 0000000..3473307 --- /dev/null +++ b/src/Collection/RecipeEntityMetadata.php @@ -0,0 +1,139 @@ + $entityRef + * @param \Megio\Collection\CollectionPropType $type + * @param string $tableName + */ + public function __construct( + private ICollectionRecipe $recipe, + private \ReflectionClass $entityRef, + private CollectionPropType $type, + private string $tableName, + ) + { + } + + /** + * @return string + */ + public function getTableName(): string + { + return $this->tableName; + } + + /** + * @return \ReflectionClass<\Megio\Database\Interface\ICrudable> + */ + public function getReflection(): \ReflectionClass + { + return $this->entityRef; + } + + public function getQbSelect(string $qbAlias): string + { + $schema = $this->getFullSchemaReflectedByDoctrine(); + $visibleProps = $this->type->getAllowedPropNames($schema, $this->recipe); + return implode(', ', array_map(fn($col) => $qbAlias . '.' . $col['name'], $visibleProps)); + } + + /** + * @return array{ + * meta: array{table: string, invisible: string[]}, + * props: array{maxLength: int|null, name: string, nullable: bool, type: string}[] + * } + */ + public function getSchema(): array + { + $schema = $this->getFullSchemaReflectedByDoctrine(); + $props = $this->type->getAllowedPropNames($schema, $this->recipe); + + return [ + 'meta' => [ + 'table' => $this->tableName, + 'invisible' => $this->recipe->invisibleColumns() + ], + 'props' => $this->sortColumns($props) + ]; + } + + /** + * @return array{maxLength: int|null, name: string, nullable: bool, type: string}[] + */ + private function getFullSchemaReflectedByDoctrine(): array + { + $props = []; + foreach ($this->entityRef->getProperties() as $prop) { + $attrs = array_map(fn($attr) => $attr->newInstance(), $prop->getAttributes()); + + /** @var Column[] $columnAttrs */ + $columnAttrs = array_filter($attrs, fn($attr) => $attr instanceof Column); + if (count($columnAttrs) !== 0) { + $attr = array_values($columnAttrs)[0]; + $props[] = $this->getColumnMetadata($attr, $prop); + } + } + + // move array item with name "id" to first position + $idProp = array_filter($props, fn($prop) => $prop['name'] !== 'id'); + return array_merge(array_values(array_filter($props, fn($prop) => $prop['name'] === 'id')), $idProp); + } + + /** + * @param \Doctrine\ORM\Mapping\Column $attr + * @param \ReflectionProperty $prop + * @return array{maxLength: int|null, name: string, nullable: bool, type: string} + */ + private function getColumnMetadata(Column $attr, \ReflectionProperty $prop): array + { + $propType = $prop->getType(); + $nullable = $attr->nullable; + + $type = $attr->type; + if ($type === null) { + $type = $propType instanceof \ReflectionNamedType ? $propType->getName() : $propType ?? '@unknown'; + } + + $maxLength = $attr->length; + if ($maxLength === null && $type === 'string') { + $maxLength = 255; + } + + return [ + 'name' => $prop->getName(), + 'type' => mb_strtolower($type), + 'nullable' => $nullable, + 'maxLength' => $maxLength + ]; + } + + + /** + * @param array{maxLength: int|null, name: string, nullable: bool, type: string}[] $fields + * @return array{maxLength: int|null, name: string, nullable: bool, type: string}[] + */ + private function sortColumns(array $fields): array + { + $associativeFields = []; + foreach ($fields as $field) { + $associativeFields[$field['name']] = $field; + } + + $sortedFields = []; + foreach ($this->type->getPropNames($this->recipe) as $name) { + if (array_key_exists($name, $associativeFields)) { + $sortedFields[] = $associativeFields[$name]; + } + } + + return $sortedFields; + } +} \ No newline at end of file diff --git a/src/Collection/RecipeFinder.php b/src/Collection/RecipeFinder.php new file mode 100644 index 0000000..35c9f36 --- /dev/null +++ b/src/Collection/RecipeFinder.php @@ -0,0 +1,46 @@ +from(Path::appDir() . '/Recipe'); + foreach ($appFiles as $file) { + $class = 'App\\Recipe\\' . $file->getBasename('.php'); + if (is_subclass_of($class, ICollectionRecipe::class)) { + $this->recipes[] = new $class(); + } + } + + $vendorFiles = Finder::findFiles()->from(Path::megioVendorDir() . '/src/Recipe'); + foreach ($vendorFiles as $file) { + $class = 'Megio\\Recipe\\' . $file->getBasename('.php'); + if (is_subclass_of($class, ICollectionRecipe::class)) { + $this->recipes[] = new $class(); + } + } + + return $this; + } + + /** @return ICollectionRecipe[] */ + public function getAll(): array + { + return $this->recipes; + } + + public function findByName(string $name): ?ICollectionRecipe + { + $recipe = current(array_filter($this->recipes, fn($r) => $r->name() === $name)); + return $recipe ?: null; + } +} \ No newline at end of file diff --git a/src/Database/CrudHelper/CrudHelper.php b/src/Database/CrudHelper/CrudHelper.php deleted file mode 100644 index 628ae6c..0000000 --- a/src/Database/CrudHelper/CrudHelper.php +++ /dev/null @@ -1,224 +0,0 @@ -error; - } - - /** - * @return array - */ - public function getAllEntities(): array - { - $metadata = $this->em->getMetadataFactory()->getAllMetadata(); - - $entities = array_map(function (ClassMetadata $metadata) { - $attr = $metadata->getReflectionClass()->getAttributes(Table::class)[0]->newInstance(); - return ['table' => str_replace('`', '', $attr->name ?? ''), 'value' => $metadata->name]; - }, $metadata); - - return array_filter($entities, fn($entity) => !in_array($entity['value'], self::EXCLUDED_EVERYWHERE)); - } - - /** - * @param string $tableName - * @return class-string|null - */ - public function getEntityClassName(string $tableName): ?string - { - $entityNames = $this->getAllEntities(); - $current = current(array_filter($entityNames, fn($namespace) => $namespace['table'] === $tableName)); - return $current ? $current['value'] : null; - } - - /** - * @param class-string $entityClassName - * @param string $propertyName - * @return array - */ - public function getPropertyDefaults(string $entityClassName, string $propertyName): array - { - try { - $ref = new \ReflectionClass($entityClassName); - return $ref->getProperty($propertyName)->getDefaultValue(); - } catch (\ReflectionException) { - return []; - } - } - - /** - * @param class-string $entityClassName - * @return array - */ - public function getEntitySchema(string $entityClassName): array - { - $props = []; - try { - $ref = new \ReflectionClass($entityClassName); - foreach ($ref->getProperties() as $prop) { - $attrs = array_map(fn($attr) => $attr->newInstance(), $prop->getAttributes()); - - /** @var Column[] $columnAttrs */ - $columnAttrs = array_filter($attrs, fn($attr) => $attr instanceof Column); - if (count($columnAttrs) !== 0) { - $attr = array_values($columnAttrs)[0]; - $props[] = $this->getEntityColumnProps($attr, $prop); - } - } - } catch (\ReflectionException) { - } - - // move array item with name "id" to first position - $idProp = array_filter($props, fn($prop) => $prop['name'] !== 'id'); - return array_merge(array_values(array_filter($props, fn($prop) => $prop['name'] === 'id')), $idProp); - } - - /** - * @param string $tableName - * @param string $visiblePropsProperty - * @param bool $schema - * @return \Megio\Database\CrudHelper\EntityMetadata|null - */ - public function getEntityMetadata(string $tableName, string $visiblePropsProperty, bool $schema = false): ?EntityMetadata - { - $this->error = null; - - $className = $this->getEntityClassName($tableName); - - if (!$className) { - $this->error = "Collection '{$tableName}' not found"; - return null; - } - - $visibleProps = $this->getPropertyDefaults($className, $visiblePropsProperty); - $visibleProps = array_merge(['id'], array_filter($visibleProps, fn($prop) => $prop !== 'id')); - - if (count($visibleProps) === 1) { // 'id' is automatically included - $this->error = "Collection '{$tableName}' has no visible fields"; - return null; - } - - $fieldsSchema = []; - $invisibleFields = []; - - if ($schema) { - $invisibleFields = $this->getPropertyDefaults($className, self::PROPERTY_INVISIBLE); - $schemaProps = $this->getEntitySchema($className); - $fields = array_values(array_filter($schemaProps, fn($prop) => in_array($prop['name'], $visibleProps))); - $fieldsSchema = $this->sortFieldsByOrder($fields, $visibleProps); - } - - return new EntityMetadata($className, $tableName, $visibleProps, $fieldsSchema, $invisibleFields); - } - - /** - * @param \Megio\Database\Interface\ICrudable $entity - * @param array $props - * @return \Megio\Database\Interface\ICrudable - * @throws \Megio\Database\CrudHelper\CrudException - */ - public function setUpEntityProps(ICrudable $entity, array $props): ICrudable - { - $ref = new \ReflectionClass($entity); - $methods = array_map(fn($method) => $method->name, $ref->getMethods()); - - foreach ($props as $key => $value) { - try { - $methodName = 'set' . ucfirst($key); - if (in_array($methodName, $methods)) { - $m = $ref->getMethod($methodName)->name; - $entity->$m($value); - } else { - $ref->getProperty($key)->setValue($entity, $value); - } - } catch (\ReflectionException) { - throw new CrudException("Property '{$key}' does not exist"); - } - - } - - return $entity; - } - - /** - * @param \Doctrine\ORM\Mapping\Column $attr - * @param \ReflectionProperty $prop - * @return array - */ - public function getEntityColumnProps(Column $attr, \ReflectionProperty $prop): array - { - $propType = $prop->getType(); - $nullable = $attr->nullable; - - $type = $attr->type; - if ($type === null) { - $type = $propType instanceof \ReflectionNamedType ? $propType->getName() : $propType ?? '@unknown'; - } - - $maxLength = $attr->length; - if ($maxLength === null && $type === 'string') { - $maxLength = 255; - } - - return [ - 'name' => $prop->getName(), - 'type' => mb_strtolower($type), - 'nullable' => $nullable, - 'maxLength' => $maxLength - ]; - } - - /** - * @param array> $fields - * @param array $sortedNames - * @return array> - */ - public function sortFieldsByOrder(array $fields, array $sortedNames) : array - { - $associativeFields = []; - foreach ($fields as $field) { - $associativeFields[$field['name']] = $field; - } - - $sortedFields = []; - foreach ($sortedNames as $name) { - if (array_key_exists($name, $associativeFields)) { - $sortedFields[] = $associativeFields[$name]; - } - } - - return $sortedFields; - } -} \ No newline at end of file diff --git a/src/Database/CrudHelper/EntityMetadata.php b/src/Database/CrudHelper/EntityMetadata.php deleted file mode 100644 index ec8e7cd..0000000 --- a/src/Database/CrudHelper/EntityMetadata.php +++ /dev/null @@ -1,47 +0,0 @@ - $props - * @param array $propsSchema - * @param array $invisibleFields - */ - public function __construct( - public string $className, - public string $tableName, - protected array $props, - protected array $propsSchema = [], - protected array $invisibleFields = [] - ) - { - } - - public function getQuerySelect(string $alias): string - { - return implode(', ', array_map(fn($prop) => $alias . '.' . $prop, $this->props)); - } - - /** - * @return array - */ - public function getSchema(): array - { - return [ - 'meta' => [ - 'table' => $this->tableName, - 'invisible' => $this->invisibleFields - ], - 'props' => $this->propsSchema - ]; - } -} \ No newline at end of file diff --git a/src/Database/Entity/Admin.php b/src/Database/Entity/Admin.php index d15ac65..ae76241 100644 --- a/src/Database/Entity/Admin.php +++ b/src/Database/Entity/Admin.php @@ -31,15 +31,6 @@ class Admin implements ICrudable, IAuthenticable use TId, TCreatedAt, TUpdatedAt, TEmail, TPassword, TLastLogin; - /** @var string[] */ - public array $invisibleFields = ['id', 'updatedAt']; - - /** @var string[] */ - public array $showAllFields = ['email', 'lastLogin', 'createdAt', 'updatedAt']; - - /** @var string[] */ - public array $showOneFields = ['email', 'lastLogin', 'createdAt', 'updatedAt']; - /** * @return Collection */ diff --git a/src/Database/EntityFinder.php b/src/Database/EntityFinder.php new file mode 100644 index 0000000..8e9b64b --- /dev/null +++ b/src/Database/EntityFinder.php @@ -0,0 +1,53 @@ + + */ + public function findAll(): array + { + $metadata = $this->em->getMetadataFactory()->getAllMetadata(); + + $entities = array_map(function (ClassMetadata $metadata) { + $attr = $metadata->getReflectionClass()->getAttributes(Table::class)[0]->newInstance(); + return [ + 'table' => str_replace('`', '', $attr->name ?? ''), + 'className' => $metadata->name + ]; + }, $metadata); + + return array_filter($entities, fn($entity) => !in_array($entity['className'], self::EXCLUDED_EVERYWHERE)); + } + + + /** + * @param string $tableName + * @return class-string|null + */ + public function getClassName(string $tableName): ?string + { + $entityNames = $this->findAll(); + $current = current(array_filter($entityNames, fn($namespace) => $namespace['table'] === $tableName)); + return $current ? $current['className'] : null; + } +} \ No newline at end of file diff --git a/src/Database/Manager/AuthResourceManager.php b/src/Database/Manager/AuthResourceManager.php index f30a999..7167927 100644 --- a/src/Database/Manager/AuthResourceManager.php +++ b/src/Database/Manager/AuthResourceManager.php @@ -8,7 +8,7 @@ namespace Megio\Database\Manager; use Nette\Utils\Strings; -use Megio\Database\CrudHelper\CrudHelper; +use Megio\Database\EntityFinder; use Megio\Database\Entity\Admin; use Megio\Database\Entity\Auth\Resource; use Megio\Database\EntityManager; @@ -22,7 +22,7 @@ public function __construct( private EntityManager $em, private RouteCollection $routes, - private CrudHelper $crudHelper, + private EntityFinder $entityFinder, ) { } @@ -79,7 +79,6 @@ public function updateResources(bool $flush = true, array $viewResources = [], R */ public function diffNames(array $sourceNames, ResourceType $type): array { - /** @var \Megio\Database\Entity\Auth\Resource[] $resources */ $resources = $this->em->getAuthResourceRepo()->findBy(['type' => $type->value]); $resourceNames = array_map(fn(Resource $resource) => $resource->getName(), $resources); @@ -129,8 +128,8 @@ public function routerViewResources(array $viewResources): array public function collectionDataResources(): array { $excluded = [Admin::class]; - $entities = $this->crudHelper->getAllEntities(); - $entities = array_filter($entities, fn($entity) => !in_array($entity['value'], $excluded)); + $entities = $this->entityFinder->findAll(); + $entities = array_filter($entities, fn($entity) => !in_array($entity['className'], $excluded)); $tables = array_map(fn($entity) => $entity['table'], $entities); $resourceNames = array_keys($this->routes->all()); @@ -154,8 +153,8 @@ public function collectionDataResources(): array public function collectionNavResources(): array { $excluded = [Admin::class]; - $entities = $this->crudHelper->getAllEntities(); - $entities = array_filter($entities, fn($entity) => !in_array($entity['value'], $excluded)); + $entities = $this->entityFinder->findAll(); + $entities = array_filter($entities, fn($entity) => !in_array($entity['className'], $excluded)); $tables = array_map(fn($entity) => $entity['table'], $entities); $names = []; diff --git a/src/Event/Collection/OnProcessingExceptionEvent.php b/src/Event/Collection/OnProcessingExceptionEvent.php index 1d55506..08de9dc 100644 --- a/src/Event/Collection/OnProcessingExceptionEvent.php +++ b/src/Event/Collection/OnProcessingExceptionEvent.php @@ -7,7 +7,7 @@ namespace Megio\Event\Collection; -use Megio\Database\CrudHelper\EntityMetadata; +use Megio\Collection\RecipeEntityMetadata; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Contracts\EventDispatcher\Event; @@ -15,11 +15,11 @@ class OnProcessingExceptionEvent extends Event { public function __construct( - private mixed $data, - private readonly Request $request, - private readonly EntityMetadata $metadata, - private readonly \Throwable $exception, - private Response $response, + private mixed $data, + private readonly Request $request, + private readonly RecipeEntityMetadata $metadata, + private readonly \Throwable $exception, + private Response $response, ) { } @@ -41,9 +41,9 @@ public function getRequest(): Request } /** - * @return \Megio\Database\CrudHelper\EntityMetadata + * @return \Megio\Collection\RecipeEntityMetadata */ - public function getMetadata(): EntityMetadata + public function getMetadata(): RecipeEntityMetadata { return $this->metadata; } diff --git a/src/Event/Collection/OnProcessingFinishEvent.php b/src/Event/Collection/OnProcessingFinishEvent.php index be714df..e46deae 100644 --- a/src/Event/Collection/OnProcessingFinishEvent.php +++ b/src/Event/Collection/OnProcessingFinishEvent.php @@ -7,7 +7,7 @@ namespace Megio\Event\Collection; -use Megio\Database\CrudHelper\EntityMetadata; +use Megio\Collection\RecipeEntityMetadata; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Contracts\EventDispatcher\Event; @@ -15,11 +15,11 @@ class OnProcessingFinishEvent extends Event { public function __construct( - private mixed $data, - private readonly Request $request, - private readonly EntityMetadata $metadata, - private mixed $result, - private Response $response, + private mixed $data, + private readonly Request $request, + private readonly RecipeEntityMetadata $metadata, + private mixed $result, + private Response $response, ) { } @@ -41,9 +41,9 @@ public function getRequest(): Request } /** - * @return \Megio\Database\CrudHelper\EntityMetadata + * @return \Megio\Collection\RecipeEntityMetadata */ - public function getMetadata(): EntityMetadata + public function getMetadata(): RecipeEntityMetadata { return $this->metadata; } diff --git a/src/Event/Collection/OnProcessingStartEvent.php b/src/Event/Collection/OnProcessingStartEvent.php index 57c8a59..49e5c53 100644 --- a/src/Event/Collection/OnProcessingStartEvent.php +++ b/src/Event/Collection/OnProcessingStartEvent.php @@ -7,7 +7,7 @@ namespace Megio\Event\Collection; -use Megio\Database\CrudHelper\EntityMetadata; +use Megio\Collection\RecipeEntityMetadata; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Contracts\EventDispatcher\Event; @@ -17,9 +17,9 @@ class OnProcessingStartEvent extends Event protected ?Response $response = null; public function __construct( - private mixed $data, - private readonly Request $request, - private readonly EntityMetadata $metadata, + private mixed $data, + private readonly Request $request, + private readonly RecipeEntityMetadata $metadata, ) { } @@ -33,9 +33,9 @@ public function getData(): mixed } /** - * @return \Megio\Database\CrudHelper\EntityMetadata + * @return \Megio\Collection\RecipeEntityMetadata */ - public function getMetadata(): EntityMetadata + public function getMetadata(): RecipeEntityMetadata { return $this->metadata; } diff --git a/src/Http/Request/Auth/EmailAuthRequest.php b/src/Http/Request/Auth/EmailAuthRequest.php index 390e5bd..2188e10 100644 --- a/src/Http/Request/Auth/EmailAuthRequest.php +++ b/src/Http/Request/Auth/EmailAuthRequest.php @@ -8,7 +8,7 @@ namespace Megio\Http\Request\Auth; use Nette\Security\Passwords; -use Megio\Database\CrudHelper\CrudHelper; +use Megio\Database\EntityFinder; use Megio\Database\Entity\Auth\Token; use Megio\Database\EntityManager; use Megio\Database\Interface\IAuthenticable; @@ -26,15 +26,15 @@ public function __construct( private readonly EntityManager $em, private readonly JWTResolver $jwt, private readonly ClaimsFormatter $claims, - private readonly CrudHelper $crudHelper + private readonly EntityFinder $entityFinder ) { } public function schema(): array { - $all = $this->crudHelper->getAllEntities(); - $filtered = array_filter($all, fn($item) => is_subclass_of($item['value'], IAuthenticable::class)); + $all = $this->entityFinder->findAll(); + $filtered = array_filter($all, fn($item) => is_subclass_of($item['className'], IAuthenticable::class)); $tables = array_map(fn($class) => $class['table'], $filtered); return [ @@ -46,7 +46,7 @@ public function schema(): array public function process(array $data): Response { - $className = $this->crudHelper->getEntityClassName($data['source']); + $className = $this->entityFinder->getClassName($data['source']); if (!$className || !is_subclass_of($className, IAuthenticable::class)) { return $this->error(['Invalid source']); diff --git a/src/Http/Request/Auth/RevokeTokenRequest.php b/src/Http/Request/Auth/RevokeTokenRequest.php index c6f73b6..48a97d0 100644 --- a/src/Http/Request/Auth/RevokeTokenRequest.php +++ b/src/Http/Request/Auth/RevokeTokenRequest.php @@ -7,7 +7,7 @@ namespace Megio\Http\Request\Auth; -use Megio\Database\CrudHelper\CrudHelper; +use Megio\Database\EntityFinder; use Megio\Database\EntityManager; use Nette\Schema\Expect; use Megio\Database\Interface\IAuthenticable; @@ -16,14 +16,17 @@ class RevokeTokenRequest extends Request { - public function __construct(private readonly EntityManager $em, private readonly CrudHelper $crudHelper) + public function __construct( + private readonly EntityManager $em, + private readonly EntityFinder $entityFinder + ) { } public function schema(): array { - $all = $this->crudHelper->getAllEntities(); - $filtered = array_filter($all, fn($item) => is_subclass_of($item['value'], IAuthenticable::class)); + $all = $this->entityFinder->findAll(); + $filtered = array_filter($all, fn($item) => is_subclass_of($item['className'], IAuthenticable::class)); $tables = array_map(fn($class) => $class['table'], $filtered); return [ diff --git a/src/Http/Request/Collection/BaseCrudRequest.php b/src/Http/Request/Collection/BaseCrudRequest.php deleted file mode 100644 index 4df9619..0000000 --- a/src/Http/Request/Collection/BaseCrudRequest.php +++ /dev/null @@ -1,23 +0,0 @@ -helper->getEntityMetadata($tableName, $visiblePropsProperty, $schema); - } -} \ No newline at end of file diff --git a/src/Http/Request/Collection/CreateRequest.php b/src/Http/Request/Collection/CreateRequest.php index 997a5c7..5f3104f 100644 --- a/src/Http/Request/Collection/CreateRequest.php +++ b/src/Http/Request/Collection/CreateRequest.php @@ -8,45 +8,56 @@ namespace Megio\Http\Request\Collection; use Doctrine\DBAL\Exception\UniqueConstraintViolationException; +use Doctrine\ORM\Exception\ORMException; +use Megio\Collection\CollectionException; +use Megio\Collection\CollectionPropType; +use Megio\Collection\Mapping\ArrayToEntity; +use Megio\Collection\RecipeFinder; +use Megio\Http\Request\Request; use Nette\Schema\Expect; -use Megio\Database\CrudHelper\CrudException; -use Megio\Database\Entity\EntityException; use Megio\Database\EntityManager; -use Megio\Database\CrudHelper\CrudHelper; use Megio\Event\Collection\CollectionEvent; use Megio\Event\Collection\OnProcessingStartEvent; use Megio\Event\Collection\OnProcessingExceptionEvent; use Megio\Event\Collection\OnProcessingFinishEvent; use Symfony\Component\HttpFoundation\Response; -class CreateRequest extends BaseCrudRequest +class CreateRequest extends Request { public function __construct( protected readonly EntityManager $em, - protected readonly CrudHelper $helper, + protected readonly RecipeFinder $recipeFinder, ) { } public function schema(): array { - $tables = array_map(fn($meta) => $meta['table'], $this->helper->getAllEntities()); + $names = array_map(fn($r) => $r->name(), $this->recipeFinder->load()->getAll()); // TODO: get rows types trough reflection and validate them return [ - 'table' => Expect::anyOf(...$tables)->required(), + 'table' => Expect::anyOf(...$names)->required(), // TODO: rename to recipeName 'rows' => Expect::array()->min(1)->max(1000)->required() ]; } public function process(array $data): Response { - if (!$meta = $this->setUpMetadata($data['table'], false)) { - return $this->error([$this->helper->getError()]); + $recipe = $this->recipeFinder->findByName($data['table']); + + if ($recipe === null) { + return $this->error(["Collection {$data['table']} not found"]); + } + + try { + $metadata = $recipe->getEntityMetadata(CollectionPropType::NONE); + } catch (CollectionException $e) { + return $this->error([$e->getMessage()]); } - $event = new OnProcessingStartEvent($data, $this->request, $meta); + $event = new OnProcessingStartEvent($data, $this->request, $metadata); $dispatcher = $this->dispatcher->dispatch($event, CollectionEvent::ON_PROCESSING_START); if ($dispatcher->getResponse()) { @@ -56,16 +67,13 @@ public function process(array $data): Response $ids = []; foreach ($data['rows'] as $row) { - /** @var \Megio\Database\Interface\ICrudable $entity */ - $entity = new $meta->className(); - try { - $entity = $this->helper->setUpEntityProps($entity, $row); + $entity = ArrayToEntity::create($recipe, $metadata, $row); $this->em->persist($entity); $ids[] = $entity->getId(); - } catch (CrudException|EntityException $e) { + } catch (CollectionException|ORMException $e) { $response = $this->error([$e->getMessage()], 406); - $event = new OnProcessingExceptionEvent($data, $this->request, $meta, $e, $response); + $event = new OnProcessingExceptionEvent($data, $this->request, $metadata, $e, $response); $dispatcher = $this->dispatcher->dispatch($event, CollectionEvent::ON_PROCESSING_EXCEPTION); return $dispatcher->getResponse(); } @@ -79,7 +87,7 @@ public function process(array $data): Response } catch (UniqueConstraintViolationException $e) { $this->em->rollback(); $response = $this->error([$e->getMessage()]); - $event = new OnProcessingExceptionEvent($data, $this->request, $meta, $e, $response); + $event = new OnProcessingExceptionEvent($data, $this->request, $metadata, $e, $response); $dispatcher = $this->dispatcher->dispatch($event, CollectionEvent::ON_PROCESSING_EXCEPTION); return $dispatcher->getResponse(); } catch (\Exception $e) { @@ -94,7 +102,7 @@ public function process(array $data): Response $response = $this->json($result); - $event = new OnProcessingFinishEvent($data, $this->request, $meta, $result, $response); + $event = new OnProcessingFinishEvent($data, $this->request, $metadata, $result, $response); $dispatcher = $this->dispatcher->dispatch($event, CollectionEvent::ON_PROCESSING_FINISH); return $dispatcher->getResponse(); diff --git a/src/Http/Request/Collection/DeleteRequest.php b/src/Http/Request/Collection/DeleteRequest.php index 15f749a..49bd34f 100644 --- a/src/Http/Request/Collection/DeleteRequest.php +++ b/src/Http/Request/Collection/DeleteRequest.php @@ -7,9 +7,12 @@ namespace Megio\Http\Request\Collection; +use Megio\Collection\CollectionException; +use Megio\Collection\CollectionPropType; +use Megio\Collection\RecipeFinder; +use Megio\Http\Request\Request; use Nette\Schema\Expect; use Megio\Database\EntityManager; -use Megio\Database\CrudHelper\CrudHelper; use Megio\Event\Collection\CollectionEvent; use Megio\Event\Collection\OnProcessingExceptionEvent; use Megio\Event\Collection\OnProcessingStartEvent; @@ -17,36 +20,47 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -class DeleteRequest extends BaseCrudRequest +class DeleteRequest extends Request { - public function __construct(protected readonly EntityManager $em, protected readonly CrudHelper $helper) + public function __construct( + protected readonly EntityManager $em, + protected readonly RecipeFinder $recipeFinder, + ) { } public function schema(): array { - $tables = array_map(fn($meta) => $meta['table'], $this->helper->getAllEntities()); + $names = array_map(fn($r) => $r->name(), $this->recipeFinder->load()->getAll()); return [ - 'table' => Expect::anyOf(...$tables)->required(), + 'table' => Expect::anyOf(...$names)->required(), // TODO: rename to recipeName 'ids' => Expect::arrayOf('string')->min(1)->required(), ]; } public function process(array $data): Response { - if (!$meta = $this->setUpMetadata($data['table'], false)) { - return $this->error([$this->helper->getError()]); + $recipe = $this->recipeFinder->findByName($data['table']); + + if ($recipe === null) { + return $this->error(["Collection {$data['table']} not found"]); + } + + try { + $metadata = $recipe->getEntityMetadata(CollectionPropType::NONE); + } catch (CollectionException $e) { + return $this->error([$e->getMessage()]); } - $event = new OnProcessingStartEvent($data, $this->request, $meta); + $event = new OnProcessingStartEvent($data, $this->request, $metadata); $dispatcher = $this->dispatcher->dispatch($event, CollectionEvent::ON_PROCESSING_START); if ($dispatcher->getResponse()) { return $dispatcher->getResponse(); } - $repo = $this->em->getRepository($meta->className); + $repo = $this->em->getRepository($recipe->source()); $qb = $repo->createQueryBuilder('entity') ->where('entity.id IN (:ids)') @@ -59,7 +73,7 @@ public function process(array $data): Response if ($diff !== 0) { $e = new NotFoundHttpException("{$diff} of {$countItems} items you want to delete already does not exist"); $response = $this->error([$e->getMessage()], 404); - $event = new OnProcessingExceptionEvent($data, $this->request, $meta, $e, $response); + $event = new OnProcessingExceptionEvent($data, $this->request, $metadata, $e, $response); $dispatcher = $this->dispatcher->dispatch($event, CollectionEvent::ON_PROCESSING_EXCEPTION); return $dispatcher->getResponse(); } @@ -73,7 +87,7 @@ public function process(array $data): Response $response = $this->json($result); - $event = new OnProcessingFinishEvent($data, $this->request, $meta, $result, $response); + $event = new OnProcessingFinishEvent($data, $this->request, $metadata, $result, $response); $dispatcher = $this->dispatcher->dispatch($event, CollectionEvent::ON_PROCESSING_FINISH); return $dispatcher->getResponse(); diff --git a/src/Http/Request/Collection/NavbarRequest.php b/src/Http/Request/Collection/NavbarRequest.php index 59667c1..87569d4 100644 --- a/src/Http/Request/Collection/NavbarRequest.php +++ b/src/Http/Request/Collection/NavbarRequest.php @@ -7,7 +7,7 @@ namespace Megio\Http\Request\Collection; -use Megio\Database\CrudHelper\CrudHelper; +use Megio\Collection\RecipeFinder; use Megio\Database\Entity\Admin; use Megio\Helper\Router; use Megio\Http\Request\Request; @@ -17,8 +17,8 @@ class NavbarRequest extends Request { public function __construct( - protected readonly AuthUser $authUser, - protected readonly CrudHelper $helper + protected readonly AuthUser $authUser, + protected readonly RecipeFinder $recipeFinder ) { } @@ -30,17 +30,17 @@ public function schema(): array public function process(array $data): Response { - $classes = $this->helper->getAllEntities(); - $classes = array_filter($classes, fn($class) => $class['value'] !== Admin::class); - $tables = array_map(fn($class) => $class['table'], $classes); + $recipes = $this->recipeFinder->load()->getAll(); + $recipes = array_filter($recipes, fn($recipe) => $recipe->source() !== Admin::class); + $recipeNames = array_map(fn($recipe) => $recipe->name(), $recipes); if (!$this->authUser->get() instanceof Admin) { $resources = $this->authUser->getResources(); - $tables = array_filter($tables, fn($table) => in_array(Router::ROUTE_META_NAVBAR . '.' . $table, $resources)); + $recipeNames = array_filter($recipeNames, fn($endpoint) => in_array(Router::ROUTE_META_NAVBAR . '.' . $endpoint, $resources)); } - sort($tables); + sort($recipeNames); - return $this->json(['items' => $tables]); + return $this->json(['items' => $recipeNames]); } } \ No newline at end of file diff --git a/src/Http/Request/Collection/ShowOneRequest.php b/src/Http/Request/Collection/ShowOneRequest.php index 2dcaa74..a84d0d9 100644 --- a/src/Http/Request/Collection/ShowOneRequest.php +++ b/src/Http/Request/Collection/ShowOneRequest.php @@ -8,9 +8,12 @@ namespace Megio\Http\Request\Collection; use Doctrine\ORM\AbstractQuery; +use Megio\Collection\CollectionException; +use Megio\Collection\CollectionPropType; +use Megio\Collection\RecipeFinder; +use Megio\Http\Request\Request; use Nette\Schema\Expect; use Megio\Database\EntityManager; -use Megio\Database\CrudHelper\CrudHelper; use Megio\Event\Collection\CollectionEvent; use Megio\Event\Collection\OnProcessingExceptionEvent; use Megio\Event\Collection\OnProcessingStartEvent; @@ -18,18 +21,21 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -class ShowOneRequest extends BaseCrudRequest +class ShowOneRequest extends Request { - public function __construct(protected readonly EntityManager $em, protected readonly CrudHelper $helper) + public function __construct( + protected readonly EntityManager $em, + protected readonly RecipeFinder $recipeFinder + ) { } public function schema(): array { - $tables = array_map(fn($meta) => $meta['table'], $this->helper->getAllEntities()); + $names = array_map(fn($r) => $r->name(), $this->recipeFinder->load()->getAll()); return [ - 'table' => Expect::anyOf(...$tables)->required(), + 'table' => Expect::anyOf(...$names)->required(), // TODO: rename to recipeName 'schema' => Expect::bool(false), 'id' => Expect::string()->required(), ]; @@ -37,21 +43,29 @@ public function schema(): array public function process(array $data): Response { - if (!$meta = $this->setUpMetadata($data['table'], $data['schema'], CrudHelper::PROPERTY_SHOW_ONE)) { - return $this->error([$this->helper->getError()]); + $recipe = $this->recipeFinder->findByName($data['table']); + + if ($recipe === null) { + return $this->error(["Collection {$data['table']} not found"]); + } + + try { + $metadata = $recipe->getEntityMetadata(CollectionPropType::SHOW_ONE); + } catch (CollectionException $e) { + return $this->error([$e->getMessage()]); } - $event = new OnProcessingStartEvent($data, $this->request, $meta); + $event = new OnProcessingStartEvent($data, $this->request, $metadata); $dispatcher = $this->dispatcher->dispatch($event, CollectionEvent::ON_PROCESSING_START); if ($dispatcher->getResponse()) { return $dispatcher->getResponse(); } - $repo = $this->em->getRepository($meta->className); + $repo = $this->em->getRepository($recipe->source()); $qb = $repo->createQueryBuilder('entity') - ->select($meta->getQuerySelect('entity')) + ->select($metadata->getQbSelect('entity')) ->where('entity.id = :id') ->setParameter('id', $data['id']); @@ -60,7 +74,7 @@ public function process(array $data): Response if (!$item) { $e = new NotFoundHttpException("Item '{$data['id']}' not found"); $response = $this->error([$e->getMessage()], 404); - $event = new OnProcessingExceptionEvent($data, $this->request, $meta, $e, $response); + $event = new OnProcessingExceptionEvent($data, $this->request, $metadata, $e, $response); $dispatcher = $this->dispatcher->dispatch($event, CollectionEvent::ON_PROCESSING_EXCEPTION); return $dispatcher->getResponse(); } @@ -68,12 +82,12 @@ public function process(array $data): Response $result = ['item' => $item]; if ($data['schema']) { - $result['schema'] = $meta->getSchema(); + $result['schema'] = $metadata->getSchema(); } $response = $this->json($result); - $event = new OnProcessingFinishEvent($data, $this->request, $meta, $result, $response); + $event = new OnProcessingFinishEvent($data, $this->request, $metadata, $result, $response); $dispatcher = $this->dispatcher->dispatch($event, CollectionEvent::ON_PROCESSING_FINISH); return $dispatcher->getResponse(); diff --git a/src/Http/Request/Collection/ShowRequest.php b/src/Http/Request/Collection/ShowRequest.php index 4812846..4315e66 100644 --- a/src/Http/Request/Collection/ShowRequest.php +++ b/src/Http/Request/Collection/ShowRequest.php @@ -7,26 +7,32 @@ namespace Megio\Http\Request\Collection; -use Megio\Database\CrudHelper\CrudHelper; +use Megio\Collection\CollectionException; +use Megio\Collection\CollectionPropType; +use Megio\Collection\RecipeFinder; use Megio\Database\EntityManager; +use Megio\Http\Request\Request; use Nette\Schema\Expect; use Megio\Event\Collection\CollectionEvent; use Megio\Event\Collection\OnProcessingStartEvent; use Megio\Event\Collection\OnProcessingFinishEvent; use Symfony\Component\HttpFoundation\Response; -class ShowRequest extends BaseCrudRequest +class ShowRequest extends Request { - public function __construct(protected readonly EntityManager $em, protected readonly CrudHelper $helper) + public function __construct( + protected readonly EntityManager $em, + protected readonly RecipeFinder $recipeFinder, + ) { } public function schema(): array { - $tables = array_map(fn($meta) => $meta['table'], $this->helper->getAllEntities()); + $names = array_map(fn($r) => $r->name(), $this->recipeFinder->load()->getAll()); return [ - 'table' => Expect::anyOf(...$tables)->required(), + 'table' => Expect::anyOf(...$names)->required(), // TODO: rename to recipeName 'schema' => Expect::bool(false), 'currentPage' => Expect::int(1)->min(1)->required(), 'itemsPerPage' => Expect::int(10)->max(1000)->required(), @@ -39,21 +45,29 @@ public function schema(): array public function process(array $data): Response { - if (!$meta = $this->setUpMetadata($data['table'], $data['schema'], CrudHelper::PROPERTY_SHOW_ALL)) { - return $this->error([$this->helper->getError()]); + $recipe = $this->recipeFinder->findByName($data['table']); + + if ($recipe === null) { + return $this->error(["Collection {$data['table']} not found"]); + } + + try { + $metadata = $recipe->getEntityMetadata( CollectionPropType::SHOW_ALL); + } catch (CollectionException $e) { + return $this->error([$e->getMessage()]); } - $event = new OnProcessingStartEvent($data, $this->request, $meta); + $event = new OnProcessingStartEvent($data, $this->request, $metadata); $dispatcher = $this->dispatcher->dispatch($event, CollectionEvent::ON_PROCESSING_START); if ($dispatcher->getResponse()) { return $dispatcher->getResponse(); } - $repo = $this->em->getRepository($meta->className); + $repo = $this->em->getRepository($recipe->source()); $qb = $repo->createQueryBuilder('entity') - ->select($meta->getQuerySelect('entity')); + ->select($metadata->getQbSelect('entity')); $count = (clone $qb)->select('count(entity.id)')->getQuery()->getSingleScalarResult(); @@ -75,12 +89,12 @@ public function process(array $data): Response ]; if ($data['schema']) { - $result['schema'] = $meta->getSchema(); + $result['schema'] = $metadata->getSchema(); } $response = $this->json($result); - $event = new OnProcessingFinishEvent($data, $this->request, $meta, $result, $response); + $event = new OnProcessingFinishEvent($data, $this->request, $metadata, $result, $response); $dispatcher = $this->dispatcher->dispatch($event, CollectionEvent::ON_PROCESSING_FINISH); return $dispatcher->getResponse(); diff --git a/src/Http/Request/Collection/UpdateRequest.php b/src/Http/Request/Collection/UpdateRequest.php index a30f145..8892e00 100644 --- a/src/Http/Request/Collection/UpdateRequest.php +++ b/src/Http/Request/Collection/UpdateRequest.php @@ -8,11 +8,14 @@ namespace Megio\Http\Request\Collection; use Doctrine\DBAL\Exception\UniqueConstraintViolationException; +use Megio\Collection\CollectionException; +use Megio\Collection\CollectionPropType; +use Megio\Collection\Mapping\ArrayToEntity; +use Megio\Collection\RecipeFinder; +use Megio\Http\Request\Request; use Nette\Schema\Expect; -use Megio\Database\CrudHelper\CrudException; use Megio\Database\Entity\EntityException; use Megio\Database\EntityManager; -use Megio\Database\CrudHelper\CrudHelper; use Megio\Event\Collection\CollectionEvent; use Megio\Event\Collection\OnProcessingFinishEvent; use Megio\Event\Collection\OnProcessingStartEvent; @@ -20,20 +23,22 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -class UpdateRequest extends BaseCrudRequest +class UpdateRequest extends Request { - public function __construct(protected readonly EntityManager $em, protected readonly CrudHelper $helper) + public function __construct( + protected readonly EntityManager $em, + protected readonly RecipeFinder $recipeFinder) { } public function schema(): array { - $tables = array_map(fn($meta) => $meta['table'], $this->helper->getAllEntities()); + $names = array_map(fn($r) => $r->name(), $this->recipeFinder->load()->getAll()); // TODO: get rows types trough reflection and validate them return [ - 'table' => Expect::anyOf(...$tables)->required(), + 'table' => Expect::anyOf(...$names)->required(), // TODO: rename to recipeName 'rows' => Expect::arrayOf( Expect::structure([ 'id' => Expect::string()->required(), @@ -45,11 +50,19 @@ public function schema(): array public function process(array $data): Response { - if (!$meta = $this->setUpMetadata($data['table'], false)) { - return $this->error([$this->helper->getError()]); + $recipe = $this->recipeFinder->findByName($data['table']); + + if ($recipe === null) { + return $this->error(["Collection {$data['table']} not found"]); + } + + try { + $metadata = $recipe->getEntityMetadata( CollectionPropType::NONE); + } catch (CollectionException $e) { + return $this->error([$e->getMessage()]); } - $event = new OnProcessingStartEvent($data, $this->request, $meta); + $event = new OnProcessingStartEvent($data, $this->request, $metadata); $dispatcher = $this->dispatcher->dispatch($event, CollectionEvent::ON_PROCESSING_START); if ($dispatcher->getResponse()) { @@ -58,7 +71,7 @@ public function process(array $data): Response $ids = array_map(fn($row) => $row['id'], $data['rows']); - $qb = $this->em->getRepository($meta->className) + $qb = $this->em->getRepository($recipe->source()) ->createQueryBuilder('entity') ->select('entity') ->where('entity.id IN (:ids)') @@ -68,21 +81,21 @@ public function process(array $data): Response $rows = $qb->getQuery()->getResult(); foreach ($data['rows'] as $row) { - $dbRow = current(array_filter($rows, fn($db) => $db->getId() === $row['id'])); + $item = current(array_filter($rows, fn($db) => $db->getId() === $row['id'])); - if (!$dbRow) { + if (!$item) { $e = new NotFoundHttpException("Item '{$row['id']}' not found"); $response = $this->error([$e->getMessage()], 404); - $event = new OnProcessingExceptionEvent($data, $this->request, $meta, $e, $response); + $event = new OnProcessingExceptionEvent($data, $this->request, $metadata, $e, $response); $dispatcher = $this->dispatcher->dispatch($event, CollectionEvent::ON_PROCESSING_EXCEPTION); return $dispatcher->getResponse(); } try { - $this->helper->setUpEntityProps($dbRow, $row['data']); - } catch (CrudException|EntityException $e) { + ArrayToEntity::update($metadata, $item, $row['data']); + } catch (CollectionException|EntityException $e) { $response = $this->error([$e->getMessage()], 406); - $event = new OnProcessingExceptionEvent($data, $this->request, $meta, $e, $response); + $event = new OnProcessingExceptionEvent($data, $this->request, $metadata, $e, $response); $dispatcher = $this->dispatcher->dispatch($event, CollectionEvent::ON_PROCESSING_EXCEPTION); return $dispatcher->getResponse(); } @@ -96,7 +109,7 @@ public function process(array $data): Response } catch (UniqueConstraintViolationException $e) { $this->em->rollback(); $response = $this->error([$e->getMessage()]); - $event = new OnProcessingExceptionEvent($data, $this->request, $meta, $e, $response); + $event = new OnProcessingExceptionEvent($data, $this->request, $metadata, $e, $response); $dispatcher = $this->dispatcher->dispatch($event, CollectionEvent::ON_PROCESSING_EXCEPTION); return $dispatcher->getResponse(); } catch (\Exception $e) { @@ -111,7 +124,7 @@ public function process(array $data): Response $response = $this->json($result); - $event = new OnProcessingFinishEvent($data, $this->request, $meta, $result, $response); + $event = new OnProcessingFinishEvent($data, $this->request, $metadata, $result, $response); $dispatcher = $this->dispatcher->dispatch($event, CollectionEvent::ON_PROCESSING_FINISH); return $dispatcher->getResponse(); diff --git a/src/Http/Request/Resource/CreateRoleRequest.php b/src/Http/Request/Resource/CreateRoleRequest.php index 5f80ff6..4259854 100644 --- a/src/Http/Request/Resource/CreateRoleRequest.php +++ b/src/Http/Request/Resource/CreateRoleRequest.php @@ -30,8 +30,6 @@ public function schema(): array public function process(array $data): Response { $name = Strings::webalize($data['name']); - - /** @var Role|null $role */ $role = $this->em->getAuthRoleRepo()->findOneBy(['name' => $name]); if ($role) { diff --git a/src/Http/Request/Resource/DeleteRoleRequest.php b/src/Http/Request/Resource/DeleteRoleRequest.php index 3d3872b..20058bc 100644 --- a/src/Http/Request/Resource/DeleteRoleRequest.php +++ b/src/Http/Request/Resource/DeleteRoleRequest.php @@ -28,7 +28,6 @@ public function schema(): array public function process(array $data): Response { - /** @var Role|null $role */ $role = $this->em->getAuthRoleRepo()->findOneBy(['id' => $data['id']]); if (!$role) { diff --git a/src/Http/Request/Resource/ShowAllRequest.php b/src/Http/Request/Resource/ShowAllRequest.php index 162f5da..f76b43b 100644 --- a/src/Http/Request/Resource/ShowAllRequest.php +++ b/src/Http/Request/Resource/ShowAllRequest.php @@ -37,7 +37,6 @@ public function schema(): array public function process(array $data): Response { - /** @var \Megio\Database\Entity\Auth\Resource[] $resources */ $resources = $this->em->getAuthResourceRepo()->findBy([], ['type' => 'ASC', 'name' => 'ASC']); /** @var \Megio\Database\Entity\Auth\Role[] $roles */ diff --git a/src/Http/Request/Resource/UpdateRoleRequest.php b/src/Http/Request/Resource/UpdateRoleRequest.php index a5dd44d..fb68b93 100644 --- a/src/Http/Request/Resource/UpdateRoleRequest.php +++ b/src/Http/Request/Resource/UpdateRoleRequest.php @@ -31,10 +31,7 @@ public function schema(): array public function process(array $data): Response { - /** @var Resource|null $resource */ $resource = $this->em->getAuthResourceRepo()->findOneBy(['id' => $data['resource_id']]); - - /** @var Role|null $role */ $role = $this->em->getAuthRoleRepo()->findOneBy(['id' => $data['role_id']]); if (!$role || !$resource) { diff --git a/src/Recipe/AdminRecipe.php b/src/Recipe/AdminRecipe.php new file mode 100644 index 0000000..17f6be3 --- /dev/null +++ b/src/Recipe/AdminRecipe.php @@ -0,0 +1,30 @@ +getMetadata()->tableName; + $tableName = $event->getMetadata()->getTableName(); $resourceName = $routeName . '.' . $tableName; if (!in_array($resourceName, $this->authUser->getResources())) { diff --git a/src/Subscriber/AuthRequest.php b/src/Subscriber/AuthRequest.php index e1bcd69..91abebe 100644 --- a/src/Subscriber/AuthRequest.php +++ b/src/Subscriber/AuthRequest.php @@ -7,7 +7,7 @@ namespace Megio\Subscriber; -use Megio\Database\CrudHelper\CrudHelper; +use Megio\Database\EntityFinder; use Megio\Database\Entity\Admin; use Megio\Database\EntityManager; use Megio\Database\Interface\IAuthenticable; @@ -27,7 +27,7 @@ class AuthRequest implements EventSubscriberInterface protected Request $request; public function __construct( - protected CrudHelper $crudHelper, + protected EntityFinder $entityFinder, protected JWTResolver $jwt, protected EntityManager $em, protected RouteCollection $routes, @@ -99,14 +99,13 @@ public function onRequest(RequestEvent $event): void return; } - $className = $this->crudHelper->getEntityClassName($token->getSource()); + $className = $this->entityFinder->getClassName($token->getSource()); if (!$className || !is_subclass_of($className, IAuthenticable::class)) { $this->sendError("For source {$token->getSource()} does not exists IAuthenticable entity"); return; } - /** @var class-string $className */ $userRepo = $this->em->getRepository($className); $qb = $userRepo->createQueryBuilder('user')