Skip to content

Instantly share code, notes, and snippets.

@chalasr
Last active September 26, 2023 00:59
Show Gist options
  • Save chalasr/77be8eee5e3ecd3c06ec to your computer and use it in GitHub Desktop.
Save chalasr/77be8eee5e3ecd3c06ec to your computer and use it in GitHub Desktop.
Implements a custom EntityRepository using Factory in Symfony2.6+
<?php
namespace App\Util\Doctrine\Repository;
use App\Util\Doctrine\Entity\AbstractEntity;
use Doctrine\Common\Persistence\ObjectRepository;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
/**
* Enhanced EntityRepository that pre-handle exceptions.
*
* @author Robin Chalas <rchalas@sutunam.com>
*/
class EnhancedRepository extends EntityRepository implements ObjectRepository
{
const NOT_FOUND_MESSAGE = 'The resource cannot be found';
const ALREADY_EXISTS_MESSAGE = 'A resource already exists';
/**
* Creates a new resource.
*
* @param array $properties
*
* @return object
*/
public function create(array $properties)
{
$entity = new $this->_entityName();
foreach ($properties as $property => $value) {
$setter = 'set'.ucfirst($property);
$entity->$setter($value);
}
$this->_em->persist($entity);
try {
$this->_em->flush();
} catch (Exception $e) {
throw new UnprocessableEntityHttpException($e->getMessage());
}
return $entity;
}
/**
* Updates an existing resource.
*
* @param AbstractEntity $entity
* @param array $properties
*
* @return object
*/
public function update(AbstractEntity $entity, array $properties)
{
foreach ($properties as $property => $value) {
$setter = 'set'.ucfirst($property);
$entity->$setter($value);
}
try {
$this->_em->flush();
} catch (Exception $e) {
throw new UnprocessableEntityHttpException($e->getMessage());
}
return $entity;
}
/**
* Deletes a given resource.
*
* @param AbstractEntity $entity
*
* @return void
*/
public function delete(AbstractEntity $entity)
{
$this->_em->remove($entity);
try {
$this->_em->flush();
} catch (Exception $e) {
throw new UnprocessableEntityHttpException($e->getMessage());
}
}
/**
* Finds a resource by identifier.
*
* @param int $id The resource identifier
* @param int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants or NULL
* @param int|null $lockVersion The lock version.
*
* @return object
*
* @throws NotFoundHttpException
*/
public function findOrFail($id, $lockMode = null, $lockVersion = null)
{
$entity = $this->find($id);
if (null === $entity) {
throw new NotFoundHttpException(
sprintf('%s (\'id\' = %d)', self::NOT_FOUND_MESSAGE, $id)
);
}
return $entity;
}
/**
* Find resource by criteria or fail.
*
* @param array $criteria
* @param array|null $orderBy
* @param int|null $limit
* @param int|null $offset
*
* @return object
*
* @throws NotFoundHttpException
*/
public function findByOrFail(array $criteria, array $orderBy = null, $limit = null, $offset = null)
{
$entities = $this->findBy($criteria, $orderBy);
if (count($entities) == 0) {
throw new NotFoundHttpException(
sprintf('%s (%s)', self::NOT_FOUND_MESSAGE, $this->implodeCriteria($criteria))
);
}
return $entities;
}
/**
* Find resource by criteria or create a new.
*
* @param array $criteria
* @param array|null $orderBy
*
* @return object
*
* @throws NotFoundHttpException
*/
public function findOneByOrFail(array $criteria, array $orderBy = null)
{
$entity = $this->findOneBy($criteria, $orderBy);
if (null === $entity) {
throw new NotFoundHttpException(
sprintf('%s (%s)', self::NOT_FOUND_MESSAGE, $this->implodeCriteria($criteria))
);
}
return $entity;
}
/**
* Check for existing resource by criteria and fail.
*
* @param array $criteria
* @param array|null $orderBy
*
* @return object
*
* @throws UnprocessableEntityHttpException
*/
public function findOneByAndFail(array $criteria, array $orderBy = null)
{
$entity = $this->findOneBy($criteria, $orderBy);
if (null !== $entity) {
throw new UnprocessableEntityHttpException(
sprintf('%s (%s)', self::ALREADY_EXISTS_MESSAGE, $this->implodeCriteria($criteria))
);
}
return $entity;
}
/**
* Find resource by criteria or create a new.
*
* @param array $criteria
* @param array|null $orderBy
*
* @return object
*/
public function findOneByOrCreate(array $criteria, array $orderBy = null)
{
$entity = $this->findOneBy($criteria, $orderBy);
if (null === $entity) {
return $this->create($criteria);
}
return $entity;
}
/**
* Get criteria as string.
*
* @param array $criteria The query criteria
*
* @return string
*/
private function implodeCriteria(array $criteria)
{
if (true === empty($criteria)) {
return 'no fields';
}
$keys = implode(', ', array_keys($criteria));
$values = implode(', ', array_values($criteria));
return sprintf("%s' = '%s'", $keys, $values);
}
}
<?php
namespace App\AcmeBundle;
use App\Util\DependencyInjection\Compiler\CompilerPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class AppSportBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new CompilerPass());
}
}
<?php
namespace App\Util\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
/**
* Repository factory CompilerPass.
*
* @author Robin Chalas <rchalas@sutunam.com>
*/
class CompilerPass implements CompilerPassInterface
{
/**
* Process compilation of containers.
*
* @param ContainerBuilder $container
*/
public function process(ContainerBuilder $container)
{
$factory = $container->findDefinition('app.doctrine.repository.factory');
$repositories = [];
foreach ($container->findTaggedServiceIds('app.repository') as $id => $params) {
foreach ($params as $param) {
$repositories[$param['class']] = $id;
$repository = $container->findDefinition($id);
$repository->replaceArgument(0, new Reference('doctrine.orm.default_entity_manager'));
$definition = new Definition();
$definition->setClass('Doctrine\ORM\Mapping\ClassMetadata');
$definition->setFactoryService('doctrine.orm.default_entity_manager');
$definition->setFactoryMethod('getClassMetadata');
$definition->setArguments([$param['class']]);
$repository->replaceArgument(1, $definition);
}
}
$factory->replaceArgument(0, $repositories);
$container->findDefinition('doctrine.orm.configuration')->addMethodCall('setRepositoryFactory', [$factory]);
}
}
<?php
namespace App\Util\Doctrine\Repository;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Repository\RepositoryFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Factory that set a default EntityRepository for specific entities.
*
* @author Robin Chalas <rchalas@sutunam.com>
*/
class EnhancedRepositoryFactory implements RepositoryFactory
{
private $repositories;
private $container;
private $default;
/**
* Constructor.
*
* @param array $repositories Registered repositories
* @param ContainerInterface $container
* @param RepositoryFactory $default
*/
public function __construct(array $repositories, ContainerInterface $container, RepositoryFactory $default)
{
$this->ids = $repositories;
$this->container = $container;
$this->default = $default;
}
/**
* {@inheritdoc}
*/
public function getRepository(EntityManagerInterface $entityManager, $entityName)
{
if (isset($this->ids[$entityName])) {
return $this->container->get($this->ids[$entityName]);
}
$metadata = $entityManager->getClassMetadata($entityName);
$entityNamespace = $metadata->getName();
if (is_subclass_of($entityNamespace, '\App\Util\Doctrine\Entity\EntityInterface', true)) {
return new EnhancedRepository($entityManager, $metadata);
}
return $this->default->getRepository($entityManager, $entityName);
}
}
services:
app.doctrine.repository.factory:
class: App\Util\Doctrine\Repository\EnhancedRepositoryFactory
arguments:
- []
- '@service_container'
- '@app.doctrine.repository.factory.default'
app.doctrine.repository.factory.default:
class: Doctrine\ORM\Repository\DefaultRepositoryFactory
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment