This commit is contained in:
Xes
2025-08-14 22:41:49 +02:00
parent 2de81ccc46
commit 8ce45119b6
39774 changed files with 4309466 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2010 Thomas Rabaix
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,84 @@
{
"name": "sonata-project/doctrine-extensions",
"description": "Doctrine2 behavioral extensions",
"license": "MIT",
"type": "library",
"keywords": [
"json",
"doctrine2",
"doctrine"
],
"authors": [
{
"name": "Thomas Rabaix",
"email": "thomas.rabaix@sonata-project.org",
"homepage": "http://sonata-project.org"
},
{
"name": "Sonata Community",
"homepage": "https://github.com/sonata-project/sonata-doctrine-extensions/contributors"
}
],
"homepage": "https://github.com/sonata-project/sonata-doctrine-extensions",
"require": {
"php": "^7.4 || ^8.0",
"doctrine/dbal": "^2.13 || ^3.1",
"doctrine/persistence": "^1.3.6 || ^2.0 || ^3.0"
},
"require-dev": {
"doctrine/common": "^2.7 || ^3.0",
"doctrine/doctrine-bundle": "^2.0",
"doctrine/mongodb-odm": "^2.0",
"doctrine/orm": "^2.5",
"friendsofphp/php-cs-fixer": "^3.4",
"jackalope/jackalope-doctrine-dbal": "^1.0",
"matthiasnoback/symfony-config-test": "^4.2",
"matthiasnoback/symfony-dependency-injection-test": "^4.0",
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "^1.1",
"phpstan/phpstan-phpunit": "^1.0",
"phpstan/phpstan-strict-rules": "^1.0",
"phpunit/phpunit": "^9.5.13",
"psalm/plugin-phpunit": "^0.16",
"rector/rector": "^0.13",
"symfony/dependency-injection": "^4.4 || ^5.4 || ^6.0",
"symfony/expression-language": "^4.4 || ^5.4 || ^6.0",
"symfony/framework-bundle": "^4.4 || ^5.4 || ^6.0",
"symfony/phpunit-bridge": "^6.1",
"vimeo/psalm": "^4.1"
},
"conflict": {
"doctrine/annotations": "<1.7",
"doctrine/doctrine-bundle": "<2.0",
"doctrine/mongodb-odm": "<2.0",
"doctrine/orm": "<2.5"
},
"suggest": {
"doctrine/orm": "If you use doctrine orm",
"doctrine/phpcr-odm": "If you use doctrine phpcr",
"symfony/framework-bundle": "If you want to use symfony"
},
"autoload": {
"psr-4": {
"Sonata\\Doctrine\\": "src/",
"Sonata\\Doctrine\\Bridge\\Symfony\\": "src/Bridge/Symfony/"
}
},
"autoload-dev": {
"psr-4": {
"Sonata\\Doctrine\\Tests\\": "tests/"
}
},
"config": {
"allow-plugins": {
"composer/package-versions-deprecated": true,
"phpstan/extension-installer": true
},
"sort-packages": true
},
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
}
}

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;
}
}