Skip to content

Commit

Permalink
Adds EntityCaster for spiral/filters component
Browse files Browse the repository at this point in the history
fixes #75
  • Loading branch information
butschster committed Jan 5, 2024
1 parent 09cac59 commit b933c0c
Show file tree
Hide file tree
Showing 14 changed files with 418 additions and 26 deletions.
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true

[*.json]
indent_size = 2
13 changes: 11 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,17 @@
"cycle/schema-builder": "^2.6",
"doctrine/inflector": "^1.4 || ^2.0",
"spiral/attributes": "^2.10 || ^3.0",
"spiral/framework": "^3.3",
"spiral/reactor": "^3.0",
"spiral/scaffolder": "^3.0",
"spiral/prototype": "^3.0",
"spiral/console": "^3.0",
"spiral/core": "^3.0",
"spiral/boot": "^3.0",
"spiral/auth": "^3.0",
"spiral/tokenizer": "^3.0",
"spiral/config": "^3.0",
"spiral/validator": "^1.2",
"spiral/filters": "^3.9",
"spiral/data-grid-bridge": "^3.0",
"psr/container": "^1.1 || ^2.0"
},
Expand All @@ -41,8 +48,10 @@
"infection/infection": "^0.26.6",
"mockery/mockery": "^1.5",
"phpunit/phpunit": "^9.5.20",
"spiral/framework": "^3.9",
"spiral/testing": "^2.4",
"spiral/validator": "^1.2",
"spiral/nyholm-bridge": "^1.3",
"spiral-packages/database-seeder": "^3.1",
"vimeo/psalm": "^4.27"
},
"autoload": {
Expand Down
67 changes: 67 additions & 0 deletions src/Filter/EntityCaster.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

declare(strict_types=1);

namespace Spiral\Cycle\Filter;

use Cycle\ORM\ORMInterface;
use Psr\Container\ContainerInterface;
use Spiral\Filters\Exception\SetterException;
use Spiral\Filters\Model\FilterInterface;
use Spiral\Filters\Model\Mapper\CasterInterface;

final class EntityCaster implements CasterInterface
{
/**
* @var array<class-string, non-empty-string>
*/
private static array $cache = [];
private ?ORMInterface $orm = null;

public function __construct(
protected readonly ContainerInterface $container,
) {
}

public function supports(\ReflectionNamedType $type): bool
{
if ($type->isBuiltin()) {
return false;

Check warning on line 29 in src/Filter/EntityCaster.php

View check run for this annotation

Codecov / codecov/patch

src/Filter/EntityCaster.php#L29

Added line #L29 was not covered by tests
}

return $this->getOrm()->getSchema()->defines($type->getName());
}

public function setValue(FilterInterface $filter, \ReflectionProperty $property, mixed $value): void
{
$role = $this->resolveRole($property->getType());
$object = $this->getOrm()->getRepository($role)->findByPK($value);

if ($object === null && !$property->getType()->allowsNull()) {
throw new SetterException(message: \sprintf('Unable to find entity `%s` by primary key "%s"', $role, $value));
}

$property->setValue($filter, $object);
}

private function resolveRole(\ReflectionNamedType $type): string
{
if (isset(self::$cache[$type->getName()])) {
return self::$cache[$type->getName()];
}

$role = $this->getOrm()->resolveRole($type->getName());
self::$cache[$type->getName()] = $role;

return $role;
}

private function getOrm(): ORMInterface
{
if ($this->orm === null) {
$this->orm = $this->container->get(ORMInterface::class);
}

return $this->orm;
}
}
7 changes: 7 additions & 0 deletions tests/app/Bootloader/AppBootloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
use Spiral\App\Repositories\RoleRepositoryInterface;
use Spiral\Bootloader\DomainBootloader;
use Spiral\Core\CoreInterface;
use Spiral\Cycle\Filter\EntityCaster;
use Spiral\Cycle\Interceptor\CycleInterceptor;
use Spiral\Filters\Model\Mapper\CasterRegistryInterface;

final class AppBootloader extends DomainBootloader
{
Expand All @@ -23,4 +25,9 @@ final class AppBootloader extends DomainBootloader
protected const INTERCEPTORS = [
CycleInterceptor::class,
];

public function init(CasterRegistryInterface $casterRegistry, EntityCaster $caster): void
{
$casterRegistry->register($caster);
}
}
21 changes: 21 additions & 0 deletions tests/app/Controller/Filter/RoleFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace Spiral\App\Controller\Filter;

use Spiral\App\Entities\Role;
use Spiral\Filters\Attribute\Input\Post;
use Spiral\Filters\Model\Filter;

final class RoleFilter extends Filter
{
#[Post]
public string $name;

#[Post]
public Role $role;

#[Post]
public ?Role $nullableRole;
}
9 changes: 7 additions & 2 deletions tests/app/Controller/HomeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,17 @@ class HomeController
{
public function entity(User $user)
{
return $user->getName();
return [
'user' => $user->getName(),
];
}

public function entity2(User $user, Role $role)
{
return 'ok';
return [
'user' => $user->getName(),
'role' => $role->name,
];
}

public function index(): string
Expand Down
31 changes: 31 additions & 0 deletions tests/app/Controller/RoleController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace Spiral\App\Controller;

use Spiral\App\Controller\Filter\RoleFilter;
use Spiral\App\Entities\Role;
use Spiral\Router\Annotation\Route;

final class RoleController
{
#[Route(route: "/role", methods: ["POST"])]
public function create(RoleFilter $filter): array
{
return [
'name' => $filter->name,
'role' => $filter->role->name,
'id' => $filter->role->id,
];
}

#[Route(route: "/role/<role>", methods: ["GET"])]
public function show(Role $role): array
{
return [
'name' => $role->name,
'id' => $role->id,
];
}
}
32 changes: 32 additions & 0 deletions tests/app/Database/Factory/RoleFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Spiral\App\Database\Factory;

use Spiral\App\Entities\Role;
use Spiral\DatabaseSeeder\Factory\AbstractFactory;
use Spiral\DatabaseSeeder\Factory\FactoryInterface;

/**
* @implements FactoryInterface<Role>
*/
final class RoleFactory extends AbstractFactory
{
public function makeEntity(array $definition): object
{
return new Role($definition['name']);
}

public function entity(): string
{
return Role::class;
}

public function definition(): array
{
return [
'name' => $this->faker->word,
];
}
}
48 changes: 48 additions & 0 deletions tests/app/Database/Factory/UserFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace Spiral\App\Database\Factory;

use Spiral\App\Entities\Role;
use Spiral\App\Entities\User;
use Spiral\DatabaseSeeder\Factory\AbstractFactory;
use Spiral\DatabaseSeeder\Factory\FactoryInterface;

/**
* @implements FactoryInterface<User>
*/
final class UserFactory extends AbstractFactory
{
public function makeEntity(array $definition): object
{
$user = new User($definition['name']);
$user->email = $definition['email'];
$user->company = $definition['company'];

return $user;
}

public function addRole(Role $role): self
{
return $this->entityState(static function (User $user) use ($role) {
$user->roles->add($role);

return $user;
});
}

public function entity(): string
{
return User::class;
}

public function definition(): array
{
return [
'name' => $this->faker->name,
'email' => $this->faker->email,
'company' => $this->faker->company,
];
}
}
8 changes: 8 additions & 0 deletions tests/src/BaseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@
use Spiral\App\Bootloader\AppBootloader;
use Spiral\App\Bootloader\SyncTablesBootloader;
use Spiral\Bootloader as Framework;
use Spiral\Nyholm\Bootloader as Nyholm;
use Spiral\Config\Patch\Set;
use Spiral\Console\Bootloader\ConsoleBootloader;
use Spiral\Core\ConfigsInterface;
use Spiral\Cycle\Bootloader as CycleBridge;
use Spiral\DataGrid\Bootloader\GridBootloader;
use Spiral\Router\Bootloader\AnnotatedRoutesBootloader;
use Spiral\Testing\TestCase;

abstract class BaseTest extends TestCase
Expand Down Expand Up @@ -64,6 +66,12 @@ public function defineBootloaders(): array
CycleBridge\DatabaseBootloader::class,
CycleBridge\MigrationsBootloader::class,

// Http
AnnotatedRoutesBootloader::class,
Framework\Http\RouterBootloader::class,
Nyholm\NyholmBootloader::class,
Framework\Security\FiltersBootloader::class,

// ORM
CycleBridge\SchemaBootloader::class,
CycleBridge\CycleOrmBootloader::class,
Expand Down
42 changes: 42 additions & 0 deletions tests/src/DatabaseTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace Spiral\Tests;

use Spiral\DatabaseSeeder\Database\Traits\DatabaseAsserts;
use Spiral\DatabaseSeeder\Database\Traits\Helper;
use Spiral\DatabaseSeeder\Database\Traits\ShowQueries;
use Spiral\DatabaseSeeder\Database\Traits\Transactions;

abstract class DatabaseTest extends BaseTest
{
use Transactions, Helper, DatabaseAsserts, ShowQueries;

protected function tearDown(): void
{
parent::tearDown();

$this->cleanIdentityMap();
$this->getCurrentDatabaseDriver()->disconnect();
}

public function persist(object ...$entity): void
{
$em = $this->getEntityManager();
foreach ($entity as $e) {
$em->persist($e);
}
$em->run();
}

/**
* @template T of object
* @param T $entity
* @return T
*/
public function refreshEntity(object $entity, string $pkField = 'uuid'): object
{
return $this->getRepositoryFor($entity)->findByPK($entity->{$pkField});
}
}
Loading

0 comments on commit b933c0c

Please sign in to comment.