Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EntityCaster: Enhanced Data Casting into Entities for spiral/filters component #87

Merged
merged 3 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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);
butschster marked this conversation as resolved.
Show resolved Hide resolved

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
Loading