Actualización

This commit is contained in:
Xes
2025-04-10 12:24:57 +02:00
parent 8969cc929d
commit 45420b6f0d
39760 changed files with 4303286 additions and 0 deletions

View File

@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Doctrine\Adapter;
class AdapterChain implements AdapterInterface
{
/**
* @var AdapterInterface[]
*/
protected $adapters = [];
/**
* @return void
*/
public function addAdapter(AdapterInterface $adapter)
{
$this->adapters[] = $adapter;
}
public function getNormalizedIdentifier($model)
{
foreach ($this->adapters as $adapter) {
$identifier = $adapter->getNormalizedIdentifier($model);
if (null !== $identifier) {
return $identifier;
}
}
return null;
}
public function getUrlSafeIdentifier($model)
{
foreach ($this->adapters as $adapter) {
$safeIdentifier = $adapter->getUrlSafeIdentifier($model);
if (null !== $safeIdentifier) {
return $safeIdentifier;
}
}
return null;
}
}
class_exists(\Sonata\CoreBundle\Model\Adapter\AdapterChain::class);

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Doctrine\Adapter;
interface AdapterInterface
{
public const ID_SEPARATOR = '~';
/**
* Get the identifiers for this model class as a string.
*
* @param object $model
*
* @return string|null a string representation of the identifiers for this instance
*/
public function getNormalizedIdentifier($model);
/**
* Get the identifiers as a string that is save to use in an url.
*
* This is similar to getNormalizedIdentifier but guarantees an id that can
* be used in an URL.
*
* @param object $model
*
* @return string|null string representation of the id that is save to use in an url
*/
public function getUrlSafeIdentifier($model);
}
interface_exists(\Sonata\CoreBundle\Model\Adapter\AdapterInterface::class);

View File

@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Doctrine\Adapter\ORM;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ManagerRegistry;
use Sonata\Doctrine\Adapter\AdapterInterface;
/**
* This is a port of the DoctrineORMAdminBundle / ModelManager class.
*/
class DoctrineORMAdapter implements AdapterInterface
{
/**
* @var ManagerRegistry
*/
protected $registry;
public function __construct(ManagerRegistry $registry)
{
$this->registry = $registry;
}
public function getNormalizedIdentifier($model)
{
// NEXT_MAJOR: Remove this check and add type hint instead.
if (!\is_object($model)) {
if (null === $model) {
@trigger_error(sprintf(
'Passing other type than object as argument 1 for method %s() is deprecated since'
.' sonata-project/doctrine-extensions 1.15. It will accept only object in version 2.0.',
__METHOD__
), \E_USER_DEPRECATED);
return null;
}
throw new \RuntimeException(sprintf(
'Argument 1 passed to "%s()" must be an object, %s given.',
__METHOD__,
\gettype($model)
));
}
$manager = $this->registry->getManagerForClass(\get_class($model));
if (!$manager instanceof EntityManagerInterface) {
return null;
}
if (!$manager->getUnitOfWork()->isInIdentityMap($model)) {
return null;
}
return implode(self::ID_SEPARATOR, $manager->getUnitOfWork()->getEntityIdentifier($model));
}
/**
* {@inheritdoc}
*
* The ORM implementation does nothing special but you still should use
* this method when using the id in a URL to allow for future improvements.
*/
public function getUrlSafeIdentifier($model)
{
return $this->getNormalizedIdentifier($model);
}
}
class_exists(\Sonata\CoreBundle\Model\Adapter\DoctrineORMAdapter::class);

View File

@@ -0,0 +1,101 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Doctrine\Adapter\PHPCR;
use Doctrine\ODM\PHPCR\DocumentManager;
use Doctrine\ODM\PHPCR\Mapping\ClassMetadata;
use Doctrine\Persistence\ManagerRegistry;
use Sonata\Doctrine\Adapter\AdapterInterface;
@trigger_error(
'The Sonata\Doctrine\Adapter\PHPCR\DoctrinePHPCRAdapter class is deprecated'
.' since sonata-project/doctrine-extensions 1.17, to be removed in version 2.0.',
\E_USER_DEPRECATED
);
/**
* NEXT_MAJOR: Remove this class.
*
* @deprecated since 1.17 to be remove in 2.0.
*/
class DoctrinePHPCRAdapter implements AdapterInterface
{
/**
* @var ManagerRegistry
*/
protected $registry;
public function __construct(ManagerRegistry $registry)
{
$this->registry = $registry;
}
public function getNormalizedIdentifier($model)
{
// NEXT_MAJOR: Remove this check and add type hint instead.
if (!\is_object($model)) {
if (null === $model) {
@trigger_error(sprintf(
'Passing other type than object as argument 1 for method %s() is deprecated since'
.' sonata-project/doctrine-extensions 1.15. It will accept only object in version 2.0.',
__METHOD__
), \E_USER_DEPRECATED);
return null;
}
throw new \RuntimeException(sprintf(
'Argument 1 passed to "%s()" must be an object, %s given.',
__METHOD__,
\gettype($model)
));
}
$manager = $this->registry->getManagerForClass(\get_class($model));
if (!$manager instanceof DocumentManager) {
return null;
}
if (!$manager->contains($model)) {
return null;
}
$class = $manager->getClassMetadata(\get_class($model));
\assert($class instanceof ClassMetadata);
return $class->getIdentifierValue($model);
}
/**
* Currently only the leading slash is removed.
*
* TODO: do we also have to encode certain characters like spaces or does that happen automatically?
*
* {@inheritdoc}
*/
public function getUrlSafeIdentifier($model)
{
$id = $this->getNormalizedIdentifier($model);
if (null !== $id) {
return substr($id, 1);
}
return null;
}
}
class_exists(\Sonata\CoreBundle\Model\Adapter\DoctrinePHPCRAdapter::class);

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Doctrine\Bridge\Symfony\Bundle;
use Sonata\Doctrine\Bridge\Symfony\SonataDoctrineBundle as ForwardCompatibleSonataDoctrineBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
// @phpstan-ignore-next-line
if (false) {
/**
* NEXT_MAJOR: remove this class.
*
* @deprecated Since sonata-project/doctrine-extensions 1.9, to be removed in 2.0. Use Sonata\Doctrine\Bridge\Symfony\SonataDoctrineBundle instead.
*/
final class SonataDoctrineBundle extends Bundle
{
}
}
class_alias(ForwardCompatibleSonataDoctrineBundle::class, __NAMESPACE__.'\SonataDoctrineBundle');

View File

@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Doctrine\Bridge\Symfony\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
final class AdapterCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if (!$container->has('sonata.doctrine.model.adapter.chain')) {
return;
}
$definition = $container->findDefinition('sonata.doctrine.model.adapter.chain');
if ($this->isDoctrineOrmLoaded($container)) {
$definition->addMethodCall('addAdapter', [new Reference('sonata.doctrine.adapter.doctrine_orm')]);
} else {
$container->removeDefinition('sonata.doctrine.adapter.doctrine_orm');
}
// NEXT_MAJOR: Remove this code.
if ($this->isDoctrinePHPCRLoaded($container)) {
$definition->addMethodCall('addAdapter', [new Reference('sonata.doctrine.adapter.doctrine_phpcr')]);
} else {
$container->removeDefinition('sonata.doctrine.adapter.doctrine_phpcr');
}
}
private function isDoctrineOrmLoaded(ContainerBuilder $container): bool
{
return $container->has('doctrine') && $container->has('sonata.doctrine.adapter.doctrine_orm');
}
/**
* NEXT_MAJOR: Remove this method.
*/
private function isDoctrinePHPCRLoaded(ContainerBuilder $container): bool
{
return $container->has('doctrine_phpcr') && $container->has('sonata.doctrine.adapter.doctrine_phpcr');
}
}

View File

@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Doctrine\Bridge\Symfony\DependencyInjection\Compiler;
use Sonata\Doctrine\Mapper\DoctrineCollector;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
final class MapperCompilerPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container): void
{
if (!$this->isDoctrineOrmLoaded($container)) {
$container->removeDefinition('sonata.doctrine.mapper');
return;
}
$mapper = $container->getDefinition('sonata.doctrine.mapper');
$collector = DoctrineCollector::getInstance();
foreach ($collector->getAssociations() as $class => $associations) {
foreach ($associations as $type => $options) {
$mapper->addMethodCall('addAssociation', [$class, $type, $options]);
}
}
foreach ($collector->getDiscriminatorColumns() as $class => $columnDefinition) {
$mapper->addMethodCall('addDiscriminatorColumn', [$class, $columnDefinition]);
}
foreach ($collector->getDiscriminators() as $class => $discriminators) {
foreach ($discriminators as $key => $discriminatorClass) {
$mapper->addMethodCall('addDiscriminator', [$class, $key, $discriminatorClass]);
}
}
foreach ($collector->getInheritanceTypes() as $class => $type) {
$mapper->addMethodCall('addInheritanceType', [$class, $type]);
}
foreach ($collector->getIndexes() as $class => $indexes) {
foreach ($indexes as $field => $options) {
$mapper->addMethodCall('addIndex', [$class, $field, $options]);
}
}
foreach ($collector->getUniques() as $class => $uniques) {
foreach ($uniques as $field => $options) {
$mapper->addMethodCall('addUnique', [$class, $field, $options]);
}
}
foreach ($collector->getOverrides() as $class => $overrides) {
foreach ($overrides as $type => $options) {
$mapper->addMethodCall('addOverride', [$class, $type, $options]);
}
}
$collector->clear();
}
private function isDoctrineOrmLoaded(ContainerBuilder $container): bool
{
return $container->hasDefinition('doctrine') && $container->hasDefinition('sonata.doctrine.mapper');
}
}

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Doctrine\Bridge\Symfony\DependencyInjection;
use Doctrine\ODM\PHPCR\DocumentManager;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class SonataDoctrineExtension extends Extension
{
/**
* @param mixed[] $configs
*
* @return void
*/
public function load(array $configs, ContainerBuilder $container)
{
$loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.php');
if (interface_exists(EntityManagerInterface::class)) {
$loader->load('doctrine_orm.php');
$loader->load('mapper_orm.php');
}
$bundles = $container->getParameter('kernel.bundles');
\assert(\is_array($bundles));
// NEXT_MAJOR: Remove this
if (class_exists(DocumentManager::class) && isset($bundles['DoctrinePHPCRBundle'])) {
$loader->load('doctrine_phpcr.php');
}
}
}

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Sonata\Doctrine\Adapter\ORM\DoctrineORMAdapter;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\DependencyInjection\Loader\Configurator\ReferenceConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
// Use "service" function for creating references to services when dropping support for Symfony 4.4
$containerConfigurator->services()
->set('sonata.doctrine.adapter.doctrine_orm', DoctrineORMAdapter::class)
->args([
new ReferenceConfigurator('doctrine'),
]);
};

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Sonata\Doctrine\Adapter\PHPCR\DoctrinePHPCRAdapter;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\DependencyInjection\Loader\Configurator\ReferenceConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
// Use "service" function for creating references to services when dropping support for Symfony 4.4
$containerConfigurator->services()
->set('sonata.doctrine.adapter.doctrine_phpcr', DoctrinePHPCRAdapter::class)
->args([
new ReferenceConfigurator('doctrine_phpcr'),
]);
};

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Sonata\Doctrine\Mapper\ORM\DoctrineORMMapper;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$containerConfigurator->services()
->set('sonata.doctrine.mapper', DoctrineORMMapper::class)
->tag('doctrine.event_subscriber');
};

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Sonata\Doctrine\Adapter\AdapterChain;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$containerConfigurator->services()
->set('sonata.doctrine.model.adapter.chain', AdapterChain::class)
->public();
};

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Doctrine\Bridge\Symfony;
use Sonata\Doctrine\Bridge\Symfony\DependencyInjection\Compiler\AdapterCompilerPass;
use Sonata\Doctrine\Bridge\Symfony\DependencyInjection\Compiler\MapperCompilerPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
final class SonataDoctrineBundle extends Bundle
{
/**
* @return void
*/
public function build(ContainerBuilder $container)
{
$container->addCompilerPass(new AdapterCompilerPass());
$container->addCompilerPass(new MapperCompilerPass());
}
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Doctrine\Bridge\Symfony;
@trigger_error(sprintf(
'The %s\SonataDoctrineSymfonyBundle class is deprecated since sonata-project/doctrine-extensions 1.17, to be removed in version 2.0. Use %s instead.',
__NAMESPACE__,
SonataDoctrineBundle::class
), \E_USER_DEPRECATED);
class_alias(SonataDoctrineBundle::class, __NAMESPACE__.'\SonataDoctrineSymfonyBundle');

View File

@@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Doctrine\Document;
use Doctrine\ODM\MongoDB\DocumentManager;
use Sonata\Doctrine\Model\BaseManager;
/**
* @author Hugo Briand <briand@ekino.com>
*
* @phpstan-template T of object
* @phpstan-extends BaseManager<T>
*/
abstract class BaseDocumentManager extends BaseManager
{
/**
* Make sure the code is compatible with legacy code.
*
* NEXT_MAJOR: Remove the magic getter.
*
* @param string $name
*
* @return mixed
*/
public function __get($name)
{
if ('dm' === $name) {
@trigger_error(
'Accessing to the document manager through the magic getter is deprecated since'
.' sonata-project/sonata-doctrine-extensions 1.15 and will throw an exception in 2.0.'
.' Use the "getObjectManager()" method instead.',
\E_USER_DEPRECATED
);
return $this->getObjectManager();
}
throw new \RuntimeException(sprintf('The property %s does not exists', $name));
}
/**
* NEXT_MAJOR: Remove this method.
*
* @deprecated since sonata-project/sonata-doctrine-extensions 1.15
*/
public function getConnection()
{
throw new \LogicException('MongoDB does not use a database connection.');
}
/**
* @return DocumentManager
*/
public function getDocumentManager()
{
$dm = $this->getObjectManager();
\assert($dm instanceof DocumentManager);
return $dm;
}
}
class_exists(\Sonata\CoreBundle\Model\BaseDocumentManager::class);

View File

@@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Doctrine\Document;
use Doctrine\Persistence\ObjectManager;
use Sonata\Doctrine\Model\BaseManager;
@trigger_error(
'The Sonata\Doctrine\Document\BasePHPCRManager class is deprecated'
.' since sonata-project/doctrine-extensions 1.17x, to be removed in version 2.0.',
\E_USER_DEPRECATED
);
/**
* NEXT_MAJOR: Remove this class.
*
* @deprecated since 1.17 to be remove in 2.0.
*
* @phpstan-template T of object
* @phpstan-extends BaseManager<T>
*/
abstract class BasePHPCRManager extends BaseManager
{
/**
* Make sure the code is compatible with legacy code.
*
* NEXT_MAJOR: Remove the magic getter.
*
* @param string $name
*
* @return mixed
*/
public function __get($name)
{
if ('dm' === $name) {
@trigger_error(
'Accessing to the document manager through the magic getter is deprecated since'
.' sonata-project/sonata-doctrine-extensions 1.15 and will throw an exception in 2.0.'
.' Use the "getObjectManager()" method instead.',
\E_USER_DEPRECATED
);
return $this->getObjectManager();
}
throw new \RuntimeException(sprintf('The property %s does not exists', $name));
}
/**
* NEXT_MAJOR: Remove this method.
*
* @deprecated since sonata-project/sonata-doctrine-extensions 1.15
*/
public function getConnection()
{
throw new \LogicException('PHPCR does not use a database connection.');
}
/**
* NEXT_MAJOR: Remove this method.
*
* @deprecated since sonata-project/sonata-doctrine-extensions 1.15
*/
public function getTableName()
{
throw new \LogicException('PHPCR does not use a reference name for a list of data.');
}
/**
* @return ObjectManager
*/
public function getDocumentManager()
{
return $this->getObjectManager();
}
}
class_exists(\Sonata\CoreBundle\Model\BasePHPCRManager::class);

View File

@@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Doctrine\Entity;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Sonata\Doctrine\Exception\TransactionException;
use Sonata\Doctrine\Model\BaseManager;
use Sonata\Doctrine\Model\TransactionalManagerInterface;
/**
* @author Sylvain Deloux <sylvain.deloux@ekino.com>
*
* @phpstan-template T of object
* @phpstan-extends BaseManager<T>
*/
abstract class BaseEntityManager extends BaseManager implements TransactionalManagerInterface
{
/**
* Make sure the code is compatible with legacy code.
*
* NEXT_MAJOR: Remove the magic getter.
*
* @param string $name
*
* @return mixed
*/
public function __get($name)
{
if ('em' === $name) {
@trigger_error(
'Accessing to the entity manager through the magic getter is deprecated since'
.' sonata-project/sonata-doctrine-extensions 1.15 and will throw an exception in 2.0.'
.' Use the "getObjectManager()" method instead.',
\E_USER_DEPRECATED
);
return $this->getObjectManager();
}
throw new \RuntimeException(sprintf('The property %s does not exists', $name));
}
/**
* NEXT_MAJOR: Remove this method.
*
* @deprecated since sonata-project/sonata-doctrine-extensions 1.15
*/
public function getConnection()
{
@trigger_error(sprintf(
'The "%s()" method is deprecated since sonata-project/sonata-doctrine-extensions 1.15'
.' and will be removed in version 2.0. Use "%s" instead.',
__METHOD__,
'getEntityManager()->getConnection()'
), \E_USER_DEPRECATED);
return $this->getEntityManager()->getConnection();
}
/**
* @return EntityManagerInterface
*/
public function getEntityManager()
{
$objectManager = $this->getObjectManager();
\assert($objectManager instanceof EntityManagerInterface);
return $objectManager;
}
public function beginTransaction(): void
{
$this->getEntityManager()->beginTransaction();
}
public function commit(): void
{
try {
$this->getEntityManager()->commit();
} catch (\Throwable $exception) {
throw new TransactionException($exception->getMessage(), (int) $exception->getCode(), $exception);
}
}
public function rollBack(): void
{
$this->getEntityManager()->rollback();
}
/**
* @phpstan-return EntityRepository<T>
*/
protected function getRepository(): EntityRepository
{
return $this->getEntityManager()->getRepository($this->class);
}
}
class_exists(\Sonata\CoreBundle\Model\BaseEntityManager::class);

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Doctrine\Exception;
/**
* @author Erison Silva <erison.sdn@gmail.com>
*/
final class TransactionException extends \RuntimeException
{
}

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Doctrine\Mapper\Builder;
final class ColumnDefinitionBuilder
{
/**
* @var array<string, mixed>
*/
private array $options = [];
private function __construct()
{
}
public static function create(): self
{
return new self();
}
/**
* @param mixed $value
*
* @return $this
*/
public function add(string $key, $value): self
{
$this->options[$key] = $value;
return $this;
}
/**
* @return array<string, mixed>
*/
public function getOptions(): array
{
return $this->options;
}
}

View File

@@ -0,0 +1,239 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Doctrine\Mapper\Builder;
final class OptionsBuilder
{
private const ONE_TO_ONE = 'one_to_one';
private const ONE_TO_MANY = 'one_to_many';
private const MANY_TO_ONE = 'many_to_one';
private const MANY_TO_MANY = 'many_to_many';
/**
* @var array<string, mixed>
*/
private array $options = [];
private ?string $type;
/**
* NEXT_MAJOR: Make the arguments mandatory.
*/
private function __construct(?string $type = null, ?string $fieldName = null, ?string $targetEntity = null)
{
$this->type = $type;
if (null !== $fieldName) {
$this->options['fieldName'] = $fieldName;
}
if (null !== $targetEntity) {
$this->options['targetEntity'] = $targetEntity;
}
}
/**
* NEXT_MAJOR: Remove this method.
*
* @deprecated since sonata-project/doctrine-extensions 1.6, to be removed in 2.0.
*/
public static function create(): self
{
return new self();
}
/**
* @param mixed $value
*
* @return $this
*/
public function add(string $key, $value): self
{
$this->options[$key] = $value;
return $this;
}
public static function createOneToOne(string $fieldName, string $targetEntity): self
{
return new self(self::ONE_TO_ONE, $fieldName, $targetEntity);
}
public static function createOneToMany(string $fieldName, string $targetEntity): self
{
return new self(self::ONE_TO_MANY, $fieldName, $targetEntity);
}
public static function createManyToOne(string $fieldName, string $targetEntity): self
{
return new self(self::MANY_TO_ONE, $fieldName, $targetEntity);
}
public static function createManyToMany(string $fieldName, string $targetEntity): self
{
return new self(self::MANY_TO_MANY, $fieldName, $targetEntity);
}
/**
* @return $this
*/
public function mappedBy(string $mappedBy): self
{
if (!\in_array($this->type, [self::ONE_TO_MANY, self::ONE_TO_ONE, self::MANY_TO_MANY], true)) {
throw new \RuntimeException(
'Invalid option, mappedBy only applies to one-to-many and one-to-one associations'
);
}
$this->options['mappedBy'] = $mappedBy;
return $this;
}
/**
* @return $this
*/
public function inversedBy(string $inversedBy): self
{
if (!\in_array($this->type, [self::ONE_TO_ONE, self::MANY_TO_ONE, self::MANY_TO_MANY], true)) {
throw new \RuntimeException(
'Invalid option: inversedBy only applies to one-to-one, many-to-one or many-to-many associations'
);
}
$this->options['inversedBy'] = $inversedBy;
return $this;
}
/**
* @param array{
* name: string,
* referencedColumnName: string,
* unique?: bool,
* nullable?: bool,
* onDelete?: string,
* columnDefinition?: string
* }[] $joinColumns
* @param array{
* name: string,
* referencedColumnName: string,
* unique?: bool,
* nullable?: bool,
* onDelete?: string,
* columnDefinition?: string
* }[] $inverseJoinColumns
*/
public function addJoinTable(string $name, array $joinColumns, array $inverseJoinColumns): self
{
if (self::MANY_TO_MANY !== $this->type) {
throw new \RuntimeException('Invalid mapping, joinTables only apply to many-to-many associations');
}
$this->options['joinTable'] = [
'name' => $name,
'joinColumns' => $joinColumns,
'inverseJoinColumns' => $inverseJoinColumns,
];
return $this;
}
/**
* @param 'ASC'|'DESC' $orientation
*
* @return $this
*/
public function addOrder(string $field, string $orientation): self
{
if (!\in_array($this->type, [self::ONE_TO_MANY, self::MANY_TO_MANY], true)) {
throw new \RuntimeException(
'Invalid option: orderBy only applies to one-to-many or many-to-many associations'
);
}
if (!isset($this->options['orderBy'])) {
$this->options['orderBy'] = [];
}
$this->options['orderBy'] = array_merge($this->options['orderBy'], [$field => $orientation]);
return $this;
}
/**
* @param array{
* name: string,
* referencedColumnName: string,
* unique?: bool,
* nullable?: bool,
* onDelete?: string,
* columnDefinition?: string
* } $joinColumn
*
* @return $this
*/
public function addJoin(array $joinColumn): self
{
if (!\in_array($this->type, [self::MANY_TO_ONE, self::ONE_TO_ONE], true)) {
throw new \RuntimeException(
'Invalid option, joinColumns only apply to many-to-one and one-to-one associations'
);
}
if (!isset($this->options['joinColumns'])) {
$this->options['joinColumns'] = [];
}
$this->options['joinColumns'][] = $joinColumn;
return $this;
}
/**
* @return $this
*
* @psalm-param list<'persist'|'remove'|'merge'|'detach'|'refresh'|'all'> $value
*/
public function cascade(array $value): self
{
$this->options['cascade'] = $value;
return $this;
}
/**
* @return $this
*/
public function orphanRemoval(): self
{
if (!\in_array($this->type, [self::ONE_TO_ONE, self::ONE_TO_MANY], true)) {
throw new \RuntimeException(
'Invalid option, orphanRemoval only apply to one-to-one and one-to-many associations'
);
}
$this->options['orphanRemoval'] = true;
return $this;
}
/**
* @return array<string, mixed>
*/
public function getOptions(): array
{
return $this->options;
}
}

View File

@@ -0,0 +1,273 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Doctrine\Mapper;
use Sonata\Doctrine\Mapper\Builder\ColumnDefinitionBuilder;
use Sonata\Doctrine\Mapper\Builder\OptionsBuilder;
final class DoctrineCollector
{
/**
* @var array<class-string, array<string, array<array<string, mixed>>>>
*/
private array $associations = [];
/**
* @var array<class-string, array<string, array<string>>>
*/
private array $indexes = [];
/**
* @var array<class-string, array<string, array<string>>>
*/
private array $uniques = [];
/**
* @var array<class-string, array<string, class-string>>
*/
private array $discriminators = [];
/**
* @var array<class-string, array<string, mixed>>
*/
private array $discriminatorColumns = [];
/**
* @var array<class-string, int>
*/
private array $inheritanceTypes = [];
/**
* @var array<class-string, array<string, array<array<string, mixed>>>>
*/
private array $overrides = [];
private static ?DoctrineCollector $instance = null;
private function __construct()
{
}
/**
* @return DoctrineCollector
*/
public static function getInstance(): self
{
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Add a discriminator to a class.
*
* @param string $key Key is the database value and values are the classes
*
* @phpstan-param class-string $class
* @phpstan-param class-string $discriminatorClass
*/
public function addDiscriminator(string $class, string $key, string $discriminatorClass): void
{
if (!isset($this->discriminators[$class])) {
$this->discriminators[$class] = [];
}
if (!isset($this->discriminators[$class][$key])) {
$this->discriminators[$class][$key] = $discriminatorClass;
}
}
/**
* @phpstan-param class-string $class
*/
public function addDiscriminatorColumn(string $class, ColumnDefinitionBuilder $columnDef): void
{
if (!isset($this->discriminatorColumns[$class])) {
$this->discriminatorColumns[$class] = $columnDef->getOptions();
}
}
/**
* @param int $type
*
* @phpstan-param class-string $class
*/
public function addInheritanceType(string $class, $type): void
{
// NEXT_MAJOR: Move int check to method signature
if (!\is_int($type)) {
@trigger_error(sprintf(
'Passing other type than int as argument 2 for method %s() is deprecated since sonata-project/doctrine-extensions 1.8. It will accept only int in version 2.0.',
__METHOD__
), \E_USER_DEPRECATED);
}
if (!isset($this->inheritanceTypes[$class])) {
$this->inheritanceTypes[$class] = $type;
}
}
/**
* @phpstan-param class-string $class
*/
public function addAssociation(string $class, string $type, OptionsBuilder $options): void
{
if (!isset($this->associations[$class])) {
$this->associations[$class] = [];
}
if (!isset($this->associations[$class][$type])) {
$this->associations[$class][$type] = [];
}
$this->associations[$class][$type][] = $options->getOptions();
}
/**
* @param string[] $columns
*
* @phpstan-param class-string $class
*/
public function addIndex(string $class, string $name, array $columns): void
{
$this->verifyColumnNames($columns);
if (!isset($this->indexes[$class])) {
$this->indexes[$class] = [];
}
if (isset($this->indexes[$class][$name])) {
return;
}
$this->indexes[$class][$name] = $columns;
}
/**
* @param string[] $columns
*
* @phpstan-param class-string $class
*/
public function addUnique(string $class, string $name, array $columns): void
{
$this->verifyColumnNames($columns);
if (!isset($this->indexes[$class])) {
$this->uniques[$class] = [];
}
if (isset($this->uniques[$class][$name])) {
return;
}
$this->uniques[$class][$name] = $columns;
}
/**
* @phpstan-param class-string $class
*/
public function addOverride(string $class, string $type, OptionsBuilder $options): void
{
if (!isset($this->overrides[$class])) {
$this->overrides[$class] = [];
}
if (!isset($this->overrides[$class][$type])) {
$this->overrides[$class][$type] = [];
}
$this->overrides[$class][$type][] = $options->getOptions();
}
/**
* @return array<class-string, array<string, array<array<string, mixed>>>>
*/
public function getAssociations(): array
{
return $this->associations;
}
/**
* @return array<class-string, array<string, class-string>>
*/
public function getDiscriminators(): array
{
return $this->discriminators;
}
/**
* @return array<class-string, array<string, mixed>>
*/
public function getDiscriminatorColumns(): array
{
return $this->discriminatorColumns;
}
/**
* @return array<class-string, int>
*/
public function getInheritanceTypes(): array
{
return $this->inheritanceTypes;
}
/**
* @return array<class-string, array<string, array<string>>>
*/
public function getIndexes(): array
{
return $this->indexes;
}
/**
* @return array<class-string, array<string, array<string>>>
*/
public function getUniques(): array
{
return $this->uniques;
}
/**
* @return array<class-string, array<string, array<array<string, mixed>>>>
*/
public function getOverrides(): array
{
return $this->overrides;
}
public function clear(): void
{
$this->associations = [];
$this->indexes = [];
$this->uniques = [];
$this->discriminatorColumns = [];
$this->inheritanceTypes = [];
$this->discriminators = [];
$this->overrides = [];
}
/**
* @param string[] $columns
*/
private function verifyColumnNames(array $columns): void
{
foreach ($columns as $column) {
if (!\is_string($column)) {
throw new \InvalidArgumentException(sprintf('The column is not a valid string, %s given', \gettype($column)));
}
}
}
}

View File

@@ -0,0 +1,389 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Doctrine\Mapper\ORM;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata as ORMClassMetadata;
use Doctrine\Persistence\Event\LoadClassMetadataEventArgs;
use Doctrine\Persistence\Mapping\ClassMetadata;
final class DoctrineORMMapper implements EventSubscriber
{
/**
* @var array<class-string, array<string, array<array<string, mixed>>>>
*/
private array $associations = [];
/**
* @var array<class-string, array<string, class-string>>
*/
private array $discriminators = [];
/**
* @var array<class-string, array<string, mixed>>
*/
private array $discriminatorColumns = [];
/**
* @var array<class-string, int>
* @phpstan-var array<class-string, ORMClassMetadata::INHERITANCE_TYPE_*>
*/
private array $inheritanceTypes = [];
/**
* @var array<class-string, array<string, array<string>>>
*/
private array $indexes = [];
/**
* @var array<class-string, array<string, array<string>>>
*/
private array $uniques = [];
/**
* @var array<class-string, array<string, array<array<string, mixed>>>>
*/
private array $overrides = [];
public function getSubscribedEvents(): array
{
return [
'loadClassMetadata',
];
}
/**
* @param array<array<string, mixed>> $options
*
* @phpstan-param class-string $class
*/
public function addAssociation(string $class, string $type, $options): void
{
// NEXT_MAJOR: Move array check to method signature
if (!\is_array($options)) {
@trigger_error(sprintf(
'Passing other type than array as argument 3 for method %s() is deprecated since sonata-project/doctrine-extensions 1.8. It will accept only array in version 2.0.',
__METHOD__
), \E_USER_DEPRECATED);
}
if (!isset($this->associations[$class])) {
$this->associations[$class] = [];
}
$this->associations[$class][$type] = $options;
}
/**
* Add a discriminator to a class.
*
* @param string $key Key is the database value and values are the classes
*
* @phpstan-param class-string $class
* @phpstan-param class-string $discriminatorClass
*/
public function addDiscriminator(string $class, string $key, string $discriminatorClass): void
{
if (!isset($this->discriminators[$class])) {
$this->discriminators[$class] = [];
}
if (!isset($this->discriminators[$class][$key])) {
$this->discriminators[$class][$key] = $discriminatorClass;
}
}
/**
* @param array<string, mixed> $columnDef
*
* @phpstan-param class-string $class
*/
public function addDiscriminatorColumn(string $class, $columnDef): void
{
// NEXT_MAJOR: Move array check to method signature
if (!\is_array($columnDef)) {
@trigger_error(sprintf(
'Passing other type than array as argument 2 for method %s() is deprecated since sonata-project/doctrine-extensions 1.8. It will accept only array in version 2.0.',
__METHOD__
), \E_USER_DEPRECATED);
}
if (!isset($this->discriminatorColumns[$class])) {
$this->discriminatorColumns[$class] = $columnDef;
}
}
/**
* @phpstan-param class-string $class
* @phpstan-param ORMClassMetadata::INHERITANCE_TYPE_* $type
*
* @see ClassMetadata for supported types
*/
public function addInheritanceType(string $class, int $type): void
{
if (!isset($this->inheritanceTypes[$class])) {
$this->inheritanceTypes[$class] = $type;
}
}
/**
* @param string[] $columns
*
* @phpstan-param class-string $class
*/
public function addIndex(string $class, string $name, array $columns): void
{
$this->verifyColumnNames($columns);
if (!isset($this->indexes[$class])) {
$this->indexes[$class] = [];
}
if (isset($this->indexes[$class][$name])) {
return;
}
$this->indexes[$class][$name] = $columns;
}
/**
* @param string[] $columns
*
* @phpstan-param class-string $class
*/
public function addUnique(string $class, string $name, array $columns): void
{
$this->verifyColumnNames($columns);
if (!isset($this->uniques[$class])) {
$this->uniques[$class] = [];
}
if (isset($this->uniques[$class][$name])) {
return;
}
$this->uniques[$class][$name] = $columns;
}
/**
* @param array<array<string, mixed>> $options
*
* @phpstan-param class-string $class
*/
public function addOverride(string $class, string $type, $options): void
{
// NEXT_MAJOR: Move array check to method signature
if (!\is_array($options)) {
@trigger_error(sprintf(
'Passing other type than array as argument 3 for method %s() is deprecated since sonata-project/doctrine-extensions 1.8. It will accept only array in version 2.0.',
__METHOD__
), \E_USER_DEPRECATED);
}
if (!isset($this->overrides[$class])) {
$this->overrides[$class] = [];
}
$this->overrides[$class][$type] = $options;
}
/**
* @param LoadClassMetadataEventArgs<ClassMetadata<object>, EntityManagerInterface> $eventArgs
*/
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs): void
{
$metadata = $eventArgs->getClassMetadata();
$this->loadAssociations($metadata);
$this->loadIndexes($metadata);
$this->loadUniques($metadata);
$this->loadDiscriminatorColumns($metadata);
$this->loadDiscriminators($metadata);
$this->loadInheritanceTypes($metadata);
$this->loadOverrides($metadata);
}
/**
* @param ClassMetadata<object> $metadata
*
* @throws \RuntimeException
*/
private function loadAssociations(ClassMetadata $metadata): void
{
if (!\array_key_exists($metadata->getName(), $this->associations)) {
return;
}
try {
foreach ($this->associations[$metadata->getName()] as $type => $mappings) {
foreach ($mappings as $mapping) {
// the association is already set, skip the native one
if ($metadata->hasAssociation($mapping['fieldName'])) {
continue;
}
// @phpstan-ignore-next-line https://github.com/phpstan/phpstan/issues/1105
\call_user_func([$metadata, $type], $mapping);
}
}
} catch (\ReflectionException $e) {
throw new \RuntimeException(sprintf('Error with class %s : %s', $metadata->getName(), $e->getMessage()), 404, $e);
}
}
/**
* @param ClassMetadata<object> $metadata
*
* @throws \RuntimeException
*/
private function loadDiscriminatorColumns(ClassMetadata $metadata): void
{
if (!\array_key_exists($metadata->getName(), $this->discriminatorColumns)) {
return;
}
\assert($metadata instanceof ORMClassMetadata);
try {
if (isset($this->discriminatorColumns[$metadata->getName()])) {
$arrayDiscriminatorColumns = $this->discriminatorColumns[$metadata->getName()];
if (isset($metadata->discriminatorColumn)) {
$arrayDiscriminatorColumns = array_merge($metadata->discriminatorColumn, $this->discriminatorColumns[$metadata->getName()]);
}
$metadata->setDiscriminatorColumn($arrayDiscriminatorColumns);
}
} catch (\ReflectionException $e) {
throw new \RuntimeException(sprintf('Error with class %s : %s', $metadata->getName(), $e->getMessage()), 404, $e);
}
}
/**
* @param ClassMetadata<object> $metadata
*
* @throws \RuntimeException
*/
private function loadInheritanceTypes(ClassMetadata $metadata): void
{
if (!\array_key_exists($metadata->getName(), $this->inheritanceTypes)) {
return;
}
\assert($metadata instanceof ORMClassMetadata);
try {
if (isset($this->inheritanceTypes[$metadata->getName()])) {
$metadata->setInheritanceType($this->inheritanceTypes[$metadata->getName()]);
}
} catch (\ReflectionException $e) {
throw new \RuntimeException(sprintf('Error with class %s : %s', $metadata->getName(), $e->getMessage()), 404, $e);
}
}
/**
* @param ClassMetadata<object> $metadata
*
* @throws \RuntimeException
*/
private function loadDiscriminators(ClassMetadata $metadata): void
{
if (!\array_key_exists($metadata->getName(), $this->discriminators)) {
return;
}
\assert($metadata instanceof ORMClassMetadata);
try {
foreach ($this->discriminators[$metadata->getName()] as $key => $class) {
if (\in_array($key, $metadata->discriminatorMap, true)) {
continue;
}
$metadata->setDiscriminatorMap([$key => $class]);
}
} catch (\ReflectionException $e) {
throw new \RuntimeException(sprintf('Error with class %s : %s', $metadata->getName(), $e->getMessage()), 404, $e);
}
}
/**
* @param ClassMetadata<object> $metadata
*/
private function loadIndexes(ClassMetadata $metadata): void
{
if (!\array_key_exists($metadata->getName(), $this->indexes)) {
return;
}
\assert($metadata instanceof ORMClassMetadata);
foreach ($this->indexes[$metadata->getName()] as $name => $columns) {
$metadata->table['indexes'][$name] = ['columns' => $columns];
}
}
/**
* @param ClassMetadata<object> $metadata
*/
private function loadUniques(ClassMetadata $metadata): void
{
if (!\array_key_exists($metadata->getName(), $this->uniques)) {
return;
}
\assert($metadata instanceof ORMClassMetadata);
foreach ($this->uniques[$metadata->getName()] as $name => $columns) {
$metadata->table['uniqueConstraints'][$name] = ['columns' => $columns];
}
}
/**
* @param ClassMetadata<object> $metadata
*/
private function loadOverrides(ClassMetadata $metadata): void
{
if (!\array_key_exists($metadata->getName(), $this->overrides)) {
return;
}
try {
foreach ($this->overrides[$metadata->getName()] as $type => $overrides) {
foreach ($overrides as $override) {
// @phpstan-ignore-next-line https://github.com/phpstan/phpstan/issues/1105
\call_user_func([$metadata, $type], $override['fieldName'], $override);
}
}
} catch (\ReflectionException $e) {
throw new \RuntimeException(
sprintf('Error with class %s : %s', $metadata->getName(), $e->getMessage()),
404,
$e
);
}
}
/**
* @param string[] $columns
*/
private function verifyColumnNames(array $columns): void
{
foreach ($columns as $column) {
if (!\is_string($column)) {
throw new \InvalidArgumentException(sprintf('The column is not a valid string, %s given', \gettype($column)));
}
}
}
}

View File

@@ -0,0 +1,203 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Doctrine\Model;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Persistence\ObjectManager;
use Doctrine\Persistence\ObjectRepository;
/**
* @author Hugo Briand <briand@ekino.com>
*
* @phpstan-template T of object
* @phpstan-implements ManagerInterface<T>
*/
abstract class BaseManager implements ManagerInterface, ClearableManagerInterface
{
/**
* @var ManagerRegistry
*/
protected $registry;
/**
* @var string
*
* @phpstan-var class-string<T>
*/
protected $class;
/**
* @param string $class
*
* @phpstan-param class-string<T> $class
*/
public function __construct($class, ManagerRegistry $registry)
{
$this->registry = $registry;
$this->class = $class;
}
/**
* @return ObjectManager
*
* @throws \RuntimeException
*/
public function getObjectManager()
{
$manager = $this->registry->getManagerForClass($this->class);
if (null === $manager) {
throw new \RuntimeException(sprintf(
'Unable to find the mapping information for the class %s.'
.' Please check the `auto_mapping` option'
.' (http://symfony.com/doc/current/reference/configuration/doctrine.html#configuration-overview)'
.' or add the bundle to the `mappings` section in the doctrine configuration.',
$this->class
));
}
return $manager;
}
public function getClass()
{
return $this->class;
}
public function findAll()
{
return $this->getRepository()->findAll();
}
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null)
{
return $this->getRepository()->findBy($criteria, $orderBy, $limit, $offset);
}
public function findOneBy(array $criteria, ?array $orderBy = null)
{
if (null !== $orderBy) {
@trigger_error(
'The $orderBy argument of '.__METHOD__.' is deprecated since sonata-project/doctrine-extensions 1.4, to be removed in 2.0.',
\E_USER_DEPRECATED
);
}
return $this->getRepository()->findOneBy($criteria);
}
public function find($id)
{
return $this->getRepository()->find($id);
}
public function create()
{
return new $this->class();
}
public function save($entity, $andFlush = true)
{
$this->checkObject($entity);
$this->getObjectManager()->persist($entity);
if ($andFlush) {
$this->getObjectManager()->flush();
}
}
public function delete($entity, $andFlush = true)
{
$this->checkObject($entity);
$this->getObjectManager()->remove($entity);
if ($andFlush) {
$this->getObjectManager()->flush();
}
}
/**
* NEXT_MAJOR: Remove this method.
*
* @deprecated since sonata-project/sonata-doctrine-extensions 1.15
*/
public function getTableName()
{
@trigger_error(sprintf(
'The "%s()" method is deprecated since sonata-project/sonata-doctrine-extensions 1.15'
.' and will be removed in version 2.0.',
__METHOD__
), \E_USER_DEPRECATED);
$metadata = $this->getObjectManager()->getClassMetadata($this->class);
\assert($metadata instanceof ClassMetadataInfo);
return $metadata->table['name'];
}
/**
* NEXT_MAJOR: Remove $objectName parameter and argument along with psalm and phpstan suppressions.
*
* @psalm-suppress TooManyArguments
*/
public function clear(?string $objectName = null): void
{
if (\func_num_args() > 0) {
@trigger_error(sprintf(
'Passing an argument to "%s()" method is deprecated since sonata-project/sonata-doctrine-extensions 1.17.',
__METHOD__
), \E_USER_DEPRECATED);
}
// @phpstan-ignore-next-line
$this->getObjectManager()->clear($objectName);
}
/**
* Returns the related Object Repository.
*
* @return ObjectRepository<object>
*
* @phpstan-return ObjectRepository<T>
*/
protected function getRepository()
{
return $this->getObjectManager()->getRepository($this->class);
}
/**
* @param object $object
*
* @return void
*
* @throws \InvalidArgumentException
*
* @phpstan-param T $object
*/
protected function checkObject($object)
{
if (!$object instanceof $this->class) {
throw new \InvalidArgumentException(sprintf(
'Object must be instance of %s, %s given',
$this->class,
\is_object($object) ? \get_class($object) : \gettype($object)
));
}
}
}
class_exists(\Sonata\CoreBundle\Model\BaseManager::class);

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Doctrine\Model;
/**
* @author Jordi Sala <jordism91@gmail.com>
*/
interface ClearableManagerInterface
{
/**
* NEXT_MAJOR: Remove $objectName parameter.
*
* Clears the object manager for the given model.
*/
public function clear(?string $objectName = null): void;
}

View File

@@ -0,0 +1,132 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Doctrine\Model;
use Doctrine\DBAL\Connection;
/**
* @author Sylvain Deloux <sylvain.deloux@ekino.com>
*
* @phpstan-template T of object
*/
interface ManagerInterface
{
/**
* Return the Entity class name.
*
* @return string
*
* @phpstan-return class-string<T>
*/
public function getClass();
/**
* Find all entities in the repository.
*
* @return object[]
*
* @phpstan-return T[]
*/
public function findAll();
/**
* Find entities by a set of criteria.
*
* @param array<string, mixed> $criteria
* @param array<string, 'asc'|'ASC'|'desc'|'DESC'>|null $orderBy
* @param int|null $limit
* @param int|null $offset
*
* @return object[]
*
* @phpstan-return T[]
*/
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null);
/**
* Find a single entity by a set of criteria.
*
* @param array<string, mixed> $criteria
* @param array<string, string>|null $orderBy
*
* @return object|null
*
* @phpstan-return T|null
*/
public function findOneBy(array $criteria, ?array $orderBy = null);
/**
* Finds an entity by its primary key / identifier.
*
* @param mixed $id The identifier
*
* @return object|null
*
* @phpstan-return T|null
*/
public function find($id);
/**
* Create an empty Entity instance.
*
* @return object
*
* @phpstan-return T
*/
public function create();
/**
* Save an Entity.
*
* @param object $entity The Entity to save
* @param bool $andFlush Flush the EntityManager after saving the object?
*
* @return void
*
* @phpstan-param T $entity
*/
public function save($entity, $andFlush = true);
/**
* Delete an Entity.
*
* @param object $entity The Entity to delete
* @param bool $andFlush Flush the EntityManager after deleting the object?
*
* @return void
*
* @phpstan-param T $entity
*/
public function delete($entity, $andFlush = true);
/**
* Get the related table name.
*
* NEXT_MAJOR: Remove this ORM-related method from the interface.
*
* @return string
*/
public function getTableName();
/**
* Get the DB driver connection.
*
* NEXT_MAJOR: Remove this ORM-related method from the interface.
*
* @return Connection
*/
public function getConnection();
}
interface_exists(\Sonata\CoreBundle\Model\ManagerInterface::class);

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Doctrine\Model;
use Sonata\DatagridBundle\Pager\PagerInterface;
/**
* @author Raphaël Benitte <benitteraphael@gmail.com>
*
* NEXT_MAJOR: Remove this interface.
*
* @deprecated since 1.3, to be removed in 2.0. Use Sonata\DatagridBundle\Pager\PageableInterface instead.
*/
interface PageableManagerInterface
{
/**
* @param array<string, mixed> $criteria
* @param array<string, string> $sort
* @param int $page
* @param int $limit
*
* @return PagerInterface
*/
public function getPager(array $criteria, $page, $limit = 10, array $sort = []);
}
interface_exists(\Sonata\CoreBundle\Model\PageableManagerInterface::class);

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Doctrine\Model;
use Sonata\Doctrine\Exception\TransactionException;
/**
* @author Erison Silva <erison.sdn@gmail.com>
*/
interface TransactionalManagerInterface
{
public function beginTransaction(): void;
/**
* @throws TransactionException
*/
public function commit(): void;
public function rollBack(): void;
}

View File

@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Doctrine\Test;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\Mapping\ClassMetadata;
use PHPUnit\Framework\TestCase;
/**
* NEXT_MAJOR: Remove this class.
*
* @deprecated since sonata-project/doctrine-extensions 1.5, to be removed in 2.0.
*/
class EntityManagerMockFactory
{
/**
* @param string[] $fields
*
* @return EntityManagerInterface
*/
public static function create(TestCase $test, \Closure $qbCallback, $fields)
{
$query = $test->createMock(AbstractQuery::class);
$query->method('execute')->willReturn(true);
$qb = $test->createMock(QueryBuilder::class);
$qb->method('select')->willReturn($qb);
$qb->method('getQuery')->willReturn($query);
$qb->method('where')->willReturn($qb);
$qb->method('orderBy')->willReturn($qb);
$qb->method('andWhere')->willReturn($qb);
$qb->method('leftJoin')->willReturn($qb);
$qbCallback($qb);
$repository = $test->createMock(EntityRepository::class);
$repository->method('createQueryBuilder')->willReturn($qb);
$metadata = $test->createMock(ClassMetadata::class);
$metadata->method('getFieldNames')->willReturn($fields);
$metadata->method('getName')->willReturn('className');
$em = $test->createMock(EntityManager::class);
$em->method('getRepository')->willReturn($repository);
$em->method('getClassMetadata')->willReturn($metadata);
return $em;
}
}

View File

@@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Doctrine\Test;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\Mapping\ClassMetadata;
use PHPUnit\Framework\MockObject\MockObject;
/**
* NEXT_MAJOR: Remove this class.
*
* @deprecated since sonata-project/doctrine-extensions 1.x, to be removed in 2.0.
*/
trait EntityManagerMockFactoryTrait
{
/**
* @param string[] $fields
*
* @return EntityManagerInterface|MockObject
*/
final protected function createEntityManagerMock(\Closure $qbCallback, array $fields): MockObject
{
$query = $this->createMock(AbstractQuery::class);
$query->method('execute')->willReturn(true);
$query->method('getResult')->willReturn([]);
$query->method('getOneOrNullResult')->willReturn(null);
$qb = $this->createMock(QueryBuilder::class);
$qb->method('getQuery')->willReturn($query);
$qb->method('distinct')->willReturn($qb);
$qb->method('from')->willReturn($qb);
$qb->method('select')->willReturn($qb);
$qb->method('addSelect')->willReturn($qb);
$qb->method('where')->willReturn($qb);
$qb->method('andWhere')->willReturn($qb);
$qb->method('orWhere')->willReturn($qb);
$qb->method('setParameter')->willReturn($qb);
$qb->method('setParameters')->willReturn($qb);
$qb->method('setFirstResult')->willReturn($qb);
$qb->method('setMaxResults')->willReturn($qb);
$qb->method('groupBy')->willReturn($qb);
$qb->method('addGroupBy')->willReturn($qb);
$qb->method('having')->willReturn($qb);
$qb->method('andHaving')->willReturn($qb);
$qb->method('orHaving')->willReturn($qb);
$qb->method('orderBy')->willReturn($qb);
$qb->method('addOrderBy')->willReturn($qb);
$qb->method('join')->willReturn($qb);
$qb->method('innerJoin')->willReturn($qb);
$qb->method('leftJoin')->willReturn($qb);
$qbCallback($qb);
$repository = $this->createMock(EntityRepository::class);
$repository->method('createQueryBuilder')->willReturn($qb);
$metadata = $this->createMock(ClassMetadata::class);
$metadata->method('getFieldNames')->willReturn($fields);
$metadata->method('getName')->willReturn('className');
$em = $this->createMock(EntityManager::class);
$em->method('getRepository')->willReturn($repository);
$em->method('getClassMetadata')->willReturn($metadata);
return $em;
}
}

View File

@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Doctrine\Types;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Type;
/**
* NEXT_MAJOR: Remove this class.
*
* Convert a value into a json string to be stored into the persistency layer.
*
* @deprecated since sonata-project/doctrine-extensions 1.2, to be removed in 2.0. Use JsonType from Doctrine DBAL instead.
*/
class JsonType extends Type
{
public const JSON = 'json';
public function convertToPHPValue($value, AbstractPlatform $platform)
{
if (null === $value) {
return null;
}
return json_decode((string) $value, true, 512, \JSON_THROW_ON_ERROR);
}
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
if (null === $value) {
return null;
}
return json_encode($value, \JSON_THROW_ON_ERROR);
}
public function getName()
{
return self::JSON;
}
public function getSQLDeclaration(array $column, AbstractPlatform $platform)
{
return $platform->getClobTypeDeclarationSQL($column);
}
public function requiresSQLCommentHint(AbstractPlatform $platform)
{
return true;
}
}