Skip to content

Commit

Permalink
Dependent entity (#43)
Browse files Browse the repository at this point in the history
  • Loading branch information
metalslave authored Aug 18, 2023
1 parent 3e393e6 commit 7f33431
Show file tree
Hide file tree
Showing 10 changed files with 399 additions and 2 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ jobs:
working-directory: './'

- name: 'Run PHPUnit'
run: ./vendor/bin/phpunit -v -c phpunit.xml.dist
run: ./vendor/bin/phpunit -c phpunit.xml.dist

code-coverage:
needs: test
Expand All @@ -139,7 +139,7 @@ jobs:
working-directory: './'

- name: 'Run PHPUnit with Code Coverage'
run: ./vendor/bin/phpunit -v -c phpunit.xml.dist --coverage-clover=coverage.xml
run: ./vendor/bin/phpunit -c phpunit.xml.dist --coverage-clover=coverage.xml

- name: 'Download Coverage Files'
uses: actions/download-artifact@v2
Expand Down
40 changes: 40 additions & 0 deletions Attribute/DependentEntity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php
/*
* This file is part of the StfalconApiBundle.
*
* (c) Stfalcon LLC <stfalcon.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace StfalconStudio\ApiBundle\Attribute;

use StfalconStudio\ApiBundle\Exception\InvalidArgumentException;

/**
* Dependent Entity Attribute.
*/
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class DependentEntity
{
/**
* @param string $propertyPath
*/
public function __construct(private readonly string $propertyPath)
{
if (empty($propertyPath)) {
throw new InvalidArgumentException('The "propertyPath" parameter can not be empty.');
}
}

/**
* @return string
*/
public function getPropertyPath(): string
{
return $this->propertyPath;
}
}
20 changes: 20 additions & 0 deletions Service/DependentEntity/DependentEntityInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php
/*
* This file is part of the StfalconApiBundle.
*
* (c) Stfalcon LLC <stfalcon.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace StfalconStudio\ApiBundle\Service\DependentEntity;

/**
* Dependent Entity Interface.
*/
interface DependentEntityInterface
{
}
79 changes: 79 additions & 0 deletions Service/DependentEntity/DependentEntityService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php
/*
* This file is part of the StfalconApiBundle.
*
* (c) Stfalcon LLC <stfalcon.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace StfalconStudio\ApiBundle\Service\DependentEntity;

use StfalconStudio\ApiBundle\Attribute\DependentEntity;
use StfalconStudio\ApiBundle\Exception\LogicException;
use StfalconStudio\ApiBundle\Exception\RuntimeException;
use StfalconStudio\ApiBundle\Service\Repository\RepositoryService;
use StfalconStudio\ApiBundle\Traits\PropertyAccessorTrait;

/**
* Dependent Entity Service.
*/
class DependentEntityService
{
use PropertyAccessorTrait;

/**
* @param RepositoryService $repositoryService
*/
public function __construct(private readonly RepositoryService $repositoryService)
{
}

/**
* @param DependentEntityInterface $entity
*
* @return void
*/
public function setDependentEntities(DependentEntityInterface $entity): void
{
$class = new \ReflectionClass($entity);

$properties = $class->getProperties();

foreach ($properties as $property) {
$propertyName = $property->getName();
$attributes = $property->getAttributes(DependentEntity::class);

if (\count($attributes) > 1) {
throw new RuntimeException(\sprintf('Detected more than one DependentEntity attribute for property %s. Only one DependentEntity attribute allowed per property.', $propertyName));
}

if (empty($attributes)) {
continue;
}

$propertyPath = $attributes[0]->getArguments()['propertypath'];

$propertyPathValue = $this->propertyAccessor->getValue($entity, $propertyPath);

if (\is_string($propertyPathValue)) {
$reflectionType = $property->getType();

if (!$reflectionType instanceof \ReflectionNamedType) {
throw new LogicException(\sprintf('Cannot get reflection type from property %s', $propertyName));
}

$class = $reflectionType->getName();

$dependentEntity = $this->repositoryService->getEntityById($propertyPathValue, $class);
} else {
$dependentEntity = null;
}

$this->propertyAccessor->setValue($entity, $propertyName, $dependentEntity);
}
}
}
26 changes: 26 additions & 0 deletions Service/Repository/FindableByIdInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php
/*
* This file is part of the StfalconApiBundle.
*
* (c) Stfalcon LLC <stfalcon.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace StfalconStudio\ApiBundle\Service\Repository;

/**
* Findable By Id Interface.
*/
interface FindableByIdInterface
{
/**
* @param string $id
*
* @return mixed
*/
public function findOneById(string $id): mixed;
}
26 changes: 26 additions & 0 deletions Service/Repository/GettableOneByIdInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php
/*
* This file is part of the StfalconApiBundle.
*
* (c) Stfalcon LLC <stfalcon.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace StfalconStudio\ApiBundle\Service\Repository;

/**
* Gettable One By Id Interface.
*/
interface GettableOneByIdInterface
{
/**
* @param string $id
*
* @return mixed
*/
public function getOneById(string $id): mixed;
}
58 changes: 58 additions & 0 deletions Service/Repository/RepositoryService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php
/*
* This file is part of the StfalconApiBundle.
*
* (c) Stfalcon LLC <stfalcon.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace StfalconStudio\ApiBundle\Service\Repository;

use StfalconStudio\ApiBundle\Exception\LogicException;
use StfalconStudio\ApiBundle\Traits\EntityManagerTrait;

/**
* Repository Service.
*/
class RepositoryService
{
use EntityManagerTrait;

/**
* @param string $id
* @param string $class
*
* @return mixed
*/
public function getEntityById(string $id, string $class): mixed
{
$repository = $this->em->getRepository($class); // @phpstan-ignore-line

if (!$repository instanceof GettableOneByIdInterface) {
throw new LogicException(\sprintf('Repository %s should implements %s interface', $repository->getClassName(), GettableOneByIdInterface::class));
}

return $repository->getOneById($id);
}

/**
* @param string $id
* @param string $class
*
* @return mixed
*/
public function findEntityById(string $id, string $class): mixed
{
$repository = $this->em->getRepository($class); // @phpstan-ignore-line

if (!$repository instanceof FindableByIdInterface) {
throw new LogicException(\sprintf('Repository %s should implements %s interface', $repository->getClassName(), FindableByIdInterface::class));
}

return $repository->findOneById($id);
}
}
35 changes: 35 additions & 0 deletions Traits/PropertyAccessorTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php
/*
* This file is part of the StfalconApiBundle.
*
* (c) Stfalcon LLC <stfalcon.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace StfalconStudio\ApiBundle\Traits;

use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\Contracts\Service\Attribute\Required;

/**
* Property Accessor Trait.
*/
trait PropertyAccessorTrait
{
protected PropertyAccessor $propertyAccessor;

/**
* @param PropertyAccessor $propertyAccessor
*
* @return void
*/
#[Required]
public function setPropertyAccessor(PropertyAccessor $propertyAccessor): void
{
$this->propertyAccessor = $propertyAccessor;
}
}
56 changes: 56 additions & 0 deletions Validator/Constraints/Entity/EntityExists.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php
/*
* This file is part of the StfalconApiBundle.
*
* (c) Stfalcon LLC <stfalcon.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace StfalconStudio\ApiBundle\Validator\Constraints\Entity;

use StfalconStudio\ApiBundle\Exception\InvalidArgumentException;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;

/**
* Entity Exists.
*/
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class EntityExists extends Constraint
{
public const ENTITY_DOES_NOT_EXIST = 'ENTITY_DOES_NOT_EXIST';

/** @var array<string, string> */
protected const ERROR_NAMES = [
self::ENTITY_DOES_NOT_EXIST => self::ENTITY_DOES_NOT_EXIST,
];

public string $message = 'entity_does_not_exist';

public string $class;

/**
* @param string $class
* @param mixed $options
* @param array|null $groups
* @param mixed $payload
*/
public function __construct(string $class, mixed $options = null, array $groups = null, mixed $payload = null)
{
parent::__construct($options, $groups, $payload);

if (empty($class)) {
throw new ConstraintDefinitionException('The "class" parameter can not be empty.');
}

if (!\class_exists($class)) {
throw new InvalidArgumentException(\sprintf('Class %s does not exist.', $class));
}

$this->class = $class;
}
}
Loading

0 comments on commit 7f33431

Please sign in to comment.