ヤミRoot VoidGate
User / IP
:
216.73.216.143
Host / Server
:
146.88.233.70 / dev.loger.cm
System
:
Linux hybrid1120.fr.ns.planethoster.net 3.10.0-957.21.2.el7.x86_64 #1 SMP Wed Jun 5 14:26:44 UTC 2019 x86_64
Command
|
Upload
|
Create
Mass Deface
|
Jumping
|
Symlink
|
Reverse Shell
Ping
|
Port Scan
|
DNS Lookup
|
Whois
|
Header
|
cURL
:
/
home
/
logercm
/
dev.loger.cm
/
fixtures
/
assert
/
Viewing: doctrine-bridge.tar
CacheWarmer/ProxyCacheWarmer.php 0000644 00000004732 15120140637 0012666 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\CacheWarmer; use Doctrine\Persistence\ManagerRegistry; use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; /** * The proxy generator cache warmer generates all entity proxies. * * In the process of generating proxies the cache for all the metadata is primed also, * since this information is necessary to build the proxies in the first place. * * @author Benjamin Eberlei <kontakt@beberlei.de> */ class ProxyCacheWarmer implements CacheWarmerInterface { private $registry; public function __construct(ManagerRegistry $registry) { $this->registry = $registry; } /** * This cache warmer is not optional, without proxies fatal error occurs! * * @return bool */ public function isOptional() { return false; } /** * {@inheritdoc} * * @return string[] A list of files to preload on PHP 7.4+ */ public function warmUp(string $cacheDir) { $files = []; foreach ($this->registry->getManagers() as $em) { // we need the directory no matter the proxy cache generation strategy if (!is_dir($proxyCacheDir = $em->getConfiguration()->getProxyDir())) { if (false === @mkdir($proxyCacheDir, 0777, true) && !is_dir($proxyCacheDir)) { throw new \RuntimeException(sprintf('Unable to create the Doctrine Proxy directory "%s".', $proxyCacheDir)); } } elseif (!is_writable($proxyCacheDir)) { throw new \RuntimeException(sprintf('The Doctrine Proxy directory "%s" is not writeable for the current system user.', $proxyCacheDir)); } // if proxies are autogenerated we don't need to generate them in the cache warmer if ($em->getConfiguration()->getAutoGenerateProxyClasses()) { continue; } $classes = $em->getMetadataFactory()->getAllMetadata(); $em->getProxyFactory()->generateProxyClasses($classes); foreach (scandir($proxyCacheDir) as $file) { if (!is_dir($file = $proxyCacheDir.'/'.$file)) { $files[] = $file; } } } return $files; } } DataCollector/DoctrineDataCollector.php 0000644 00000016623 15120140637 0014214 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\DataCollector; use Doctrine\DBAL\Logging\DebugStack; use Doctrine\DBAL\Types\ConversionException; use Doctrine\DBAL\Types\Type; use Doctrine\Persistence\ManagerRegistry; use Symfony\Bridge\Doctrine\Middleware\Debug\DebugDataHolder; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\DataCollector\DataCollector; use Symfony\Component\VarDumper\Caster\Caster; use Symfony\Component\VarDumper\Cloner\Stub; /** * DoctrineDataCollector. * * @author Fabien Potencier <fabien@symfony.com> */ class DoctrineDataCollector extends DataCollector { private $registry; private $connections; private $managers; private $debugDataHolder; /** * @var DebugStack[] */ private $loggers = []; public function __construct(ManagerRegistry $registry, DebugDataHolder $debugDataHolder = null) { $this->registry = $registry; $this->connections = $registry->getConnectionNames(); $this->managers = $registry->getManagerNames(); $this->debugDataHolder = $debugDataHolder; } /** * Adds the stack logger for a connection. */ public function addLogger(string $name, DebugStack $logger) { $this->loggers[$name] = $logger; } /** * {@inheritdoc} */ public function collect(Request $request, Response $response, \Throwable $exception = null) { $this->data = [ 'queries' => $this->collectQueries(), 'connections' => $this->connections, 'managers' => $this->managers, ]; } private function collectQueries(): array { $queries = []; if (null !== $this->debugDataHolder) { foreach ($this->debugDataHolder->getData() as $name => $data) { $queries[$name] = $this->sanitizeQueries($name, $data); } return $queries; } foreach ($this->loggers as $name => $logger) { $queries[$name] = $this->sanitizeQueries($name, $logger->queries); } return $queries; } public function reset() { $this->data = []; if (null !== $this->debugDataHolder) { $this->debugDataHolder->reset(); return; } foreach ($this->loggers as $logger) { $logger->queries = []; $logger->currentQuery = 0; } } public function getManagers() { return $this->data['managers']; } public function getConnections() { return $this->data['connections']; } public function getQueryCount() { return array_sum(array_map('count', $this->data['queries'])); } public function getQueries() { return $this->data['queries']; } public function getTime() { $time = 0; foreach ($this->data['queries'] as $queries) { foreach ($queries as $query) { $time += $query['executionMS']; } } return $time; } /** * {@inheritdoc} */ public function getName() { return 'db'; } /** * {@inheritdoc} */ protected function getCasters() { return parent::getCasters() + [ ObjectParameter::class => static function (ObjectParameter $o, array $a, Stub $s): array { $s->class = $o->getClass(); $s->value = $o->getObject(); $r = new \ReflectionClass($o->getClass()); if ($f = $r->getFileName()) { $s->attr['file'] = $f; $s->attr['line'] = $r->getStartLine(); } else { unset($s->attr['file']); unset($s->attr['line']); } if ($error = $o->getError()) { return [Caster::PREFIX_VIRTUAL.'⚠' => $error->getMessage()]; } if ($o->isStringable()) { return [Caster::PREFIX_VIRTUAL.'__toString()' => (string) $o->getObject()]; } return [Caster::PREFIX_VIRTUAL.'⚠' => sprintf('Object of class "%s" could not be converted to string.', $o->getClass())]; }, ]; } private function sanitizeQueries(string $connectionName, array $queries): array { foreach ($queries as $i => $query) { $queries[$i] = $this->sanitizeQuery($connectionName, $query); } return $queries; } private function sanitizeQuery(string $connectionName, array $query): array { $query['explainable'] = true; $query['runnable'] = true; if (null === $query['params']) { $query['params'] = []; } if (!\is_array($query['params'])) { $query['params'] = [$query['params']]; } if (!\is_array($query['types'])) { $query['types'] = []; } foreach ($query['params'] as $j => $param) { $e = null; if (isset($query['types'][$j])) { // Transform the param according to the type $type = $query['types'][$j]; if (\is_string($type)) { $type = Type::getType($type); } if ($type instanceof Type) { $query['types'][$j] = $type->getBindingType(); try { $param = $type->convertToDatabaseValue($param, $this->registry->getConnection($connectionName)->getDatabasePlatform()); } catch (\TypeError $e) { } catch (ConversionException $e) { } } } [$query['params'][$j], $explainable, $runnable] = $this->sanitizeParam($param, $e); if (!$explainable) { $query['explainable'] = false; } if (!$runnable) { $query['runnable'] = false; } } $query['params'] = $this->cloneVar($query['params']); return $query; } /** * Sanitizes a param. * * The return value is an array with the sanitized value and a boolean * indicating if the original value was kept (allowing to use the sanitized * value to explain the query). */ private function sanitizeParam($var, ?\Throwable $error): array { if (\is_object($var)) { return [$o = new ObjectParameter($var, $error), false, $o->isStringable() && !$error]; } if ($error) { return ['⚠ '.$error->getMessage(), false, false]; } if (\is_array($var)) { $a = []; $explainable = $runnable = true; foreach ($var as $k => $v) { [$value, $e, $r] = $this->sanitizeParam($v, null); $explainable = $explainable && $e; $runnable = $runnable && $r; $a[$k] = $value; } return [$a, $explainable, $runnable]; } if (\is_resource($var)) { return [sprintf('/* Resource(%s) */', get_resource_type($var)), false, false]; } return [$var, true, true]; } } DataCollector/ObjectParameter.php 0000644 00000001750 15120140637 0013046 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\DataCollector; final class ObjectParameter { private $object; private $error; private $stringable; private $class; public function __construct(object $object, ?\Throwable $error) { $this->object = $object; $this->error = $error; $this->stringable = \is_callable([$object, '__toString']); $this->class = \get_class($object); } public function getObject(): object { return $this->object; } public function getError(): ?\Throwable { return $this->error; } public function isStringable(): bool { return $this->stringable; } public function getClass(): string { return $this->class; } } DataFixtures/ContainerAwareLoader.php 0000644 00000002315 15120140637 0013711 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\DataFixtures; use Doctrine\Common\DataFixtures\FixtureInterface; use Doctrine\Common\DataFixtures\Loader; use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** * Doctrine data fixtures loader that injects the service container into * fixture objects that implement ContainerAwareInterface. * * Note: Use of this class requires the Doctrine data fixtures extension, which * is a suggested dependency for Symfony. */ class ContainerAwareLoader extends Loader { private $container; public function __construct(ContainerInterface $container) { $this->container = $container; } /** * {@inheritdoc} */ public function addFixture(FixtureInterface $fixture) { if ($fixture instanceof ContainerAwareInterface) { $fixture->setContainer($this->container); } parent::addFixture($fixture); } } DependencyInjection/CompilerPass/DoctrineValidationPass.php 0000644 00000003776 15120140637 0020224 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; /** * Registers additional validators. * * @author Benjamin Eberlei <kontakt@beberlei.de> */ class DoctrineValidationPass implements CompilerPassInterface { private $managerType; public function __construct(string $managerType) { $this->managerType = $managerType; } /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { $this->updateValidatorMappingFiles($container, 'xml', 'xml'); $this->updateValidatorMappingFiles($container, 'yaml', 'yml'); } /** * Gets the validation mapping files for the format and extends them with * files matching a doctrine search pattern (Resources/config/validation.orm.xml). */ private function updateValidatorMappingFiles(ContainerBuilder $container, string $mapping, string $extension) { if (!$container->hasParameter('validator.mapping.loader.'.$mapping.'_files_loader.mapping_files')) { return; } $files = $container->getParameter('validator.mapping.loader.'.$mapping.'_files_loader.mapping_files'); $validationPath = '/config/validation.'.$this->managerType.'.'.$extension; foreach ($container->getParameter('kernel.bundles_metadata') as $bundle) { if ($container->fileExists($file = $bundle['path'].'/Resources'.$validationPath) || $container->fileExists($file = $bundle['path'].$validationPath)) { $files[] = $file; } } $container->setParameter('validator.mapping.loader.'.$mapping.'_files_loader.mapping_files', $files); } } DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php 0000644 00000014744 15120140637 0023750 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass; use Symfony\Bridge\Doctrine\ContainerAwareEventManager; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Reference; /** * Registers event listeners and subscribers to the available doctrine connections. * * @author Jeremy Mikola <jmikola@gmail.com> * @author Alexander <iam.asm89@gmail.com> * @author David Maicher <mail@dmaicher.de> */ class RegisterEventListenersAndSubscribersPass implements CompilerPassInterface { private $connections; private $eventManagers; private $managerTemplate; private $tagPrefix; /** * @param string $connections Parameter ID for connections * @param string $managerTemplate sprintf() template for generating the event * manager's service ID for a connection name * @param string $tagPrefix Tag prefix for listeners and subscribers */ public function __construct(string $connections, string $managerTemplate, string $tagPrefix) { $this->connections = $connections; $this->managerTemplate = $managerTemplate; $this->tagPrefix = $tagPrefix; } /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { if (!$container->hasParameter($this->connections)) { return; } $this->connections = $container->getParameter($this->connections); $listenerRefs = $this->addTaggedServices($container); // replace service container argument of event managers with smaller service locator // so services can even remain private foreach ($listenerRefs as $connection => $refs) { $this->getEventManagerDef($container, $connection) ->replaceArgument(0, ServiceLocatorTagPass::register($container, $refs)); } } private function addTaggedServices(ContainerBuilder $container): array { $listenerTag = $this->tagPrefix.'.event_listener'; $subscriberTag = $this->tagPrefix.'.event_subscriber'; $listenerRefs = []; $taggedServices = $this->findAndSortTags([$subscriberTag, $listenerTag], $container); $managerDefs = []; foreach ($taggedServices as $taggedSubscriber) { [$tagName, $id, $tag] = $taggedSubscriber; $connections = isset($tag['connection']) ? [$container->getParameterBag()->resolveValue($tag['connection'])] : array_keys($this->connections); if ($listenerTag === $tagName && !isset($tag['event'])) { throw new InvalidArgumentException(sprintf('Doctrine event listener "%s" must specify the "event" attribute.', $id)); } foreach ($connections as $con) { if (!isset($this->connections[$con])) { throw new RuntimeException(sprintf('The Doctrine connection "%s" referenced in service "%s" does not exist. Available connections names: "%s".', $con, $id, implode('", "', array_keys($this->connections)))); } if (!isset($managerDefs[$con])) { $managerDef = $parentDef = $this->getEventManagerDef($container, $con); while (!$parentDef->getClass() && $parentDef instanceof ChildDefinition) { $parentDef = $container->findDefinition($parentDef->getParent()); } $managerClass = $container->getParameterBag()->resolveValue($parentDef->getClass()); $managerDefs[$con] = [$managerDef, $managerClass]; } else { [$managerDef, $managerClass] = $managerDefs[$con]; } if (ContainerAwareEventManager::class === $managerClass) { $refs = $managerDef->getArguments()[1] ?? []; $listenerRefs[$con][$id] = new Reference($id); if ($subscriberTag === $tagName) { $refs[] = $id; } else { $refs[] = [[$tag['event']], $id]; } $managerDef->setArgument(1, $refs); } else { if ($subscriberTag === $tagName) { $managerDef->addMethodCall('addEventSubscriber', [new Reference($id)]); } else { $managerDef->addMethodCall('addEventListener', [[$tag['event']], new Reference($id)]); } } } } return $listenerRefs; } private function getEventManagerDef(ContainerBuilder $container, string $name) { if (!isset($this->eventManagers[$name])) { $this->eventManagers[$name] = $container->getDefinition(sprintf($this->managerTemplate, $name)); } return $this->eventManagers[$name]; } /** * Finds and orders all service tags with the given name by their priority. * * The order of additions must be respected for services having the same priority, * and knowing that the \SplPriorityQueue class does not respect the FIFO method, * we should not use this class. * * @see https://bugs.php.net/53710 * @see https://bugs.php.net/60926 */ private function findAndSortTags(array $tagNames, ContainerBuilder $container): array { $sortedTags = []; foreach ($tagNames as $tagName) { foreach ($container->findTaggedServiceIds($tagName, true) as $serviceId => $tags) { foreach ($tags as $attributes) { $priority = $attributes['priority'] ?? 0; $sortedTags[$priority][] = [$tagName, $serviceId, $attributes]; } } } if ($sortedTags) { krsort($sortedTags); $sortedTags = array_merge(...$sortedTags); } return $sortedTags; } } DependencyInjection/CompilerPass/RegisterMappingsPass.php 0000644 00000021572 15120140637 0017717 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Reference; /** * Base class for the doctrine bundles to provide a compiler pass class that * helps to register doctrine mappings. * * The compiler pass is meant to register the mappings with the metadata * chain driver corresponding to one of the object managers. * * For concrete implementations, see the RegisterXyMappingsPass classes * in the DoctrineBundle resp. * DoctrineMongodbBundle, DoctrineCouchdbBundle and DoctrinePhpcrBundle. * * @author David Buchmann <david@liip.ch> */ abstract class RegisterMappingsPass implements CompilerPassInterface { /** * DI object for the driver to use, either a service definition for a * private service or a reference for a public service. * * @var Definition|Reference */ protected $driver; /** * List of namespaces handled by the driver. * * @var string[] */ protected $namespaces; /** * List of potential container parameters that hold the object manager name * to register the mappings with the correct metadata driver, for example * ['acme.manager', 'doctrine.default_entity_manager']. * * @var string[] */ protected $managerParameters; /** * Naming pattern of the metadata chain driver service ids, for example * 'doctrine.orm.%s_metadata_driver'. * * @var string */ protected $driverPattern; /** * A name for a parameter in the container. If set, this compiler pass will * only do anything if the parameter is present. (But regardless of the * value of that parameter. * * @var string|false */ protected $enabledParameter; /** * Naming pattern for the configuration service id, for example * 'doctrine.orm.%s_configuration'. * * @var string */ private $configurationPattern; /** * Method name to call on the configuration service. This depends on the * Doctrine implementation. For example addEntityNamespace. * * @var string */ private $registerAliasMethodName; /** * Map of alias to namespace. * * @var string[] */ private $aliasMap; /** * The $managerParameters is an ordered list of container parameters that could provide the * name of the manager to register these namespaces and alias on. The first non-empty name * is used, the others skipped. * * The $aliasMap parameter can be used to define bundle namespace shortcuts like the * DoctrineBundle provides automatically for objects in the default Entity/Document folder. * * @param Definition|Reference $driver Driver DI definition or reference * @param string[] $namespaces List of namespaces handled by $driver * @param string[] $managerParameters list of container parameters that could * hold the manager name * @param string $driverPattern Pattern for the metadata driver service name * @param string|false $enabledParameter Service container parameter that must be * present to enable the mapping. Set to false * to not do any check, optional. * @param string $configurationPattern Pattern for the Configuration service name * @param string $registerAliasMethodName Name of Configuration class method to * register alias * @param string[] $aliasMap Map of alias to namespace */ public function __construct($driver, array $namespaces, array $managerParameters, string $driverPattern, $enabledParameter = false, string $configurationPattern = '', string $registerAliasMethodName = '', array $aliasMap = []) { $this->driver = $driver; $this->namespaces = $namespaces; $this->managerParameters = $managerParameters; $this->driverPattern = $driverPattern; $this->enabledParameter = $enabledParameter; if (\count($aliasMap) && (!$configurationPattern || !$registerAliasMethodName)) { throw new \InvalidArgumentException('configurationPattern and registerAliasMethodName are required to register namespace alias.'); } $this->configurationPattern = $configurationPattern; $this->registerAliasMethodName = $registerAliasMethodName; $this->aliasMap = $aliasMap; } /** * Register mappings and alias with the metadata drivers. */ public function process(ContainerBuilder $container) { if (!$this->enabled($container)) { return; } $mappingDriverDef = $this->getDriver($container); $chainDriverDefService = $this->getChainDriverServiceName($container); // Definition for a Doctrine\Persistence\Mapping\Driver\MappingDriverChain $chainDriverDef = $container->getDefinition($chainDriverDefService); foreach ($this->namespaces as $namespace) { $chainDriverDef->addMethodCall('addDriver', [$mappingDriverDef, $namespace]); } if (!\count($this->aliasMap)) { return; } $configurationServiceName = $this->getConfigurationServiceName($container); // Definition of the Doctrine\...\Configuration class specific to the Doctrine flavour. $configurationServiceDefinition = $container->getDefinition($configurationServiceName); foreach ($this->aliasMap as $alias => $namespace) { $configurationServiceDefinition->addMethodCall($this->registerAliasMethodName, [$alias, $namespace]); } } /** * Get the service name of the metadata chain driver that the mappings * should be registered with. * * @return string * * @throws InvalidArgumentException if non of the managerParameters has a * non-empty value */ protected function getChainDriverServiceName(ContainerBuilder $container) { return sprintf($this->driverPattern, $this->getManagerName($container)); } /** * Create the service definition for the metadata driver. * * @param ContainerBuilder $container Passed on in case an extending class * needs access to the container * * @return Definition|Reference */ protected function getDriver(ContainerBuilder $container) { return $this->driver; } /** * Get the service name from the pattern and the configured manager name. * * @throws InvalidArgumentException if none of the managerParameters has a * non-empty value */ private function getConfigurationServiceName(ContainerBuilder $container): string { return sprintf($this->configurationPattern, $this->getManagerName($container)); } /** * Determine the manager name. * * The default implementation loops over the managerParameters and returns * the first non-empty parameter. * * @throws InvalidArgumentException if none of the managerParameters is found in the container */ private function getManagerName(ContainerBuilder $container): string { foreach ($this->managerParameters as $param) { if ($container->hasParameter($param)) { $name = $container->getParameter($param); if ($name) { return $name; } } } throw new InvalidArgumentException(sprintf('Could not find the manager name parameter in the container. Tried the following parameter names: "%s".', implode('", "', $this->managerParameters))); } /** * Determine whether this mapping should be activated or not. This allows * to take this decision with the container builder available. * * This default implementation checks if the class has the enabledParameter * configured and if so if that parameter is present in the container. * * @return bool */ protected function enabled(ContainerBuilder $container) { return !$this->enabledParameter || $container->hasParameter($this->enabledParameter); } } DependencyInjection/CompilerPass/RegisterUidTypePass.php 0000644 00000002542 15120140637 0017520 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass; use Symfony\Bridge\Doctrine\Types\UlidType; use Symfony\Bridge\Doctrine\Types\UuidType; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\Uid\AbstractUid; final class RegisterUidTypePass implements CompilerPassInterface { /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { if (!class_exists(AbstractUid::class)) { return; } if (!$container->hasParameter('doctrine.dbal.connection_factory.types')) { return; } $typeDefinition = $container->getParameter('doctrine.dbal.connection_factory.types'); if (!isset($typeDefinition['uuid'])) { $typeDefinition['uuid'] = ['class' => UuidType::class]; } if (!isset($typeDefinition['ulid'])) { $typeDefinition['ulid'] = ['class' => UlidType::class]; } $container->setParameter('doctrine.dbal.connection_factory.types', $typeDefinition); } } DependencyInjection/Security/UserProvider/EntityFactory.php 0000644 00000003577 15120140637 0020255 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\DependencyInjection\Security\UserProvider; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\UserProviderFactoryInterface; use Symfony\Component\Config\Definition\Builder\NodeDefinition; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; /** * EntityFactory creates services for Doctrine user provider. * * @author Fabien Potencier <fabien@symfony.com> * @author Christophe Coevoet <stof@notk.org> */ class EntityFactory implements UserProviderFactoryInterface { private $key; private $providerId; public function __construct(string $key, string $providerId) { $this->key = $key; $this->providerId = $providerId; } public function create(ContainerBuilder $container, string $id, array $config) { $container ->setDefinition($id, new ChildDefinition($this->providerId)) ->addArgument($config['class']) ->addArgument($config['property']) ->addArgument($config['manager_name']) ; } public function getKey() { return $this->key; } public function addConfiguration(NodeDefinition $node) { $node ->children() ->scalarNode('class') ->isRequired() ->info('The full entity class name of your user class.') ->cannotBeEmpty() ->end() ->scalarNode('property')->defaultNull()->end() ->scalarNode('manager_name')->defaultNull()->end() ->end() ; } } DependencyInjection/AbstractDoctrineExtension.php 0000644 00000053007 15120140637 0016332 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\DependencyInjection; use Symfony\Component\Config\Resource\GlobResource; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\HttpKernel\DependencyInjection\Extension; /** * This abstract classes groups common code that Doctrine Object Manager extensions (ORM, MongoDB, CouchDB) need. * * @author Benjamin Eberlei <kontakt@beberlei.de> */ abstract class AbstractDoctrineExtension extends Extension { /** * Used inside metadata driver method to simplify aggregation of data. */ protected $aliasMap = []; /** * Used inside metadata driver method to simplify aggregation of data. */ protected $drivers = []; /** * @param array $objectManager A configured object manager * * @throws \InvalidArgumentException */ protected function loadMappingInformation(array $objectManager, ContainerBuilder $container) { if ($objectManager['auto_mapping']) { // automatically register bundle mappings foreach (array_keys($container->getParameter('kernel.bundles')) as $bundle) { if (!isset($objectManager['mappings'][$bundle])) { $objectManager['mappings'][$bundle] = [ 'mapping' => true, 'is_bundle' => true, ]; } } } foreach ($objectManager['mappings'] as $mappingName => $mappingConfig) { if (null !== $mappingConfig && false === $mappingConfig['mapping']) { continue; } $mappingConfig = array_replace([ 'dir' => false, 'type' => false, 'prefix' => false, ], (array) $mappingConfig); $mappingConfig['dir'] = $container->getParameterBag()->resolveValue($mappingConfig['dir']); // a bundle configuration is detected by realizing that the specified dir is not absolute and existing if (!isset($mappingConfig['is_bundle'])) { $mappingConfig['is_bundle'] = !is_dir($mappingConfig['dir']); } if ($mappingConfig['is_bundle']) { $bundle = null; $bundleMetadata = null; foreach ($container->getParameter('kernel.bundles') as $name => $class) { if ($mappingName === $name) { $bundle = new \ReflectionClass($class); $bundleMetadata = $container->getParameter('kernel.bundles_metadata')[$name]; break; } } if (null === $bundle) { throw new \InvalidArgumentException(sprintf('Bundle "%s" does not exist or it is not enabled.', $mappingName)); } $mappingConfig = $this->getMappingDriverBundleConfigDefaults($mappingConfig, $bundle, $container, $bundleMetadata['path']); if (!$mappingConfig) { continue; } } elseif (!$mappingConfig['type']) { $mappingConfig['type'] = $this->detectMappingType($mappingConfig['dir'], $container); } $this->assertValidMappingConfiguration($mappingConfig, $objectManager['name']); $this->setMappingDriverConfig($mappingConfig, $mappingName); $this->setMappingDriverAlias($mappingConfig, $mappingName); } } /** * Register the alias for this mapping driver. * * Aliases can be used in the Query languages of all the Doctrine object managers to simplify writing tasks. */ protected function setMappingDriverAlias(array $mappingConfig, string $mappingName) { if (isset($mappingConfig['alias'])) { $this->aliasMap[$mappingConfig['alias']] = $mappingConfig['prefix']; } else { $this->aliasMap[$mappingName] = $mappingConfig['prefix']; } } /** * Register the mapping driver configuration for later use with the object managers metadata driver chain. * * @throws \InvalidArgumentException */ protected function setMappingDriverConfig(array $mappingConfig, string $mappingName) { $mappingDirectory = $mappingConfig['dir']; if (!is_dir($mappingDirectory)) { throw new \InvalidArgumentException(sprintf('Invalid Doctrine mapping path given. Cannot load Doctrine mapping/bundle named "%s".', $mappingName)); } $this->drivers[$mappingConfig['type']][$mappingConfig['prefix']] = realpath($mappingDirectory) ?: $mappingDirectory; } /** * If this is a bundle controlled mapping all the missing information can be autodetected by this method. * * Returns false when autodetection failed, an array of the completed information otherwise. * * @param string|null $bundleDir The bundle directory path * * @return array|false */ protected function getMappingDriverBundleConfigDefaults(array $bundleConfig, \ReflectionClass $bundle, ContainerBuilder $container/* , string $bundleDir = null */) { if (\func_num_args() < 4 && __CLASS__ !== static::class && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName() && !$this instanceof \PHPUnit\Framework\MockObject\MockObject && !$this instanceof \Prophecy\Prophecy\ProphecySubjectInterface && !$this instanceof \Mockery\MockInterface) { trigger_deprecation('symfony/doctrine-bridge', '5.4', 'The "%s()" method will have a new "string $bundleDir = null" argument in version 6.0, not defining it is deprecated.', __METHOD__); $bundleDir = null; } else { $bundleDir = func_get_arg(3); } $bundleClassDir = \dirname($bundle->getFileName()); $bundleDir ?? $bundleDir = $bundleClassDir; if (!$bundleConfig['type']) { $bundleConfig['type'] = $this->detectMetadataDriver($bundleDir, $container); if (!$bundleConfig['type'] && $bundleDir !== $bundleClassDir) { $bundleConfig['type'] = $this->detectMetadataDriver($bundleClassDir, $container); } } if (!$bundleConfig['type']) { // skip this bundle, no mapping information was found. return false; } if (!$bundleConfig['dir']) { if (\in_array($bundleConfig['type'], ['annotation', 'staticphp', 'attribute'])) { $bundleConfig['dir'] = $bundleClassDir.'/'.$this->getMappingObjectDefaultName(); } else { $bundleConfig['dir'] = $bundleDir.'/'.$this->getMappingResourceConfigDirectory($bundleDir); } } else { $bundleConfig['dir'] = $bundleDir.'/'.$bundleConfig['dir']; } if (!$bundleConfig['prefix']) { $bundleConfig['prefix'] = $bundle->getNamespaceName().'\\'.$this->getMappingObjectDefaultName(); } return $bundleConfig; } /** * Register all the collected mapping information with the object manager by registering the appropriate mapping drivers. */ protected function registerMappingDrivers(array $objectManager, ContainerBuilder $container) { // configure metadata driver for each bundle based on the type of mapping files found if ($container->hasDefinition($this->getObjectManagerElementName($objectManager['name'].'_metadata_driver'))) { $chainDriverDef = $container->getDefinition($this->getObjectManagerElementName($objectManager['name'].'_metadata_driver')); } else { $chainDriverDef = new Definition($this->getMetadataDriverClass('driver_chain')); $chainDriverDef->setPublic(false); } foreach ($this->drivers as $driverType => $driverPaths) { $mappingService = $this->getObjectManagerElementName($objectManager['name'].'_'.$driverType.'_metadata_driver'); if ($container->hasDefinition($mappingService)) { $mappingDriverDef = $container->getDefinition($mappingService); $args = $mappingDriverDef->getArguments(); if ('annotation' == $driverType) { $args[1] = array_merge(array_values($driverPaths), $args[1]); } else { $args[0] = array_merge(array_values($driverPaths), $args[0]); } $mappingDriverDef->setArguments($args); } elseif ('attribute' === $driverType) { $mappingDriverDef = new Definition($this->getMetadataDriverClass($driverType), [ array_values($driverPaths), ]); } elseif ('annotation' == $driverType) { $mappingDriverDef = new Definition($this->getMetadataDriverClass($driverType), [ new Reference($this->getObjectManagerElementName('metadata.annotation_reader')), array_values($driverPaths), ]); } else { $mappingDriverDef = new Definition($this->getMetadataDriverClass($driverType), [ array_values($driverPaths), ]); } $mappingDriverDef->setPublic(false); if (str_contains($mappingDriverDef->getClass(), 'yml') || str_contains($mappingDriverDef->getClass(), 'xml')) { $mappingDriverDef->setArguments([array_flip($driverPaths)]); $mappingDriverDef->addMethodCall('setGlobalBasename', ['mapping']); } $container->setDefinition($mappingService, $mappingDriverDef); foreach ($driverPaths as $prefix => $driverPath) { $chainDriverDef->addMethodCall('addDriver', [new Reference($mappingService), $prefix]); } } $container->setDefinition($this->getObjectManagerElementName($objectManager['name'].'_metadata_driver'), $chainDriverDef); } /** * Assertion if the specified mapping information is valid. * * @throws \InvalidArgumentException */ protected function assertValidMappingConfiguration(array $mappingConfig, string $objectManagerName) { if (!$mappingConfig['type'] || !$mappingConfig['dir'] || !$mappingConfig['prefix']) { throw new \InvalidArgumentException(sprintf('Mapping definitions for Doctrine manager "%s" require at least the "type", "dir" and "prefix" options.', $objectManagerName)); } if (!is_dir($mappingConfig['dir'])) { throw new \InvalidArgumentException(sprintf('Specified non-existing directory "%s" as Doctrine mapping source.', $mappingConfig['dir'])); } if (!\in_array($mappingConfig['type'], ['xml', 'yml', 'annotation', 'php', 'staticphp', 'attribute'])) { throw new \InvalidArgumentException(sprintf('Can only configure "xml", "yml", "annotation", "php", "staticphp" or "attribute" through the DoctrineBundle. Use your own bundle to configure other metadata drivers. You can register them by adding a new driver to the "%s" service definition.', $this->getObjectManagerElementName($objectManagerName.'_metadata_driver'))); } } /** * Detects what metadata driver to use for the supplied directory. * * @return string|null A metadata driver short name, if one can be detected */ protected function detectMetadataDriver(string $dir, ContainerBuilder $container) { $configPath = $this->getMappingResourceConfigDirectory($dir); $extension = $this->getMappingResourceExtension(); if (glob($dir.'/'.$configPath.'/*.'.$extension.'.xml', \GLOB_NOSORT)) { $driver = 'xml'; } elseif (glob($dir.'/'.$configPath.'/*.'.$extension.'.yml', \GLOB_NOSORT)) { $driver = 'yml'; } elseif (glob($dir.'/'.$configPath.'/*.'.$extension.'.php', \GLOB_NOSORT)) { $driver = 'php'; } else { // add the closest existing directory as a resource $resource = $dir.'/'.$configPath; while (!is_dir($resource)) { $resource = \dirname($resource); } $container->fileExists($resource, false); if ($container->fileExists($discoveryPath = $dir.'/'.$this->getMappingObjectDefaultName(), false)) { return $this->detectMappingType($discoveryPath, $container); } return null; } $container->fileExists($dir.'/'.$configPath, false); return $driver; } /** * Detects what mapping type to use for the supplied directory. * * @return string A mapping type 'attribute' or 'annotation' */ private function detectMappingType(string $directory, ContainerBuilder $container): string { if (\PHP_VERSION_ID < 80000) { return 'annotation'; } $type = 'attribute'; $glob = new GlobResource($directory, '*', true); $container->addResource($glob); $quotedMappingObjectName = preg_quote($this->getMappingObjectDefaultName(), '/'); foreach ($glob as $file) { $content = file_get_contents($file); if ( preg_match('/^#\[.*'.$quotedMappingObjectName.'\b/m', $content) || preg_match('/^#\[.*Embeddable\b/m', $content) ) { break; } if ( preg_match('/^(?: \*|\/\*\*) @.*'.$quotedMappingObjectName.'\b/m', $content) || preg_match('/^(?: \*|\/\*\*) @.*Embeddable\b/m', $content) ) { $type = 'annotation'; break; } } return $type; } /** * Loads a configured object manager metadata, query or result cache driver. * * @throws \InvalidArgumentException in case of unknown driver type */ protected function loadObjectManagerCacheDriver(array $objectManager, ContainerBuilder $container, string $cacheName) { $this->loadCacheDriver($cacheName, $objectManager['name'], $objectManager[$cacheName.'_driver'], $container); } /** * Loads a cache driver. * * @return string * * @throws \InvalidArgumentException */ protected function loadCacheDriver(string $cacheName, string $objectManagerName, array $cacheDriver, ContainerBuilder $container) { $cacheDriverServiceId = $this->getObjectManagerElementName($objectManagerName.'_'.$cacheName); switch ($cacheDriver['type']) { case 'service': $container->setAlias($cacheDriverServiceId, new Alias($cacheDriver['id'], false)); return $cacheDriverServiceId; case 'memcached': $memcachedClass = !empty($cacheDriver['class']) ? $cacheDriver['class'] : '%'.$this->getObjectManagerElementName('cache.memcached.class').'%'; $memcachedInstanceClass = !empty($cacheDriver['instance_class']) ? $cacheDriver['instance_class'] : '%'.$this->getObjectManagerElementName('cache.memcached_instance.class').'%'; $memcachedHost = !empty($cacheDriver['host']) ? $cacheDriver['host'] : '%'.$this->getObjectManagerElementName('cache.memcached_host').'%'; $memcachedPort = !empty($cacheDriver['port']) ? $cacheDriver['port'] : '%'.$this->getObjectManagerElementName('cache.memcached_port').'%'; $cacheDef = new Definition($memcachedClass); $memcachedInstance = new Definition($memcachedInstanceClass); $memcachedInstance->addMethodCall('addServer', [ $memcachedHost, $memcachedPort, ]); $container->setDefinition($this->getObjectManagerElementName(sprintf('%s_memcached_instance', $objectManagerName)), $memcachedInstance); $cacheDef->addMethodCall('setMemcached', [new Reference($this->getObjectManagerElementName(sprintf('%s_memcached_instance', $objectManagerName)))]); break; case 'redis': $redisClass = !empty($cacheDriver['class']) ? $cacheDriver['class'] : '%'.$this->getObjectManagerElementName('cache.redis.class').'%'; $redisInstanceClass = !empty($cacheDriver['instance_class']) ? $cacheDriver['instance_class'] : '%'.$this->getObjectManagerElementName('cache.redis_instance.class').'%'; $redisHost = !empty($cacheDriver['host']) ? $cacheDriver['host'] : '%'.$this->getObjectManagerElementName('cache.redis_host').'%'; $redisPort = !empty($cacheDriver['port']) ? $cacheDriver['port'] : '%'.$this->getObjectManagerElementName('cache.redis_port').'%'; $cacheDef = new Definition($redisClass); $redisInstance = new Definition($redisInstanceClass); $redisInstance->addMethodCall('connect', [ $redisHost, $redisPort, ]); $container->setDefinition($this->getObjectManagerElementName(sprintf('%s_redis_instance', $objectManagerName)), $redisInstance); $cacheDef->addMethodCall('setRedis', [new Reference($this->getObjectManagerElementName(sprintf('%s_redis_instance', $objectManagerName)))]); break; case 'apc': case 'apcu': case 'array': case 'xcache': case 'wincache': case 'zenddata': $cacheDef = new Definition('%'.$this->getObjectManagerElementName(sprintf('cache.%s.class', $cacheDriver['type'])).'%'); break; default: throw new \InvalidArgumentException(sprintf('"%s" is an unrecognized Doctrine cache driver.', $cacheDriver['type'])); } $cacheDef->setPublic(false); if (!isset($cacheDriver['namespace'])) { // generate a unique namespace for the given application if ($container->hasParameter('cache.prefix.seed')) { $seed = $container->getParameterBag()->resolveValue($container->getParameter('cache.prefix.seed')); } else { $seed = '_'.$container->getParameter('kernel.project_dir'); $seed .= '.'.$container->getParameter('kernel.container_class'); } $namespace = 'sf_'.$this->getMappingResourceExtension().'_'.$objectManagerName.'_'.ContainerBuilder::hash($seed); $cacheDriver['namespace'] = $namespace; } $cacheDef->addMethodCall('setNamespace', [$cacheDriver['namespace']]); $container->setDefinition($cacheDriverServiceId, $cacheDef); return $cacheDriverServiceId; } /** * Returns a modified version of $managerConfigs. * * The manager called $autoMappedManager will map all bundles that are not mapped by other managers. * * @return array */ protected function fixManagersAutoMappings(array $managerConfigs, array $bundles) { if ($autoMappedManager = $this->validateAutoMapping($managerConfigs)) { foreach (array_keys($bundles) as $bundle) { foreach ($managerConfigs as $manager) { if (isset($manager['mappings'][$bundle])) { continue 2; } } $managerConfigs[$autoMappedManager]['mappings'][$bundle] = [ 'mapping' => true, 'is_bundle' => true, ]; } $managerConfigs[$autoMappedManager]['auto_mapping'] = false; } return $managerConfigs; } /** * Prefixes the relative dependency injection container path with the object manager prefix. * * @example $name is 'entity_manager' then the result would be 'doctrine.orm.entity_manager' * * @return string */ abstract protected function getObjectManagerElementName(string $name); /** * Noun that describes the mapped objects such as Entity or Document. * * Will be used for autodetection of persistent objects directory. * * @return string */ abstract protected function getMappingObjectDefaultName(); /** * Relative path from the bundle root to the directory where mapping files reside. * * @param string|null $bundleDir The bundle directory path * * @return string */ abstract protected function getMappingResourceConfigDirectory(/* string $bundleDir = null */); /** * Extension used by the mapping files. * * @return string */ abstract protected function getMappingResourceExtension(); /** * The class name used by the various mapping drivers. */ abstract protected function getMetadataDriverClass(string $driverType): string; /** * Search for a manager that is declared as 'auto_mapping' = true. * * @throws \LogicException */ private function validateAutoMapping(array $managerConfigs): ?string { $autoMappedManager = null; foreach ($managerConfigs as $name => $manager) { if (!$manager['auto_mapping']) { continue; } if (null !== $autoMappedManager) { throw new \LogicException(sprintf('You cannot enable "auto_mapping" on more than one manager at the same time (found in "%s" and "%s"").', $autoMappedManager, $name)); } $autoMappedManager = $name; } return $autoMappedManager; } } Form/ChoiceList/DoctrineChoiceLoader.php 0000644 00000011114 15120140637 0014214 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Form\ChoiceList; use Doctrine\Persistence\ObjectManager; use Symfony\Component\Form\ChoiceList\Loader\AbstractChoiceLoader; /** * Loads choices using a Doctrine object manager. * * @author Bernhard Schussek <bschussek@gmail.com> */ class DoctrineChoiceLoader extends AbstractChoiceLoader { private $manager; private $class; private $idReader; private $objectLoader; /** * Creates a new choice loader. * * Optionally, an implementation of {@link EntityLoaderInterface} can be * passed which optimizes the object loading for one of the Doctrine * mapper implementations. * * @param string $class The class name of the loaded objects */ public function __construct(ObjectManager $manager, string $class, IdReader $idReader = null, EntityLoaderInterface $objectLoader = null) { $classMetadata = $manager->getClassMetadata($class); if ($idReader && !$idReader->isSingleId()) { throw new \InvalidArgumentException(sprintf('The second argument `$idReader` of "%s" must be null when the query cannot be optimized because of composite id fields.', __METHOD__)); } $this->manager = $manager; $this->class = $classMetadata->getName(); $this->idReader = $idReader; $this->objectLoader = $objectLoader; } /** * {@inheritdoc} */ protected function loadChoices(): iterable { return $this->objectLoader ? $this->objectLoader->getEntities() : $this->manager->getRepository($this->class)->findAll(); } /** * @internal to be remove in Symfony 6 */ protected function doLoadValuesForChoices(array $choices): array { // Optimize performance for single-field identifiers. We already // know that the IDs are used as values // Attention: This optimization does not check choices for existence if ($this->idReader) { trigger_deprecation('symfony/doctrine-bridge', '5.1', 'Not defining explicitly the IdReader as value callback when query can be optimized is deprecated. Don\'t pass the IdReader to "%s" or define the "choice_value" option instead.', __CLASS__); // Maintain order and indices of the given objects $values = []; foreach ($choices as $i => $object) { if ($object instanceof $this->class) { $values[$i] = $this->idReader->getIdValue($object); } } return $values; } return parent::doLoadValuesForChoices($choices); } protected function doLoadChoicesForValues(array $values, ?callable $value): array { $legacy = $this->idReader && null === $value; if ($legacy) { trigger_deprecation('symfony/doctrine-bridge', '5.1', 'Not defining explicitly the IdReader as value callback when query can be optimized is deprecated. Don\'t pass the IdReader to "%s" or define the "choice_value" option instead.', __CLASS__); } $idReader = null; if (\is_array($value) && $value[0] instanceof IdReader) { $idReader = $value[0]; } elseif ($value instanceof \Closure && ($rThis = (new \ReflectionFunction($value))->getClosureThis()) instanceof IdReader) { $idReader = $rThis; } elseif ($legacy) { $idReader = $this->idReader; } // Optimize performance in case we have an object loader and // a single-field identifier if ($idReader && $this->objectLoader) { $objects = []; $objectsById = []; // Maintain order and indices from the given $values // An alternative approach to the following loop is to add the // "INDEX BY" clause to the Doctrine query in the loader, // but I'm not sure whether that's doable in a generic fashion. foreach ($this->objectLoader->getEntitiesByIds($idReader->getIdField(), $values) as $object) { $objectsById[$idReader->getIdValue($object)] = $object; } foreach ($values as $i => $id) { if (isset($objectsById[$id])) { $objects[$i] = $objectsById[$id]; } } return $objects; } return parent::doLoadChoicesForValues($values, $value); } } Form/ChoiceList/EntityLoaderInterface.php 0000644 00000001433 15120140637 0014432 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Form\ChoiceList; /** * Custom loader for entities in the choice list. * * @author Benjamin Eberlei <kontakt@beberlei.de> */ interface EntityLoaderInterface { /** * Returns an array of entities that are valid choices in the corresponding choice list. * * @return array */ public function getEntities(); /** * Returns an array of entities matching the given identifiers. * * @return array */ public function getEntitiesByIds(string $identifier, array $values); } Form/ChoiceList/IdReader.php 0000644 00000005756 15120140637 0011701 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Form\ChoiceList; use Doctrine\Persistence\Mapping\ClassMetadata; use Doctrine\Persistence\ObjectManager; use Symfony\Component\Form\Exception\RuntimeException; /** * A utility for reading object IDs. * * @author Bernhard Schussek <bschussek@gmail.com> * * @internal */ class IdReader { private $om; private $classMetadata; private $singleId; private $intId; private $idField; /** * @var IdReader|null */ private $associationIdReader; public function __construct(ObjectManager $om, ClassMetadata $classMetadata) { $ids = $classMetadata->getIdentifierFieldNames(); $idType = $classMetadata->getTypeOfField(current($ids)); $this->om = $om; $this->classMetadata = $classMetadata; $this->singleId = 1 === \count($ids); $this->intId = $this->singleId && \in_array($idType, ['integer', 'smallint', 'bigint']); $this->idField = current($ids); // single field association are resolved, since the schema column could be an int if ($this->singleId && $classMetadata->hasAssociation($this->idField)) { $this->associationIdReader = new self($om, $om->getClassMetadata( $classMetadata->getAssociationTargetClass($this->idField) )); $this->singleId = $this->associationIdReader->isSingleId(); $this->intId = $this->associationIdReader->isIntId(); } } /** * Returns whether the class has a single-column ID. */ public function isSingleId(): bool { return $this->singleId; } /** * Returns whether the class has a single-column integer ID. */ public function isIntId(): bool { return $this->intId; } /** * Returns the ID value for an object. * * This method assumes that the object has a single-column ID. */ public function getIdValue(object $object = null): string { if (!$object) { return ''; } if (!$this->om->contains($object)) { throw new RuntimeException(sprintf('Entity of type "%s" passed to the choice field must be managed. Maybe you forget to persist it in the entity manager?', get_debug_type($object))); } $this->om->initializeObject($object); $idValue = current($this->classMetadata->getIdentifierValues($object)); if ($this->associationIdReader) { $idValue = $this->associationIdReader->getIdValue($idValue); } return (string) $idValue; } /** * Returns the name of the ID field. * * This method assumes that the object has a single-column ID. */ public function getIdField(): string { return $this->idField; } } Form/ChoiceList/ORMQueryBuilderLoader.php 0000644 00000010261 15120140637 0014326 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Form\ChoiceList; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Types\ConversionException; use Doctrine\DBAL\Types\Type; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\Exception\TransformationFailedException; /** * Loads entities using a {@link QueryBuilder} instance. * * @author Benjamin Eberlei <kontakt@beberlei.de> * @author Bernhard Schussek <bschussek@gmail.com> */ class ORMQueryBuilderLoader implements EntityLoaderInterface { /** * Contains the query builder that builds the query for fetching the * entities. * * This property should only be accessed through queryBuilder. * * @var QueryBuilder */ private $queryBuilder; public function __construct(QueryBuilder $queryBuilder) { $this->queryBuilder = $queryBuilder; } /** * {@inheritdoc} */ public function getEntities() { return $this->queryBuilder->getQuery()->execute(); } /** * {@inheritdoc} */ public function getEntitiesByIds(string $identifier, array $values) { if (null !== $this->queryBuilder->getMaxResults() || 0 < (int) $this->queryBuilder->getFirstResult()) { // an offset or a limit would apply on results including the where clause with submitted id values // that could make invalid choices valid $choices = []; $metadata = $this->queryBuilder->getEntityManager()->getClassMetadata(current($this->queryBuilder->getRootEntities())); foreach ($this->getEntities() as $entity) { if (\in_array((string) current($metadata->getIdentifierValues($entity)), $values, true)) { $choices[] = $entity; } } return $choices; } $qb = clone $this->queryBuilder; $alias = current($qb->getRootAliases()); $parameter = 'ORMQueryBuilderLoader_getEntitiesByIds_'.$identifier; $parameter = str_replace('.', '_', $parameter); $where = $qb->expr()->in($alias.'.'.$identifier, ':'.$parameter); // Guess type $entity = current($qb->getRootEntities()); $metadata = $qb->getEntityManager()->getClassMetadata($entity); if (\in_array($type = $metadata->getTypeOfField($identifier), ['integer', 'bigint', 'smallint'])) { $parameterType = Connection::PARAM_INT_ARRAY; // Filter out non-integer values (e.g. ""). If we don't, some // databases such as PostgreSQL fail. $values = array_values(array_filter($values, function ($v) { return (string) $v === (string) (int) $v || ctype_digit($v); })); } elseif (\in_array($type, ['ulid', 'uuid', 'guid'])) { $parameterType = Connection::PARAM_STR_ARRAY; // Like above, but we just filter out empty strings. $values = array_values(array_filter($values, function ($v) { return '' !== (string) $v; })); // Convert values into right type if (Type::hasType($type)) { $doctrineType = Type::getType($type); $platform = $qb->getEntityManager()->getConnection()->getDatabasePlatform(); foreach ($values as &$value) { try { $value = $doctrineType->convertToDatabaseValue($value, $platform); } catch (ConversionException $e) { throw new TransformationFailedException(sprintf('Failed to transform "%s" into "%s".', $value, $type), 0, $e); } } unset($value); } } else { $parameterType = Connection::PARAM_STR_ARRAY; } if (!$values) { return []; } return $qb->andWhere($where) ->getQuery() ->setParameter($parameter, $values, $parameterType) ->getResult(); } } Form/DataTransformer/CollectionToArrayTransformer.php 0000644 00000003200 15120140637 0017066 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Form\DataTransformer; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\Exception\TransformationFailedException; /** * @author Bernhard Schussek <bschussek@gmail.com> */ class CollectionToArrayTransformer implements DataTransformerInterface { /** * Transforms a collection into an array. * * @throws TransformationFailedException */ public function transform($collection) { if (null === $collection) { return []; } // For cases when the collection getter returns $collection->toArray() // in order to prevent modifications of the returned collection if (\is_array($collection)) { return $collection; } if (!$collection instanceof Collection) { throw new TransformationFailedException('Expected a Doctrine\Common\Collections\Collection object.'); } return $collection->toArray(); } /** * Transforms an array into a collection. * * @return Collection */ public function reverseTransform($array) { if ('' === $array || null === $array) { $array = []; } else { $array = (array) $array; } return new ArrayCollection($array); } } Form/EventListener/MergeDoctrineCollectionListener.php 0000644 00000002703 15120140637 0017221 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Form\EventListener; use Doctrine\Common\Collections\Collection; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; /** * Merge changes from the request to a Doctrine\Common\Collections\Collection instance. * * This works with ORM, MongoDB and CouchDB instances of the collection interface. * * @author Bernhard Schussek <bschussek@gmail.com> * * @see Collection */ class MergeDoctrineCollectionListener implements EventSubscriberInterface { public static function getSubscribedEvents() { // Higher priority than core MergeCollectionListener so that this one // is called before return [ FormEvents::SUBMIT => [ ['onSubmit', 5], ], ]; } public function onSubmit(FormEvent $event) { $collection = $event->getForm()->getData(); $data = $event->getData(); // If all items were removed, call clear which has a higher // performance on persistent collections if ($collection instanceof Collection && 0 === \count($data)) { $collection->clear(); } } } Form/Type/DoctrineType.php 0000644 00000024014 15120140637 0011512 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Form\Type; use Doctrine\Common\Collections\Collection; use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ObjectManager; use Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader; use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityLoaderInterface; use Symfony\Bridge\Doctrine\Form\ChoiceList\IdReader; use Symfony\Bridge\Doctrine\Form\DataTransformer\CollectionToArrayTransformer; use Symfony\Bridge\Doctrine\Form\EventListener\MergeDoctrineCollectionListener; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\ChoiceList\ChoiceList; use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator; use Symfony\Component\Form\Exception\RuntimeException; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Contracts\Service\ResetInterface; abstract class DoctrineType extends AbstractType implements ResetInterface { /** * @var ManagerRegistry */ protected $registry; /** * @var IdReader[] */ private $idReaders = []; /** * @var EntityLoaderInterface[] */ private $entityLoaders = []; /** * Creates the label for a choice. * * For backwards compatibility, objects are cast to strings by default. * * @internal This method is public to be usable as callback. It should not * be used in user code. */ public static function createChoiceLabel(object $choice): string { return (string) $choice; } /** * Creates the field name for a choice. * * This method is used to generate field names if the underlying object has * a single-column integer ID. In that case, the value of the field is * the ID of the object. That ID is also used as field name. * * @param int|string $key The choice key * @param string $value The choice value. Corresponds to the object's * ID here. * * @internal This method is public to be usable as callback. It should not * be used in user code. */ public static function createChoiceName(object $choice, $key, string $value): string { return str_replace('-', '_', $value); } /** * Gets important parts from QueryBuilder that will allow to cache its results. * For instance in ORM two query builders with an equal SQL string and * equal parameters are considered to be equal. * * @param object $queryBuilder A query builder, type declaration is not present here as there * is no common base class for the different implementations * * @internal This method is public to be usable as callback. It should not * be used in user code. */ public function getQueryBuilderPartsForCachingHash(object $queryBuilder): ?array { return null; } public function __construct(ManagerRegistry $registry) { $this->registry = $registry; } public function buildForm(FormBuilderInterface $builder, array $options) { if ($options['multiple'] && interface_exists(Collection::class)) { $builder ->addEventSubscriber(new MergeDoctrineCollectionListener()) ->addViewTransformer(new CollectionToArrayTransformer(), true) ; } } public function configureOptions(OptionsResolver $resolver) { $choiceLoader = function (Options $options) { // Unless the choices are given explicitly, load them on demand if (null === $options['choices']) { // If there is no QueryBuilder we can safely cache $vary = [$options['em'], $options['class']]; // also if concrete Type can return important QueryBuilder parts to generate // hash key we go for it as well, otherwise fallback on the instance if ($options['query_builder']) { $vary[] = $this->getQueryBuilderPartsForCachingHash($options['query_builder']) ?? $options['query_builder']; } return ChoiceList::loader($this, new DoctrineChoiceLoader( $options['em'], $options['class'], $options['id_reader'], $this->getCachedEntityLoader( $options['em'], $options['query_builder'] ?? $options['em']->getRepository($options['class'])->createQueryBuilder('e'), $options['class'], $vary ) ), $vary); } return null; }; $choiceName = function (Options $options) { // If the object has a single-column, numeric ID, use that ID as // field name. We can only use numeric IDs as names, as we cannot // guarantee that a non-numeric ID contains a valid form name if ($options['id_reader'] instanceof IdReader && $options['id_reader']->isIntId()) { return ChoiceList::fieldName($this, [__CLASS__, 'createChoiceName']); } // Otherwise, an incrementing integer is used as name automatically return null; }; // The choices are always indexed by ID (see "choices" normalizer // and DoctrineChoiceLoader), unless the ID is composite. Then they // are indexed by an incrementing integer. // Use the ID/incrementing integer as choice value. $choiceValue = function (Options $options) { // If the entity has a single-column ID, use that ID as value if ($options['id_reader'] instanceof IdReader && $options['id_reader']->isSingleId()) { return ChoiceList::value($this, [$options['id_reader'], 'getIdValue'], $options['id_reader']); } // Otherwise, an incrementing integer is used as value automatically return null; }; $emNormalizer = function (Options $options, $em) { if (null !== $em) { if ($em instanceof ObjectManager) { return $em; } return $this->registry->getManager($em); } $em = $this->registry->getManagerForClass($options['class']); if (null === $em) { throw new RuntimeException(sprintf('Class "%s" seems not to be a managed Doctrine entity. Did you forget to map it?', $options['class'])); } return $em; }; // Invoke the query builder closure so that we can cache choice lists // for equal query builders $queryBuilderNormalizer = function (Options $options, $queryBuilder) { if (\is_callable($queryBuilder)) { $queryBuilder = $queryBuilder($options['em']->getRepository($options['class'])); } return $queryBuilder; }; // Set the "id_reader" option via the normalizer. This option is not // supposed to be set by the user. $idReaderNormalizer = function (Options $options) { // The ID reader is a utility that is needed to read the object IDs // when generating the field values. The callback generating the // field values has no access to the object manager or the class // of the field, so we store that information in the reader. // The reader is cached so that two choice lists for the same class // (and hence with the same reader) can successfully be cached. return $this->getCachedIdReader($options['em'], $options['class']); }; $resolver->setDefaults([ 'em' => null, 'query_builder' => null, 'choices' => null, 'choice_loader' => $choiceLoader, 'choice_label' => ChoiceList::label($this, [__CLASS__, 'createChoiceLabel']), 'choice_name' => $choiceName, 'choice_value' => $choiceValue, 'id_reader' => null, // internal 'choice_translation_domain' => false, ]); $resolver->setRequired(['class']); $resolver->setNormalizer('em', $emNormalizer); $resolver->setNormalizer('query_builder', $queryBuilderNormalizer); $resolver->setNormalizer('id_reader', $idReaderNormalizer); $resolver->setAllowedTypes('em', ['null', 'string', ObjectManager::class]); } /** * Return the default loader object. * * @return EntityLoaderInterface */ abstract public function getLoader(ObjectManager $manager, object $queryBuilder, string $class); /** * @return string */ public function getParent() { return ChoiceType::class; } public function reset() { $this->idReaders = []; $this->entityLoaders = []; } private function getCachedIdReader(ObjectManager $manager, string $class): ?IdReader { $hash = CachingFactoryDecorator::generateHash([$manager, $class]); if (isset($this->idReaders[$hash])) { return $this->idReaders[$hash]; } $idReader = new IdReader($manager, $manager->getClassMetadata($class)); // don't cache the instance for composite ids that cannot be optimized return $this->idReaders[$hash] = $idReader->isSingleId() ? $idReader : null; } private function getCachedEntityLoader(ObjectManager $manager, object $queryBuilder, string $class, array $vary): EntityLoaderInterface { $hash = CachingFactoryDecorator::generateHash($vary); return $this->entityLoaders[$hash] ?? ($this->entityLoaders[$hash] = $this->getLoader($manager, $queryBuilder, $class)); } } Form/Type/EntityType.php 0000644 00000006156 15120140637 0011226 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Form\Type; use Doctrine\ORM\Query\Parameter; use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ObjectManager; use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader; use Symfony\Component\Form\Exception\UnexpectedTypeException; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; class EntityType extends DoctrineType { public function configureOptions(OptionsResolver $resolver) { parent::configureOptions($resolver); // Invoke the query builder closure so that we can cache choice lists // for equal query builders $queryBuilderNormalizer = function (Options $options, $queryBuilder) { if (\is_callable($queryBuilder)) { $queryBuilder = $queryBuilder($options['em']->getRepository($options['class'])); if (null !== $queryBuilder && !$queryBuilder instanceof QueryBuilder) { throw new UnexpectedTypeException($queryBuilder, QueryBuilder::class); } } return $queryBuilder; }; $resolver->setNormalizer('query_builder', $queryBuilderNormalizer); $resolver->setAllowedTypes('query_builder', ['null', 'callable', QueryBuilder::class]); } /** * Return the default loader object. * * @param QueryBuilder $queryBuilder * * @return ORMQueryBuilderLoader */ public function getLoader(ObjectManager $manager, object $queryBuilder, string $class) { if (!$queryBuilder instanceof QueryBuilder) { throw new \TypeError(sprintf('Expected an instance of "%s", but got "%s".', QueryBuilder::class, get_debug_type($queryBuilder))); } return new ORMQueryBuilderLoader($queryBuilder); } /** * {@inheritdoc} */ public function getBlockPrefix() { return 'entity'; } /** * We consider two query builders with an equal SQL string and * equal parameters to be equal. * * @param QueryBuilder $queryBuilder * * @internal This method is public to be usable as callback. It should not * be used in user code. */ public function getQueryBuilderPartsForCachingHash(object $queryBuilder): ?array { if (!$queryBuilder instanceof QueryBuilder) { throw new \TypeError(sprintf('Expected an instance of "%s", but got "%s".', QueryBuilder::class, get_debug_type($queryBuilder))); } return [ $queryBuilder->getQuery()->getSQL(), array_map([$this, 'parameterToArray'], $queryBuilder->getParameters()->toArray()), ]; } /** * Converts a query parameter to an array. */ private function parameterToArray(Parameter $parameter): array { return [$parameter->getName(), $parameter->getType(), $parameter->getValue()]; } } Form/DoctrineOrmExtension.php 0000644 00000001514 15120140637 0012302 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Form; use Doctrine\Persistence\ManagerRegistry; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractExtension; class DoctrineOrmExtension extends AbstractExtension { protected $registry; public function __construct(ManagerRegistry $registry) { $this->registry = $registry; } protected function loadTypes() { return [ new EntityType($this->registry), ]; } protected function loadTypeGuesser() { return new DoctrineOrmTypeGuesser($this->registry); } } Form/DoctrineOrmTypeGuesser.php 0000644 00000017571 15120140637 0012617 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Form; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Mapping\MappingException as LegacyMappingException; use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\Mapping\MappingException; use Doctrine\Persistence\Proxy; use Symfony\Component\Form\FormTypeGuesserInterface; use Symfony\Component\Form\Guess\Guess; use Symfony\Component\Form\Guess\TypeGuess; use Symfony\Component\Form\Guess\ValueGuess; class DoctrineOrmTypeGuesser implements FormTypeGuesserInterface { protected $registry; private $cache = []; public function __construct(ManagerRegistry $registry) { $this->registry = $registry; } /** * {@inheritdoc} */ public function guessType(string $class, string $property) { if (!$ret = $this->getMetadata($class)) { return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\TextType', [], Guess::LOW_CONFIDENCE); } [$metadata, $name] = $ret; if ($metadata->hasAssociation($property)) { $multiple = $metadata->isCollectionValuedAssociation($property); $mapping = $metadata->getAssociationMapping($property); return new TypeGuess('Symfony\Bridge\Doctrine\Form\Type\EntityType', ['em' => $name, 'class' => $mapping['targetEntity'], 'multiple' => $multiple], Guess::HIGH_CONFIDENCE); } switch ($metadata->getTypeOfField($property)) { case Types::ARRAY: case Types::SIMPLE_ARRAY: return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\CollectionType', [], Guess::MEDIUM_CONFIDENCE); case Types::BOOLEAN: return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\CheckboxType', [], Guess::HIGH_CONFIDENCE); case Types::DATETIME_MUTABLE: case Types::DATETIMETZ_MUTABLE: case 'vardatetime': return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\DateTimeType', [], Guess::HIGH_CONFIDENCE); case Types::DATETIME_IMMUTABLE: case Types::DATETIMETZ_IMMUTABLE: return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\DateTimeType', ['input' => 'datetime_immutable'], Guess::HIGH_CONFIDENCE); case Types::DATEINTERVAL: return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\DateIntervalType', [], Guess::HIGH_CONFIDENCE); case Types::DATE_MUTABLE: return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\DateType', [], Guess::HIGH_CONFIDENCE); case Types::DATE_IMMUTABLE: return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\DateType', ['input' => 'datetime_immutable'], Guess::HIGH_CONFIDENCE); case Types::TIME_MUTABLE: return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\TimeType', [], Guess::HIGH_CONFIDENCE); case Types::TIME_IMMUTABLE: return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\TimeType', ['input' => 'datetime_immutable'], Guess::HIGH_CONFIDENCE); case Types::DECIMAL: return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\NumberType', ['input' => 'string'], Guess::MEDIUM_CONFIDENCE); case Types::FLOAT: return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\NumberType', [], Guess::MEDIUM_CONFIDENCE); case Types::INTEGER: case Types::BIGINT: case Types::SMALLINT: return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\IntegerType', [], Guess::MEDIUM_CONFIDENCE); case Types::STRING: return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\TextType', [], Guess::MEDIUM_CONFIDENCE); case Types::TEXT: return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\TextareaType', [], Guess::MEDIUM_CONFIDENCE); default: return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\TextType', [], Guess::LOW_CONFIDENCE); } } /** * {@inheritdoc} */ public function guessRequired(string $class, string $property) { $classMetadatas = $this->getMetadata($class); if (!$classMetadatas) { return null; } /** @var ClassMetadataInfo $classMetadata */ $classMetadata = $classMetadatas[0]; // Check whether the field exists and is nullable or not if (isset($classMetadata->fieldMappings[$property])) { if (!$classMetadata->isNullable($property) && Types::BOOLEAN !== $classMetadata->getTypeOfField($property)) { return new ValueGuess(true, Guess::HIGH_CONFIDENCE); } return new ValueGuess(false, Guess::MEDIUM_CONFIDENCE); } // Check whether the association exists, is a to-one association and its // join column is nullable or not if ($classMetadata->isAssociationWithSingleJoinColumn($property)) { $mapping = $classMetadata->getAssociationMapping($property); if (!isset($mapping['joinColumns'][0]['nullable'])) { // The "nullable" option defaults to true, in that case the // field should not be required. return new ValueGuess(false, Guess::HIGH_CONFIDENCE); } return new ValueGuess(!$mapping['joinColumns'][0]['nullable'], Guess::HIGH_CONFIDENCE); } return null; } /** * {@inheritdoc} */ public function guessMaxLength(string $class, string $property) { $ret = $this->getMetadata($class); if ($ret && isset($ret[0]->fieldMappings[$property]) && !$ret[0]->hasAssociation($property)) { $mapping = $ret[0]->getFieldMapping($property); if (isset($mapping['length'])) { return new ValueGuess($mapping['length'], Guess::HIGH_CONFIDENCE); } if (\in_array($ret[0]->getTypeOfField($property), [Types::DECIMAL, Types::FLOAT])) { return new ValueGuess(null, Guess::MEDIUM_CONFIDENCE); } } return null; } /** * {@inheritdoc} */ public function guessPattern(string $class, string $property) { $ret = $this->getMetadata($class); if ($ret && isset($ret[0]->fieldMappings[$property]) && !$ret[0]->hasAssociation($property)) { if (\in_array($ret[0]->getTypeOfField($property), [Types::DECIMAL, Types::FLOAT])) { return new ValueGuess(null, Guess::MEDIUM_CONFIDENCE); } } return null; } protected function getMetadata(string $class) { // normalize class name $class = self::getRealClass(ltrim($class, '\\')); if (\array_key_exists($class, $this->cache)) { return $this->cache[$class]; } $this->cache[$class] = null; foreach ($this->registry->getManagers() as $name => $em) { try { return $this->cache[$class] = [$em->getClassMetadata($class), $name]; } catch (MappingException $e) { // not an entity or mapped super class } catch (LegacyMappingException $e) { // not an entity or mapped super class, using Doctrine ORM 2.2 } } return null; } private static function getRealClass(string $class): string { if (false === $pos = strrpos($class, '\\'.Proxy::MARKER.'\\')) { return $class; } return substr($class, $pos + Proxy::MARKER_LENGTH + 2); } } IdGenerator/UlidGenerator.php 0000644 00000002026 15120140637 0012223 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\IdGenerator; use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Id\AbstractIdGenerator; use Symfony\Component\Uid\Factory\UlidFactory; use Symfony\Component\Uid\Ulid; final class UlidGenerator extends AbstractIdGenerator { private $factory; public function __construct(UlidFactory $factory = null) { $this->factory = $factory; } /** * doctrine/orm < 2.11 BC layer. */ public function generate(EntityManager $em, $entity): Ulid { return $this->generateId($em, $entity); } public function generateId(EntityManagerInterface $em, $entity): Ulid { if ($this->factory) { return $this->factory->create(); } return new Ulid(); } } IdGenerator/UuidGenerator.php 0000644 00000004320 15120140637 0012233 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\IdGenerator; use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Id\AbstractIdGenerator; use Symfony\Component\Uid\Factory\UuidFactory; use Symfony\Component\Uid\Uuid; final class UuidGenerator extends AbstractIdGenerator { private $protoFactory; private $factory; private $entityGetter; public function __construct(UuidFactory $factory = null) { $this->protoFactory = $this->factory = $factory ?? new UuidFactory(); } /** * doctrine/orm < 2.11 BC layer. */ public function generate(EntityManager $em, $entity): Uuid { return $this->generateId($em, $entity); } public function generateId(EntityManagerInterface $em, $entity): Uuid { if (null !== $this->entityGetter) { if (\is_callable([$entity, $this->entityGetter])) { return $this->factory->create($entity->{$this->entityGetter}()); } return $this->factory->create($entity->{$this->entityGetter}); } return $this->factory->create(); } /** * @param Uuid|string|null $namespace * * @return static */ public function nameBased(string $entityGetter, $namespace = null): self { $clone = clone $this; $clone->factory = $clone->protoFactory->nameBased($namespace); $clone->entityGetter = $entityGetter; return $clone; } /** * @return static */ public function randomBased(): self { $clone = clone $this; $clone->factory = $clone->protoFactory->randomBased(); $clone->entityGetter = null; return $clone; } /** * @param Uuid|string|null $node * * @return static */ public function timeBased($node = null): self { $clone = clone $this; $clone->factory = $clone->protoFactory->timeBased($node); $clone->entityGetter = null; return $clone; } } Logger/DbalLogger.php 0000644 00000004722 15120140637 0010502 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Logger; use Doctrine\DBAL\Logging\SQLLogger; use Psr\Log\LoggerInterface; use Symfony\Component\Stopwatch\Stopwatch; /** * @author Fabien Potencier <fabien@symfony.com> */ class DbalLogger implements SQLLogger { public const MAX_STRING_LENGTH = 32; public const BINARY_DATA_VALUE = '(binary value)'; protected $logger; protected $stopwatch; public function __construct(LoggerInterface $logger = null, Stopwatch $stopwatch = null) { $this->logger = $logger; $this->stopwatch = $stopwatch; } /** * {@inheritdoc} * * @return void */ public function startQuery($sql, array $params = null, array $types = null) { if (null !== $this->stopwatch) { $this->stopwatch->start('doctrine', 'doctrine'); } if (null !== $this->logger) { $this->log($sql, null === $params ? [] : $this->normalizeParams($params)); } } /** * {@inheritdoc} * * @return void */ public function stopQuery() { if (null !== $this->stopwatch) { $this->stopwatch->stop('doctrine'); } } /** * Logs a message. */ protected function log(string $message, array $params) { $this->logger->debug($message, $params); } private function normalizeParams(array $params): array { foreach ($params as $index => $param) { // normalize recursively if (\is_array($param)) { $params[$index] = $this->normalizeParams($param); continue; } if (!\is_string($params[$index])) { continue; } // non utf-8 strings break json encoding if (!preg_match('//u', $params[$index])) { $params[$index] = self::BINARY_DATA_VALUE; continue; } // detect if the too long string must be shorten if (self::MAX_STRING_LENGTH < mb_strlen($params[$index], 'UTF-8')) { $params[$index] = mb_substr($params[$index], 0, self::MAX_STRING_LENGTH - 6, 'UTF-8').' [...]'; continue; } } return $params; } } Messenger/AbstractDoctrineMiddleware.php 0000644 00000003126 15120140637 0014437 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Messenger; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\ManagerRegistry; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException; use Symfony\Component\Messenger\Middleware\MiddlewareInterface; use Symfony\Component\Messenger\Middleware\StackInterface; /** * @author Konstantin Myakshin <molodchick@gmail.com> * * @internal */ abstract class AbstractDoctrineMiddleware implements MiddlewareInterface { protected $managerRegistry; protected $entityManagerName; public function __construct(ManagerRegistry $managerRegistry, string $entityManagerName = null) { $this->managerRegistry = $managerRegistry; $this->entityManagerName = $entityManagerName; } final public function handle(Envelope $envelope, StackInterface $stack): Envelope { try { $entityManager = $this->managerRegistry->getManager($this->entityManagerName); } catch (\InvalidArgumentException $e) { throw new UnrecoverableMessageHandlingException($e->getMessage(), 0, $e); } return $this->handleForManager($entityManager, $envelope, $stack); } abstract protected function handleForManager(EntityManagerInterface $entityManager, Envelope $envelope, StackInterface $stack): Envelope; } Messenger/DoctrineClearEntityManagerWorkerSubscriber.php 0000644 00000002736 15120140637 0017640 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Messenger; use Doctrine\Persistence\ManagerRegistry; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent; use Symfony\Component\Messenger\Event\WorkerMessageHandledEvent; /** * Clears entity managers between messages being handled to avoid outdated data. * * @author Ryan Weaver <ryan@symfonycasts.com> */ class DoctrineClearEntityManagerWorkerSubscriber implements EventSubscriberInterface { private $managerRegistry; public function __construct(ManagerRegistry $managerRegistry) { $this->managerRegistry = $managerRegistry; } public function onWorkerMessageHandled() { $this->clearEntityManagers(); } public function onWorkerMessageFailed() { $this->clearEntityManagers(); } public static function getSubscribedEvents() { return [ WorkerMessageHandledEvent::class => 'onWorkerMessageHandled', WorkerMessageFailedEvent::class => 'onWorkerMessageFailed', ]; } private function clearEntityManagers() { foreach ($this->managerRegistry->getManagers() as $manager) { $manager->clear(); } } } Messenger/DoctrineCloseConnectionMiddleware.php 0000644 00000002140 15120140637 0015754 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Messenger; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Middleware\StackInterface; use Symfony\Component\Messenger\Stamp\ConsumedByWorkerStamp; /** * Closes connection and therefore saves number of connections. * * @author Fuong <insidestyles@gmail.com> */ class DoctrineCloseConnectionMiddleware extends AbstractDoctrineMiddleware { protected function handleForManager(EntityManagerInterface $entityManager, Envelope $envelope, StackInterface $stack): Envelope { try { $connection = $entityManager->getConnection(); return $stack->next()->handle($envelope, $stack); } finally { if (null !== $envelope->last(ConsumedByWorkerStamp::class)) { $connection->close(); } } } } Messenger/DoctrineOpenTransactionLoggerMiddleware.php 0000644 00000002764 15120140637 0017152 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Messenger; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\ManagerRegistry; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Middleware\StackInterface; /** * Middleware to log when transaction has been left open. * * @author Grégoire Pineau <lyrixx@lyrixx.info> */ class DoctrineOpenTransactionLoggerMiddleware extends AbstractDoctrineMiddleware { private $logger; public function __construct(ManagerRegistry $managerRegistry, string $entityManagerName = null, LoggerInterface $logger = null) { parent::__construct($managerRegistry, $entityManagerName); $this->logger = $logger ?? new NullLogger(); } protected function handleForManager(EntityManagerInterface $entityManager, Envelope $envelope, StackInterface $stack): Envelope { try { return $stack->next()->handle($envelope, $stack); } finally { if ($entityManager->getConnection()->isTransactionActive()) { $this->logger->error('A handler opened a transaction but did not close it.', [ 'message' => $envelope->getMessage(), ]); } } } } Messenger/DoctrinePingConnectionMiddleware.php 0000644 00000003042 15120140637 0015606 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Messenger; use Doctrine\DBAL\Exception as DBALException; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Middleware\StackInterface; use Symfony\Component\Messenger\Stamp\ConsumedByWorkerStamp; /** * Checks whether the connection is still open or reconnects otherwise. * * @author Fuong <insidestyles@gmail.com> */ class DoctrinePingConnectionMiddleware extends AbstractDoctrineMiddleware { protected function handleForManager(EntityManagerInterface $entityManager, Envelope $envelope, StackInterface $stack): Envelope { if (null !== $envelope->last(ConsumedByWorkerStamp::class)) { $this->pingConnection($entityManager); } return $stack->next()->handle($envelope, $stack); } private function pingConnection(EntityManagerInterface $entityManager) { $connection = $entityManager->getConnection(); try { $connection->executeQuery($connection->getDatabasePlatform()->getDummySelectSQL()); } catch (DBALException $e) { $connection->close(); $connection->connect(); } if (!$entityManager->isOpen()) { $this->managerRegistry->resetManager($this->entityManagerName); } } } Messenger/DoctrineTransactionMiddleware.php 0000644 00000003307 15120140637 0015162 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Messenger; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Exception\HandlerFailedException; use Symfony\Component\Messenger\Middleware\StackInterface; use Symfony\Component\Messenger\Stamp\HandledStamp; /** * Wraps all handlers in a single doctrine transaction. * * @author Tobias Nyholm <tobias.nyholm@gmail.com> */ class DoctrineTransactionMiddleware extends AbstractDoctrineMiddleware { protected function handleForManager(EntityManagerInterface $entityManager, Envelope $envelope, StackInterface $stack): Envelope { $entityManager->getConnection()->beginTransaction(); try { $envelope = $stack->next()->handle($envelope, $stack); $entityManager->flush(); $entityManager->getConnection()->commit(); return $envelope; } catch (\Throwable $exception) { $entityManager->getConnection()->rollBack(); if ($exception instanceof HandlerFailedException) { // Remove all HandledStamp from the envelope so the retry will execute all handlers again. // When a handler fails, the queries of allegedly successful previous handlers just got rolled back. throw new HandlerFailedException($exception->getEnvelope()->withoutAll(HandledStamp::class), $exception->getNestedExceptions()); } throw $exception; } } } Middleware/Debug/Connection.php 0000644 00000011073 15120140637 0012460 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Middleware\Debug; use Doctrine\DBAL\Driver\Connection as ConnectionInterface; use Doctrine\DBAL\Driver\Middleware\AbstractConnectionMiddleware; use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\Driver\Statement as DriverStatement; use Symfony\Component\Stopwatch\Stopwatch; /** * @author Laurent VOULLEMIER <laurent.voullemier@gmail.com> * * @internal */ final class Connection extends AbstractConnectionMiddleware { private $nestingLevel = 0; private $debugDataHolder; private $stopwatch; private $connectionName; public function __construct(ConnectionInterface $connection, DebugDataHolder $debugDataHolder, ?Stopwatch $stopwatch, string $connectionName) { parent::__construct($connection); $this->debugDataHolder = $debugDataHolder; $this->stopwatch = $stopwatch; $this->connectionName = $connectionName; } public function prepare(string $sql): DriverStatement { return new Statement( parent::prepare($sql), $this->debugDataHolder, $this->connectionName, $sql, $this->stopwatch, ); } public function query(string $sql): Result { $this->debugDataHolder->addQuery($this->connectionName, $query = new Query($sql)); if (null !== $this->stopwatch) { $this->stopwatch->start('doctrine', 'doctrine'); } $query->start(); try { $result = parent::query($sql); } finally { $query->stop(); if (null !== $this->stopwatch) { $this->stopwatch->stop('doctrine'); } } return $result; } public function exec(string $sql): int { $this->debugDataHolder->addQuery($this->connectionName, $query = new Query($sql)); if (null !== $this->stopwatch) { $this->stopwatch->start('doctrine', 'doctrine'); } $query->start(); try { $affectedRows = parent::exec($sql); } finally { $query->stop(); if (null !== $this->stopwatch) { $this->stopwatch->stop('doctrine'); } } return $affectedRows; } public function beginTransaction(): bool { $query = null; if (1 === ++$this->nestingLevel) { $this->debugDataHolder->addQuery($this->connectionName, $query = new Query('"START TRANSACTION"')); } if (null !== $this->stopwatch) { $this->stopwatch->start('doctrine', 'doctrine'); } if (null !== $query) { $query->start(); } try { $ret = parent::beginTransaction(); } finally { if (null !== $query) { $query->stop(); } if (null !== $this->stopwatch) { $this->stopwatch->stop('doctrine'); } } return $ret; } public function commit(): bool { $query = null; if (1 === $this->nestingLevel--) { $this->debugDataHolder->addQuery($this->connectionName, $query = new Query('"COMMIT"')); } if (null !== $this->stopwatch) { $this->stopwatch->start('doctrine', 'doctrine'); } if (null !== $query) { $query->start(); } try { $ret = parent::commit(); } finally { if (null !== $query) { $query->stop(); } if (null !== $this->stopwatch) { $this->stopwatch->stop('doctrine'); } } return $ret; } public function rollBack(): bool { $query = null; if (1 === $this->nestingLevel--) { $this->debugDataHolder->addQuery($this->connectionName, $query = new Query('"ROLLBACK"')); } if (null !== $this->stopwatch) { $this->stopwatch->start('doctrine', 'doctrine'); } if (null !== $query) { $query->start(); } try { $ret = parent::rollBack(); } finally { if (null !== $query) { $query->stop(); } if (null !== $this->stopwatch) { $this->stopwatch->stop('doctrine'); } } return $ret; } } Middleware/Debug/DebugDataHolder.php 0000644 00000002333 15120140637 0013336 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Middleware\Debug; /** * @author Laurent VOULLEMIER <laurent.voullemier@gmail.com> */ class DebugDataHolder { private $data = []; public function addQuery(string $connectionName, Query $query): void { $this->data[$connectionName][] = [ 'sql' => $query->getSql(), 'params' => $query->getParams(), 'types' => $query->getTypes(), 'executionMS' => [$query, 'getDuration'], // stop() may not be called at this point ]; } public function getData(): array { foreach ($this->data as $connectionName => $dataForConn) { foreach ($dataForConn as $idx => $data) { if (\is_callable($data['executionMS'])) { $this->data[$connectionName][$idx]['executionMS'] = $data['executionMS'](); } } } return $this->data; } public function reset(): void { $this->data = []; } } Middleware/Debug/Driver.php 0000644 00000002333 15120140637 0011613 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Middleware\Debug; use Doctrine\DBAL\Driver as DriverInterface; use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; use Symfony\Component\Stopwatch\Stopwatch; /** * @author Laurent VOULLEMIER <laurent.voullemier@gmail.com> * * @internal */ final class Driver extends AbstractDriverMiddleware { private $debugDataHolder; private $stopwatch; private $connectionName; public function __construct(DriverInterface $driver, DebugDataHolder $debugDataHolder, ?Stopwatch $stopwatch, string $connectionName) { parent::__construct($driver); $this->debugDataHolder = $debugDataHolder; $this->stopwatch = $stopwatch; $this->connectionName = $connectionName; } public function connect(array $params): Connection { return new Connection( parent::connect($params), $this->debugDataHolder, $this->stopwatch, $this->connectionName ); } } Middleware/Debug/Middleware.php 0000644 00000002174 15120140637 0012440 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Middleware\Debug; use Doctrine\DBAL\Driver as DriverInterface; use Doctrine\DBAL\Driver\Middleware as MiddlewareInterface; use Symfony\Component\Stopwatch\Stopwatch; /** * Middleware to collect debug data. * * @author Laurent VOULLEMIER <laurent.voullemier@gmail.com> */ final class Middleware implements MiddlewareInterface { private $debugDataHolder; private $stopwatch; private $connectionName; public function __construct(DebugDataHolder $debugDataHolder, ?Stopwatch $stopwatch, string $connectionName = 'default') { $this->debugDataHolder = $debugDataHolder; $this->stopwatch = $stopwatch; $this->connectionName = $connectionName; } public function wrap(DriverInterface $driver): DriverInterface { return new Driver($driver, $this->debugDataHolder, $this->stopwatch, $this->connectionName); } } Middleware/Debug/Query.php 0000644 00000004714 15120140637 0011472 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Middleware\Debug; use Doctrine\DBAL\ParameterType; /** * @author Laurent VOULLEMIER <laurent.voullemier@gmail.com> * * @internal */ class Query { private $params = []; private $types = []; private $start; private $duration; private $sql; public function __construct(string $sql) { $this->sql = $sql; } public function start(): void { $this->start = microtime(true); } public function stop(): void { if (null !== $this->start) { $this->duration = microtime(true) - $this->start; } } /** * @param string|int $param * @param mixed $variable */ public function setParam($param, &$variable, int $type): void { // Numeric indexes start at 0 in profiler $idx = \is_int($param) ? $param - 1 : $param; $this->params[$idx] = &$variable; $this->types[$idx] = $type; } /** * @param string|int $param * @param mixed $value */ public function setValue($param, $value, int $type): void { // Numeric indexes start at 0 in profiler $idx = \is_int($param) ? $param - 1 : $param; $this->params[$idx] = $value; $this->types[$idx] = $type; } /** * @param array<string|int, string|int|float> $values */ public function setValues(array $values): void { foreach ($values as $param => $value) { $this->setValue($param, $value, ParameterType::STRING); } } public function getSql(): string { return $this->sql; } /** * @return array<int, string|int|float}> */ public function getParams(): array { return $this->params; } /** * @return array<int, int> */ public function getTypes(): array { return $this->types; } /** * Query duration in seconds. */ public function getDuration(): ?float { return $this->duration; } public function __clone() { $copy = []; foreach ($this->params as $param => $valueOrVariable) { $copy[$param] = $valueOrVariable; } $this->params = $copy; } } Middleware/Debug/Statement.php 0000644 00000004501 15120140637 0012323 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Middleware\Debug; use Doctrine\DBAL\Driver\Middleware\AbstractStatementMiddleware; use Doctrine\DBAL\Driver\Result as ResultInterface; use Doctrine\DBAL\Driver\Statement as StatementInterface; use Doctrine\DBAL\ParameterType; use Symfony\Component\Stopwatch\Stopwatch; /** * @author Laurent VOULLEMIER <laurent.voullemier@gmail.com> * * @internal */ final class Statement extends AbstractStatementMiddleware { private $debugDataHolder; private $connectionName; private $query; private $stopwatch; public function __construct(StatementInterface $statement, DebugDataHolder $debugDataHolder, string $connectionName, string $sql, Stopwatch $stopwatch = null) { parent::__construct($statement); $this->debugDataHolder = $debugDataHolder; $this->connectionName = $connectionName; $this->query = new Query($sql); $this->stopwatch = $stopwatch; } public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool { $this->query->setParam($param, $variable, $type); return parent::bindParam($param, $variable, $type, ...\array_slice(\func_get_args(), 3)); } public function bindValue($param, $value, $type = ParameterType::STRING): bool { $this->query->setValue($param, $value, $type); return parent::bindValue($param, $value, $type); } public function execute($params = null): ResultInterface { if (null !== $params) { $this->query->setValues($params); } // clone to prevent variables by reference to change $this->debugDataHolder->addQuery($this->connectionName, $query = clone $this->query); if ($this->stopwatch) { $this->stopwatch->start('doctrine', 'doctrine'); } $query->start(); try { $result = parent::execute($params); } finally { $query->stop(); if ($this->stopwatch) { $this->stopwatch->stop('doctrine'); } } return $result; } } PropertyInfo/DoctrineExtractor.php 0000644 00000025616 15120140637 0013371 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\PropertyInfo; use Doctrine\Common\Collections\Collection; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Mapping\Embedded; use Doctrine\ORM\Mapping\MappingException as OrmMappingException; use Doctrine\Persistence\Mapping\MappingException; use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface; use Symfony\Component\PropertyInfo\PropertyListExtractorInterface; use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; use Symfony\Component\PropertyInfo\Type; /** * Extracts data using Doctrine ORM and ODM metadata. * * @author Kévin Dunglas <dunglas@gmail.com> */ class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface { private $entityManager; public function __construct(EntityManagerInterface $entityManager) { $this->entityManager = $entityManager; } /** * {@inheritdoc} */ public function getProperties(string $class, array $context = []) { if (null === $metadata = $this->getMetadata($class)) { return null; } $properties = array_merge($metadata->getFieldNames(), $metadata->getAssociationNames()); if ($metadata instanceof ClassMetadataInfo && class_exists(Embedded::class) && $metadata->embeddedClasses) { $properties = array_filter($properties, function ($property) { return !str_contains($property, '.'); }); $properties = array_merge($properties, array_keys($metadata->embeddedClasses)); } return $properties; } /** * {@inheritdoc} */ public function getTypes(string $class, string $property, array $context = []) { if (null === $metadata = $this->getMetadata($class)) { return null; } if ($metadata->hasAssociation($property)) { $class = $metadata->getAssociationTargetClass($property); if ($metadata->isSingleValuedAssociation($property)) { if ($metadata instanceof ClassMetadataInfo) { $associationMapping = $metadata->getAssociationMapping($property); $nullable = $this->isAssociationNullable($associationMapping); } else { $nullable = false; } return [new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $class)]; } $collectionKeyType = Type::BUILTIN_TYPE_INT; if ($metadata instanceof ClassMetadataInfo) { $associationMapping = $metadata->getAssociationMapping($property); if (isset($associationMapping['indexBy'])) { /** @var ClassMetadataInfo $subMetadata */ $subMetadata = $this->entityManager->getClassMetadata($associationMapping['targetEntity']); // Check if indexBy value is a property $fieldName = $associationMapping['indexBy']; if (null === ($typeOfField = $subMetadata->getTypeOfField($fieldName))) { $fieldName = $subMetadata->getFieldForColumn($associationMapping['indexBy']); // Not a property, maybe a column name? if (null === ($typeOfField = $subMetadata->getTypeOfField($fieldName))) { // Maybe the column name is the association join column? $associationMapping = $subMetadata->getAssociationMapping($fieldName); /** @var ClassMetadataInfo $subMetadata */ $indexProperty = $subMetadata->getSingleAssociationReferencedJoinColumnName($fieldName); $subMetadata = $this->entityManager->getClassMetadata($associationMapping['targetEntity']); // Not a property, maybe a column name? if (null === ($typeOfField = $subMetadata->getTypeOfField($indexProperty))) { $fieldName = $subMetadata->getFieldForColumn($indexProperty); $typeOfField = $subMetadata->getTypeOfField($fieldName); } } } if (!$collectionKeyType = $this->getPhpType($typeOfField)) { return null; } } } return [new Type( Type::BUILTIN_TYPE_OBJECT, false, Collection::class, true, new Type($collectionKeyType), new Type(Type::BUILTIN_TYPE_OBJECT, false, $class) )]; } if ($metadata instanceof ClassMetadataInfo && class_exists(Embedded::class) && isset($metadata->embeddedClasses[$property])) { return [new Type(Type::BUILTIN_TYPE_OBJECT, false, $metadata->embeddedClasses[$property]['class'])]; } if ($metadata->hasField($property)) { $typeOfField = $metadata->getTypeOfField($property); if (!$builtinType = $this->getPhpType($typeOfField)) { return null; } $nullable = $metadata instanceof ClassMetadataInfo && $metadata->isNullable($property); $enumType = null; if (null !== $enumClass = $metadata->getFieldMapping($property)['enumType'] ?? null) { $enumType = new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $enumClass); } switch ($builtinType) { case Type::BUILTIN_TYPE_OBJECT: switch ($typeOfField) { case Types::DATE_MUTABLE: case Types::DATETIME_MUTABLE: case Types::DATETIMETZ_MUTABLE: case 'vardatetime': case Types::TIME_MUTABLE: return [new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, 'DateTime')]; case Types::DATE_IMMUTABLE: case Types::DATETIME_IMMUTABLE: case Types::DATETIMETZ_IMMUTABLE: case Types::TIME_IMMUTABLE: return [new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, 'DateTimeImmutable')]; case Types::DATEINTERVAL: return [new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, 'DateInterval')]; } break; case Type::BUILTIN_TYPE_ARRAY: switch ($typeOfField) { case Types::ARRAY: case 'json_array': // return null if $enumType is set, because we can't determine if collectionKeyType is string or int if ($enumType) { return null; } return [new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true)]; case Types::SIMPLE_ARRAY: return [new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, new Type(Type::BUILTIN_TYPE_INT), $enumType ?? new Type(Type::BUILTIN_TYPE_STRING))]; } break; case Type::BUILTIN_TYPE_INT: case Type::BUILTIN_TYPE_STRING: if ($enumType) { return [$enumType]; } break; } return [new Type($builtinType, $nullable)]; } return null; } /** * {@inheritdoc} */ public function isReadable(string $class, string $property, array $context = []) { return null; } /** * {@inheritdoc} */ public function isWritable(string $class, string $property, array $context = []) { if ( null === ($metadata = $this->getMetadata($class)) || ClassMetadata::GENERATOR_TYPE_NONE === $metadata->generatorType || !\in_array($property, $metadata->getIdentifierFieldNames(), true) ) { return null; } return false; } private function getMetadata(string $class): ?ClassMetadata { try { return $this->entityManager->getClassMetadata($class); } catch (MappingException|OrmMappingException $exception) { return null; } } /** * Determines whether an association is nullable. * * @see https://github.com/doctrine/doctrine2/blob/v2.5.4/lib/Doctrine/ORM/Tools/EntityGenerator.php#L1221-L1246 */ private function isAssociationNullable(array $associationMapping): bool { if (isset($associationMapping['id']) && $associationMapping['id']) { return false; } if (!isset($associationMapping['joinColumns'])) { return true; } $joinColumns = $associationMapping['joinColumns']; foreach ($joinColumns as $joinColumn) { if (isset($joinColumn['nullable']) && !$joinColumn['nullable']) { return false; } } return true; } /** * Gets the corresponding built-in PHP type. */ private function getPhpType(string $doctrineType): ?string { switch ($doctrineType) { case Types::SMALLINT: case Types::INTEGER: return Type::BUILTIN_TYPE_INT; case Types::FLOAT: return Type::BUILTIN_TYPE_FLOAT; case Types::BIGINT: case Types::STRING: case Types::TEXT: case Types::GUID: case Types::DECIMAL: return Type::BUILTIN_TYPE_STRING; case Types::BOOLEAN: return Type::BUILTIN_TYPE_BOOL; case Types::BLOB: case Types::BINARY: return Type::BUILTIN_TYPE_RESOURCE; case Types::OBJECT: case Types::DATE_MUTABLE: case Types::DATETIME_MUTABLE: case Types::DATETIMETZ_MUTABLE: case 'vardatetime': case Types::TIME_MUTABLE: case Types::DATE_IMMUTABLE: case Types::DATETIME_IMMUTABLE: case Types::DATETIMETZ_IMMUTABLE: case Types::TIME_IMMUTABLE: case Types::DATEINTERVAL: return Type::BUILTIN_TYPE_OBJECT; case Types::ARRAY: case Types::SIMPLE_ARRAY: case 'json_array': return Type::BUILTIN_TYPE_ARRAY; } return null; } } SchemaListener/DoctrineDbalCacheAdapterSchemaSubscriber.php 0000644 00000002665 15120140637 0020117 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\SchemaListener; use Doctrine\Common\EventSubscriber; use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; use Doctrine\ORM\Tools\ToolEvents; use Symfony\Component\Cache\Adapter\DoctrineDbalAdapter; /** * Automatically adds the cache table needed for the DoctrineDbalAdapter of * the Cache component. * * @author Ryan Weaver <ryan@symfonycasts.com> */ final class DoctrineDbalCacheAdapterSchemaSubscriber implements EventSubscriber { private $dbalAdapters; /** * @param iterable<mixed, DoctrineDbalAdapter> $dbalAdapters */ public function __construct(iterable $dbalAdapters) { $this->dbalAdapters = $dbalAdapters; } public function postGenerateSchema(GenerateSchemaEventArgs $event): void { $dbalConnection = $event->getEntityManager()->getConnection(); foreach ($this->dbalAdapters as $dbalAdapter) { $dbalAdapter->configureSchema($event->getSchema(), $dbalConnection); } } public function getSubscribedEvents(): array { if (!class_exists(ToolEvents::class)) { return []; } return [ ToolEvents::postGenerateSchema, ]; } } SchemaListener/MessengerTransportDoctrineSchemaSubscriber.php 0000644 00000006135 15120140637 0020671 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\SchemaListener; use Doctrine\Common\EventSubscriber; use Doctrine\DBAL\Event\SchemaCreateTableEventArgs; use Doctrine\DBAL\Events; use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; use Doctrine\ORM\Tools\ToolEvents; use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineTransport; use Symfony\Component\Messenger\Transport\TransportInterface; /** * Automatically adds any required database tables to the Doctrine Schema. * * @author Ryan Weaver <ryan@symfonycasts.com> */ final class MessengerTransportDoctrineSchemaSubscriber implements EventSubscriber { private const PROCESSING_TABLE_FLAG = self::class.':processing'; private $transports; /** * @param iterable<mixed, TransportInterface> $transports */ public function __construct(iterable $transports) { $this->transports = $transports; } public function postGenerateSchema(GenerateSchemaEventArgs $event): void { $dbalConnection = $event->getEntityManager()->getConnection(); foreach ($this->transports as $transport) { if (!$transport instanceof DoctrineTransport) { continue; } $transport->configureSchema($event->getSchema(), $dbalConnection); } } public function onSchemaCreateTable(SchemaCreateTableEventArgs $event): void { $table = $event->getTable(); // if this method triggers a nested create table below, allow Doctrine to work like normal if ($table->hasOption(self::PROCESSING_TABLE_FLAG)) { return; } foreach ($this->transports as $transport) { if (!$transport instanceof DoctrineTransport) { continue; } if (!$extraSql = $transport->getExtraSetupSqlForTable($table)) { continue; } // avoid this same listener from creating a loop on this table $table->addOption(self::PROCESSING_TABLE_FLAG, true); $createTableSql = $event->getPlatform()->getCreateTableSQL($table); /* * Add all the SQL needed to create the table and tell Doctrine * to "preventDefault" so that only our SQL is used. This is * the only way to inject some extra SQL. */ $event->addSql($createTableSql); foreach ($extraSql as $sql) { $event->addSql($sql); } $event->preventDefault(); return; } } public function getSubscribedEvents(): array { $subscribedEvents = []; if (class_exists(ToolEvents::class)) { $subscribedEvents[] = ToolEvents::postGenerateSchema; } if (class_exists(Events::class)) { $subscribedEvents[] = Events::onSchemaCreateTable; } return $subscribedEvents; } } SchemaListener/PdoCacheAdapterDoctrineSchemaSubscriber.php 0000644 00000003320 15120140637 0017764 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\SchemaListener; use Doctrine\Common\EventSubscriber; use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; use Doctrine\ORM\Tools\ToolEvents; use Symfony\Component\Cache\Adapter\PdoAdapter; /** * Automatically adds the cache table needed for the PdoAdapter. * * @author Ryan Weaver <ryan@symfonycasts.com> * * @deprecated since symfony 5.4 use DoctrineDbalCacheAdapterSchemaSubscriber */ final class PdoCacheAdapterDoctrineSchemaSubscriber implements EventSubscriber { private $pdoAdapters; /** * @param iterable<mixed, PdoAdapter> $pdoAdapters */ public function __construct(iterable $pdoAdapters) { $this->pdoAdapters = $pdoAdapters; } public function postGenerateSchema(GenerateSchemaEventArgs $event): void { $dbalConnection = $event->getEntityManager()->getConnection(); foreach ($this->pdoAdapters as $pdoAdapter) { if (PdoAdapter::class !== \get_class($pdoAdapter)) { trigger_deprecation('symfony/doctrine-bridge', '5.4', 'The "%s" class is deprecated, use "%s" instead.', self::class, DoctrineDbalCacheAdapterSchemaSubscriber::class); } $pdoAdapter->configureSchema($event->getSchema(), $dbalConnection); } } public function getSubscribedEvents(): array { if (!class_exists(ToolEvents::class)) { return []; } return [ ToolEvents::postGenerateSchema, ]; } } SchemaListener/RememberMeTokenProviderDoctrineSchemaSubscriber.php 0000644 00000003563 15120140637 0021562 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\SchemaListener; use Doctrine\Common\EventSubscriber; use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; use Doctrine\ORM\Tools\ToolEvents; use Symfony\Bridge\Doctrine\Security\RememberMe\DoctrineTokenProvider; use Symfony\Component\Security\Http\RememberMe\PersistentRememberMeHandler; use Symfony\Component\Security\Http\RememberMe\RememberMeHandlerInterface; /** * Automatically adds the rememberme table needed for the {@see DoctrineTokenProvider}. * * @author Wouter de Jong <wouter@wouterj.nl> */ final class RememberMeTokenProviderDoctrineSchemaSubscriber implements EventSubscriber { private $rememberMeHandlers; /** * @param iterable<mixed, RememberMeHandlerInterface> $rememberMeHandlers */ public function __construct(iterable $rememberMeHandlers) { $this->rememberMeHandlers = $rememberMeHandlers; } public function postGenerateSchema(GenerateSchemaEventArgs $event): void { $dbalConnection = $event->getEntityManager()->getConnection(); foreach ($this->rememberMeHandlers as $rememberMeHandler) { if ( $rememberMeHandler instanceof PersistentRememberMeHandler && ($tokenProvider = $rememberMeHandler->getTokenProvider()) instanceof DoctrineTokenProvider ) { $tokenProvider->configureSchema($event->getSchema(), $dbalConnection); } } } public function getSubscribedEvents(): array { if (!class_exists(ToolEvents::class)) { return []; } return [ ToolEvents::postGenerateSchema, ]; } } Security/RememberMe/DoctrineTokenProvider.php 0000644 00000021161 15120140637 0015367 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Security\RememberMe; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver\Result as DriverResult; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Result; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Types\Types; use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken; use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentTokenInterface; use Symfony\Component\Security\Core\Authentication\RememberMe\TokenProviderInterface; use Symfony\Component\Security\Core\Authentication\RememberMe\TokenVerifierInterface; use Symfony\Component\Security\Core\Exception\TokenNotFoundException; /** * This class provides storage for the tokens that is set in "remember-me" * cookies. This way no password secrets will be stored in the cookies on * the client machine, and thus the security is improved. * * This depends only on doctrine in order to get a database connection * and to do the conversion of the datetime column. * * In order to use this class, you need the following table in your database: * * CREATE TABLE `rememberme_token` ( * `series` char(88) UNIQUE PRIMARY KEY NOT NULL, * `value` char(88) NOT NULL, * `lastUsed` datetime NOT NULL, * `class` varchar(100) NOT NULL, * `username` varchar(200) NOT NULL * ); */ class DoctrineTokenProvider implements TokenProviderInterface, TokenVerifierInterface { private $conn; public function __construct(Connection $conn) { $this->conn = $conn; } /** * {@inheritdoc} */ public function loadTokenBySeries(string $series) { // the alias for lastUsed works around case insensitivity in PostgreSQL $sql = 'SELECT class, username, value, lastUsed AS last_used FROM rememberme_token WHERE series=:series'; $paramValues = ['series' => $series]; $paramTypes = ['series' => ParameterType::STRING]; $stmt = $this->conn->executeQuery($sql, $paramValues, $paramTypes); $row = $stmt instanceof Result || $stmt instanceof DriverResult ? $stmt->fetchAssociative() : $stmt->fetch(\PDO::FETCH_ASSOC); if ($row) { return new PersistentToken($row['class'], $row['username'], $series, $row['value'], new \DateTime($row['last_used'])); } throw new TokenNotFoundException('No token found.'); } /** * {@inheritdoc} */ public function deleteTokenBySeries(string $series) { $sql = 'DELETE FROM rememberme_token WHERE series=:series'; $paramValues = ['series' => $series]; $paramTypes = ['series' => ParameterType::STRING]; if (method_exists($this->conn, 'executeStatement')) { $this->conn->executeStatement($sql, $paramValues, $paramTypes); } else { $this->conn->executeUpdate($sql, $paramValues, $paramTypes); } } /** * {@inheritdoc} */ public function updateToken(string $series, string $tokenValue, \DateTime $lastUsed) { $sql = 'UPDATE rememberme_token SET value=:value, lastUsed=:lastUsed WHERE series=:series'; $paramValues = [ 'value' => $tokenValue, 'lastUsed' => $lastUsed, 'series' => $series, ]; $paramTypes = [ 'value' => ParameterType::STRING, 'lastUsed' => Types::DATETIME_MUTABLE, 'series' => ParameterType::STRING, ]; if (method_exists($this->conn, 'executeStatement')) { $updated = $this->conn->executeStatement($sql, $paramValues, $paramTypes); } else { $updated = $this->conn->executeUpdate($sql, $paramValues, $paramTypes); } if ($updated < 1) { throw new TokenNotFoundException('No token found.'); } } /** * {@inheritdoc} */ public function createNewToken(PersistentTokenInterface $token) { $sql = 'INSERT INTO rememberme_token (class, username, series, value, lastUsed) VALUES (:class, :username, :series, :value, :lastUsed)'; $paramValues = [ 'class' => $token->getClass(), // @deprecated since Symfony 5.3, change to $token->getUserIdentifier() in 6.0 'username' => method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername(), 'series' => $token->getSeries(), 'value' => $token->getTokenValue(), 'lastUsed' => $token->getLastUsed(), ]; $paramTypes = [ 'class' => ParameterType::STRING, 'username' => ParameterType::STRING, 'series' => ParameterType::STRING, 'value' => ParameterType::STRING, 'lastUsed' => Types::DATETIME_MUTABLE, ]; if (method_exists($this->conn, 'executeStatement')) { $this->conn->executeStatement($sql, $paramValues, $paramTypes); } else { $this->conn->executeUpdate($sql, $paramValues, $paramTypes); } } /** * {@inheritdoc} */ public function verifyToken(PersistentTokenInterface $token, string $tokenValue): bool { // Check if the token value matches the current persisted token if (hash_equals($token->getTokenValue(), $tokenValue)) { return true; } // Generate an alternative series id here by changing the suffix == to _ // this is needed to be able to store an older token value in the database // which has a PRIMARY(series), and it works as long as series ids are // generated using base64_encode(random_bytes(64)) which always outputs // a == suffix, but if it should not work for some reason we abort // for safety $tmpSeries = preg_replace('{=+$}', '_', $token->getSeries()); if ($tmpSeries === $token->getSeries()) { return false; } // Check if the previous token is present. If the given $tokenValue // matches the previous token (and it is outdated by at most 60seconds) // we also accept it as a valid value. try { $tmpToken = $this->loadTokenBySeries($tmpSeries); } catch (TokenNotFoundException $e) { return false; } if ($tmpToken->getLastUsed()->getTimestamp() + 60 < time()) { return false; } return hash_equals($tmpToken->getTokenValue(), $tokenValue); } /** * {@inheritdoc} */ public function updateExistingToken(PersistentTokenInterface $token, string $tokenValue, \DateTimeInterface $lastUsed): void { if (!$token instanceof PersistentToken) { return; } // Persist a copy of the previous token for authentication // in verifyToken should the old token still be sent by the browser // in a request concurrent to the one that did this token update $tmpSeries = preg_replace('{=+$}', '_', $token->getSeries()); // if we cannot generate a unique series it is not worth trying further if ($tmpSeries === $token->getSeries()) { return; } $this->conn->beginTransaction(); try { $this->deleteTokenBySeries($tmpSeries); $this->createNewToken(new PersistentToken($token->getClass(), $token->getUserIdentifier(), $tmpSeries, $token->getTokenValue(), $lastUsed)); $this->conn->commit(); } catch (\Exception $e) { $this->conn->rollBack(); throw $e; } } /** * Adds the Table to the Schema if "remember me" uses this Connection. */ public function configureSchema(Schema $schema, Connection $forConnection): void { // only update the schema for this connection if ($forConnection !== $this->conn) { return; } if ($schema->hasTable('rememberme_token')) { return; } $this->addTableToSchema($schema); } private function addTableToSchema(Schema $schema): void { $table = $schema->createTable('rememberme_token'); $table->addColumn('series', Types::STRING, ['length' => 88]); $table->addColumn('value', Types::STRING, ['length' => 88]); $table->addColumn('lastUsed', Types::DATETIME_MUTABLE); $table->addColumn('class', Types::STRING, ['length' => 100]); $table->addColumn('username', Types::STRING, ['length' => 200]); $table->setPrimaryKey(['series']); } } Security/User/EntityUserProvider.php 0000644 00000015461 15120140637 0013636 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Security\User; use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\Mapping\ClassMetadata; use Doctrine\Persistence\ObjectManager; use Doctrine\Persistence\ObjectRepository; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; use Symfony\Component\Security\Core\Exception\UserNotFoundException; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; /** * Wrapper around a Doctrine ObjectManager. * * Provides provisioning for Doctrine entity users. * * @author Fabien Potencier <fabien@symfony.com> * @author Johannes M. Schmitt <schmittjoh@gmail.com> */ class EntityUserProvider implements UserProviderInterface, PasswordUpgraderInterface { private $registry; private $managerName; private $classOrAlias; private $class; private $property; public function __construct(ManagerRegistry $registry, string $classOrAlias, string $property = null, string $managerName = null) { $this->registry = $registry; $this->managerName = $managerName; $this->classOrAlias = $classOrAlias; $this->property = $property; } /** * {@inheritdoc} */ public function loadUserByUsername(string $username) { trigger_deprecation('symfony/doctrine-bridge', '5.3', 'Method "%s()" is deprecated, use loadUserByIdentifier() instead.', __METHOD__); return $this->loadUserByIdentifier($username); } public function loadUserByIdentifier(string $identifier): UserInterface { $repository = $this->getRepository(); if (null !== $this->property) { $user = $repository->findOneBy([$this->property => $identifier]); } else { if (!$repository instanceof UserLoaderInterface) { throw new \InvalidArgumentException(sprintf('You must either make the "%s" entity Doctrine Repository ("%s") implement "Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface" or set the "property" option in the corresponding entity provider configuration.', $this->classOrAlias, get_debug_type($repository))); } // @deprecated since Symfony 5.3, change to $repository->loadUserByIdentifier() in 6.0 if (method_exists($repository, 'loadUserByIdentifier')) { $user = $repository->loadUserByIdentifier($identifier); } else { trigger_deprecation('symfony/doctrine-bridge', '5.3', 'Not implementing method "loadUserByIdentifier()" in user loader "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', get_debug_type($repository)); $user = $repository->loadUserByUsername($identifier); } } if (null === $user) { $e = new UserNotFoundException(sprintf('User "%s" not found.', $identifier)); $e->setUserIdentifier($identifier); throw $e; } return $user; } /** * {@inheritdoc} */ public function refreshUser(UserInterface $user) { $class = $this->getClass(); if (!$user instanceof $class) { throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_debug_type($user))); } $repository = $this->getRepository(); if ($repository instanceof UserProviderInterface) { $refreshedUser = $repository->refreshUser($user); } else { // The user must be reloaded via the primary key as all other data // might have changed without proper persistence in the database. // That's the case when the user has been changed by a form with // validation errors. if (!$id = $this->getClassMetadata()->getIdentifierValues($user)) { throw new \InvalidArgumentException('You cannot refresh a user from the EntityUserProvider that does not contain an identifier. The user object has to be serialized with its own identifier mapped by Doctrine.'); } $refreshedUser = $repository->find($id); if (null === $refreshedUser) { $e = new UserNotFoundException('User with id '.json_encode($id).' not found.'); $e->setUserIdentifier(json_encode($id)); throw $e; } } return $refreshedUser; } /** * {@inheritdoc} */ public function supportsClass(string $class) { return $class === $this->getClass() || is_subclass_of($class, $this->getClass()); } /** * {@inheritdoc} * * @final */ public function upgradePassword($user, string $newHashedPassword): void { if (!$user instanceof PasswordAuthenticatedUserInterface) { trigger_deprecation('symfony/doctrine-bridge', '5.3', 'The "%s::upgradePassword()" method expects an instance of "%s" as first argument, the "%s" class should implement it.', PasswordUpgraderInterface::class, PasswordAuthenticatedUserInterface::class, get_debug_type($user)); if (!$user instanceof UserInterface) { throw new \TypeError(sprintf('The "%s()" method expects an instance of "%s" as first argument, "%s" given.', __METHOD__, PasswordAuthenticatedUserInterface::class, get_debug_type($user))); } } $class = $this->getClass(); if (!$user instanceof $class) { throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_debug_type($user))); } $repository = $this->getRepository(); if ($repository instanceof PasswordUpgraderInterface) { $repository->upgradePassword($user, $newHashedPassword); } } private function getObjectManager(): ObjectManager { return $this->registry->getManager($this->managerName); } private function getRepository(): ObjectRepository { return $this->getObjectManager()->getRepository($this->classOrAlias); } private function getClass(): string { if (null === $this->class) { $class = $this->classOrAlias; if (str_contains($class, ':')) { $class = $this->getClassMetadata()->getName(); } $this->class = $class; } return $this->class; } private function getClassMetadata(): ClassMetadata { return $this->getObjectManager()->getClassMetadata($this->classOrAlias); } } Security/User/UserLoaderInterface.php 0000644 00000002374 15120140637 0013675 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Security\User; use Symfony\Component\Security\Core\User\UserInterface; /** * Represents a class that loads UserInterface objects from Doctrine source for the authentication system. * * This interface is meant to facilitate the loading of a User from Doctrine source using a custom method. * If you want to implement your own logic of retrieving the user from Doctrine your repository should implement this * interface. * * @see UserInterface * * @method UserInterface|null loadUserByIdentifier(string $identifier) loads the user for the given user identifier (e.g. username or email). * This method must return null if the user is not found. * * @author Michal Trojanowski <michal@kmt-studio.pl> */ interface UserLoaderInterface { /** * @return UserInterface|null * * @deprecated since Symfony 5.3, use loadUserByIdentifier() instead */ public function loadUserByUsername(string $username); } Test/DoctrineTestHelper.php 0000644 00000006201 15120140637 0011741 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Test; use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\ORM\Configuration; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Mapping\Driver\AnnotationDriver; use Doctrine\ORM\Mapping\Driver\XmlDriver; use Doctrine\Persistence\Mapping\Driver\MappingDriverChain; use Doctrine\Persistence\Mapping\Driver\SymfonyFileLocator; use PHPUnit\Framework\TestCase; /** * Provides utility functions needed in tests. * * @author Bernhard Schussek <bschussek@gmail.com> * * @deprecated since Symfony 5.3 */ class DoctrineTestHelper { /** * Returns an entity manager for testing. * * @return EntityManager */ public static function createTestEntityManager(Configuration $config = null) { if (!\extension_loaded('pdo_sqlite')) { TestCase::markTestSkipped('Extension pdo_sqlite is required.'); } if (__CLASS__ === static::class) { trigger_deprecation('symfony/doctrine-bridge', '5.3', '"%s" is deprecated and will be removed in 6.0.', __CLASS__); } if (null === $config) { $config = self::createTestConfiguration(); } $params = [ 'driver' => 'pdo_sqlite', 'memory' => true, ]; return EntityManager::create($params, $config); } /** * @return Configuration */ public static function createTestConfiguration() { if (__CLASS__ === static::class) { trigger_deprecation('symfony/doctrine-bridge', '5.3', '"%s" is deprecated and will be removed in 6.0.', __CLASS__); } $config = new Configuration(); $config->setEntityNamespaces(['SymfonyTestsDoctrine' => 'Symfony\Bridge\Doctrine\Tests\Fixtures']); $config->setAutoGenerateProxyClasses(true); $config->setProxyDir(sys_get_temp_dir()); $config->setProxyNamespace('SymfonyTests\Doctrine'); $config->setMetadataDriverImpl(new AnnotationDriver(new AnnotationReader())); return $config; } /** * @return Configuration */ public static function createTestConfigurationWithXmlLoader() { if (__CLASS__ === static::class) { trigger_deprecation('symfony/doctrine-bridge', '5.3', '"%s" is deprecated and will be removed in 6.0.', __CLASS__); } $config = static::createTestConfiguration(); $driverChain = new MappingDriverChain(); $driverChain->addDriver( new XmlDriver( new SymfonyFileLocator( [__DIR__.'/../Tests/Resources/orm' => 'Symfony\\Bridge\\Doctrine\\Tests\\Fixtures'], '.orm.xml' ) ), 'Symfony\\Bridge\\Doctrine\\Tests\\Fixtures' ); $config->setMetadataDriverImpl($driverChain); return $config; } /** * This class cannot be instantiated. */ private function __construct() { } } Test/TestRepositoryFactory.php 0000644 00000004701 15120140637 0012544 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Test; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Repository\RepositoryFactory; use Doctrine\Persistence\ObjectRepository; /** * @author Andreas Braun <alcaeus@alcaeus.org> * * @deprecated since Symfony 5.3 */ class TestRepositoryFactory implements RepositoryFactory { /** * @var ObjectRepository[] */ private $repositoryList = []; /** * {@inheritdoc} * * @return ObjectRepository */ public function getRepository(EntityManagerInterface $entityManager, $entityName) { if (__CLASS__ === static::class) { trigger_deprecation('symfony/doctrine-bridge', '5.3', '"%s" is deprecated and will be removed in 6.0.', __CLASS__); } $repositoryHash = $this->getRepositoryHash($entityManager, $entityName); if (isset($this->repositoryList[$repositoryHash])) { return $this->repositoryList[$repositoryHash]; } return $this->repositoryList[$repositoryHash] = $this->createRepository($entityManager, $entityName); } public function setRepository(EntityManagerInterface $entityManager, string $entityName, ObjectRepository $repository) { if (__CLASS__ === static::class) { trigger_deprecation('symfony/doctrine-bridge', '5.3', '"%s" is deprecated and will be removed in 6.0.', __CLASS__); } $repositoryHash = $this->getRepositoryHash($entityManager, $entityName); $this->repositoryList[$repositoryHash] = $repository; } private function createRepository(EntityManagerInterface $entityManager, string $entityName): ObjectRepository { /* @var $metadata ClassMetadata */ $metadata = $entityManager->getClassMetadata($entityName); $repositoryClassName = $metadata->customRepositoryClassName ?: $entityManager->getConfiguration()->getDefaultRepositoryClassName(); return new $repositoryClassName($entityManager, $metadata); } private function getRepositoryHash(EntityManagerInterface $entityManager, string $entityName): string { return $entityManager->getClassMetadata($entityName)->getName().spl_object_hash($entityManager); } } Types/AbstractUidType.php 0000644 00000006020 15120140637 0011425 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\ConversionException; use Doctrine\DBAL\Types\Type; use Symfony\Component\Uid\AbstractUid; abstract class AbstractUidType extends Type { /** * @return class-string<AbstractUid> */ abstract protected function getUidClass(): string; /** * {@inheritdoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform): string { if ($this->hasNativeGuidType($platform)) { return $platform->getGuidTypeDeclarationSQL($column); } return $platform->getBinaryTypeDeclarationSQL([ 'length' => '16', 'fixed' => true, ]); } /** * {@inheritdoc} * * @throws ConversionException */ public function convertToPHPValue($value, AbstractPlatform $platform): ?AbstractUid { if ($value instanceof AbstractUid || null === $value) { return $value; } if (!\is_string($value)) { throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'string', AbstractUid::class]); } try { return $this->getUidClass()::fromString($value); } catch (\InvalidArgumentException $e) { throw ConversionException::conversionFailed($value, $this->getName(), $e); } } /** * {@inheritdoc} * * @throws ConversionException */ public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string { $toString = $this->hasNativeGuidType($platform) ? 'toRfc4122' : 'toBinary'; if ($value instanceof AbstractUid) { return $value->$toString(); } if (null === $value || '' === $value) { return null; } if (!\is_string($value)) { throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'string', AbstractUid::class]); } try { return $this->getUidClass()::fromString($value)->$toString(); } catch (\InvalidArgumentException $e) { throw ConversionException::conversionFailed($value, $this->getName()); } } /** * {@inheritdoc} */ public function requiresSQLCommentHint(AbstractPlatform $platform): bool { return true; } private function hasNativeGuidType(AbstractPlatform $platform): bool { // Compatibility with DBAL < 3.4 $method = method_exists($platform, 'getStringTypeDeclarationSQL') ? 'getStringTypeDeclarationSQL' : 'getVarcharTypeDeclarationSQL'; return $platform->getGuidTypeDeclarationSQL([]) !== $platform->$method(['fixed' => true, 'length' => 36]); } } Types/UlidType.php 0000644 00000001011 15120140637 0010110 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Types; use Symfony\Component\Uid\Ulid; final class UlidType extends AbstractUidType { public function getName(): string { return 'ulid'; } protected function getUidClass(): string { return Ulid::class; } } Types/UuidType.php 0000644 00000001011 15120140637 0010121 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Types; use Symfony\Component\Uid\Uuid; final class UuidType extends AbstractUidType { public function getName(): string { return 'uuid'; } protected function getUidClass(): string { return Uuid::class; } } Validator/Constraints/UniqueEntity.php 0000644 00000005315 15120140637 0014157 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Validator\Constraints; use Symfony\Component\Validator\Constraint; /** * Constraint for the Unique Entity validator. * * @Annotation * @Target({"CLASS", "ANNOTATION"}) * * @author Benjamin Eberlei <kontakt@beberlei.de> */ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] class UniqueEntity extends Constraint { public const NOT_UNIQUE_ERROR = '23bd9dbf-6b9b-41cd-a99e-4844bcf3077f'; public $message = 'This value is already used.'; public $service = 'doctrine.orm.validator.unique'; public $em = null; public $entityClass = null; public $repositoryMethod = 'findBy'; public $fields = []; public $errorPath = null; public $ignoreNull = true; protected static $errorNames = [ self::NOT_UNIQUE_ERROR => 'NOT_UNIQUE_ERROR', ]; /** * {@inheritdoc} * * @param array|string $fields the combination of fields that must contain unique values or a set of options */ public function __construct( $fields, string $message = null, string $service = null, string $em = null, string $entityClass = null, string $repositoryMethod = null, string $errorPath = null, bool $ignoreNull = null, array $groups = null, $payload = null, array $options = [] ) { if (\is_array($fields) && \is_string(key($fields))) { $options = array_merge($fields, $options); } elseif (null !== $fields) { $options['fields'] = $fields; } parent::__construct($options, $groups, $payload); $this->message = $message ?? $this->message; $this->service = $service ?? $this->service; $this->em = $em ?? $this->em; $this->entityClass = $entityClass ?? $this->entityClass; $this->repositoryMethod = $repositoryMethod ?? $this->repositoryMethod; $this->errorPath = $errorPath ?? $this->errorPath; $this->ignoreNull = $ignoreNull ?? $this->ignoreNull; } public function getRequiredOptions() { return ['fields']; } /** * The validator must be defined as a service with this name. * * @return string */ public function validatedBy() { return $this->service; } /** * {@inheritdoc} */ public function getTargets() { return self::CLASS_CONSTRAINT; } public function getDefaultOption() { return 'fields'; } } Validator/Constraints/UniqueEntityValidator.php 0000644 00000021343 15120140637 0016024 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Validator\Constraints; use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\Mapping\ClassMetadata; use Doctrine\Persistence\ObjectManager; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Exception\ConstraintDefinitionException; use Symfony\Component\Validator\Exception\UnexpectedTypeException; use Symfony\Component\Validator\Exception\UnexpectedValueException; /** * Unique Entity Validator checks if one or a set of fields contain unique values. * * @author Benjamin Eberlei <kontakt@beberlei.de> */ class UniqueEntityValidator extends ConstraintValidator { private $registry; public function __construct(ManagerRegistry $registry) { $this->registry = $registry; } /** * @param object $entity * * @throws UnexpectedTypeException * @throws ConstraintDefinitionException */ public function validate($entity, Constraint $constraint) { if (!$constraint instanceof UniqueEntity) { throw new UnexpectedTypeException($constraint, UniqueEntity::class); } if (!\is_array($constraint->fields) && !\is_string($constraint->fields)) { throw new UnexpectedTypeException($constraint->fields, 'array'); } if (null !== $constraint->errorPath && !\is_string($constraint->errorPath)) { throw new UnexpectedTypeException($constraint->errorPath, 'string or null'); } $fields = (array) $constraint->fields; if (0 === \count($fields)) { throw new ConstraintDefinitionException('At least one field has to be specified.'); } if (null === $entity) { return; } if (!\is_object($entity)) { throw new UnexpectedValueException($entity, 'object'); } if ($constraint->em) { $em = $this->registry->getManager($constraint->em); if (!$em) { throw new ConstraintDefinitionException(sprintf('Object manager "%s" does not exist.', $constraint->em)); } } else { $em = $this->registry->getManagerForClass(\get_class($entity)); if (!$em) { throw new ConstraintDefinitionException(sprintf('Unable to find the object manager associated with an entity of class "%s".', get_debug_type($entity))); } } $class = $em->getClassMetadata(\get_class($entity)); $criteria = []; $hasNullValue = false; foreach ($fields as $fieldName) { if (!$class->hasField($fieldName) && !$class->hasAssociation($fieldName)) { throw new ConstraintDefinitionException(sprintf('The field "%s" is not mapped by Doctrine, so it cannot be validated for uniqueness.', $fieldName)); } $fieldValue = $class->reflFields[$fieldName]->getValue($entity); if (null === $fieldValue) { $hasNullValue = true; } if ($constraint->ignoreNull && null === $fieldValue) { continue; } $criteria[$fieldName] = $fieldValue; if (null !== $criteria[$fieldName] && $class->hasAssociation($fieldName)) { /* Ensure the Proxy is initialized before using reflection to * read its identifiers. This is necessary because the wrapped * getter methods in the Proxy are being bypassed. */ $em->initializeObject($criteria[$fieldName]); } } // validation doesn't fail if one of the fields is null and if null values should be ignored if ($hasNullValue && $constraint->ignoreNull) { return; } // skip validation if there are no criteria (this can happen when the // "ignoreNull" option is enabled and fields to be checked are null if (empty($criteria)) { return; } if (null !== $constraint->entityClass) { /* Retrieve repository from given entity name. * We ensure the retrieved repository can handle the entity * by checking the entity is the same, or subclass of the supported entity. */ $repository = $em->getRepository($constraint->entityClass); $supportedClass = $repository->getClassName(); if (!$entity instanceof $supportedClass) { throw new ConstraintDefinitionException(sprintf('The "%s" entity repository does not support the "%s" entity. The entity should be an instance of or extend "%s".', $constraint->entityClass, $class->getName(), $supportedClass)); } } else { $repository = $em->getRepository(\get_class($entity)); } $arguments = [$criteria]; /* If the default repository method is used, it is always enough to retrieve at most two entities because: * - No entity returned, the current entity is definitely unique. * - More than one entity returned, the current entity cannot be unique. * - One entity returned the uniqueness depends on the current entity. */ if ('findBy' === $constraint->repositoryMethod) { $arguments = [$criteria, null, 2]; } $result = $repository->{$constraint->repositoryMethod}(...$arguments); if ($result instanceof \IteratorAggregate) { $result = $result->getIterator(); } /* If the result is a MongoCursor, it must be advanced to the first * element. Rewinding should have no ill effect if $result is another * iterator implementation. */ if ($result instanceof \Iterator) { $result->rewind(); if ($result instanceof \Countable && 1 < \count($result)) { $result = [$result->current(), $result->current()]; } else { $result = $result->valid() && null !== $result->current() ? [$result->current()] : []; } } elseif (\is_array($result)) { reset($result); } else { $result = null === $result ? [] : [$result]; } /* If no entity matched the query criteria or a single entity matched, * which is the same as the entity being validated, the criteria is * unique. */ if (!$result || (1 === \count($result) && current($result) === $entity)) { return; } $errorPath = $constraint->errorPath ?? $fields[0]; $invalidValue = $criteria[$errorPath] ?? $criteria[$fields[0]]; $this->context->buildViolation($constraint->message) ->atPath($errorPath) ->setParameter('{{ value }}', $this->formatWithIdentifiers($em, $class, $invalidValue)) ->setInvalidValue($invalidValue) ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) ->setCause($result) ->addViolation(); } private function formatWithIdentifiers(ObjectManager $em, ClassMetadata $class, $value) { if (!\is_object($value) || $value instanceof \DateTimeInterface) { return $this->formatValue($value, self::PRETTY_DATE); } if (method_exists($value, '__toString')) { return (string) $value; } if ($class->getName() !== $idClass = \get_class($value)) { // non unique value might be a composite PK that consists of other entity objects if ($em->getMetadataFactory()->hasMetadataFor($idClass)) { $identifiers = $em->getClassMetadata($idClass)->getIdentifierValues($value); } else { // this case might happen if the non unique column has a custom doctrine type and its value is an object // in which case we cannot get any identifiers for it $identifiers = []; } } else { $identifiers = $class->getIdentifierValues($value); } if (!$identifiers) { return sprintf('object("%s")', $idClass); } array_walk($identifiers, function (&$id, $field) { if (!\is_object($id) || $id instanceof \DateTimeInterface) { $idAsString = $this->formatValue($id, self::PRETTY_DATE); } else { $idAsString = sprintf('object("%s")', \get_class($id)); } $id = sprintf('%s => %s', $field, $idAsString); }); return sprintf('object("%s") identified by (%s)', $idClass, implode(', ', $identifiers)); } } Validator/DoctrineInitializer.php 0000644 00000001645 15120140637 0013162 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Validator; use Doctrine\Persistence\ManagerRegistry; use Symfony\Component\Validator\ObjectInitializerInterface; /** * Automatically loads proxy object before validation. * * @author Fabien Potencier <fabien@symfony.com> */ class DoctrineInitializer implements ObjectInitializerInterface { protected $registry; public function __construct(ManagerRegistry $registry) { $this->registry = $registry; } public function initialize(object $object) { $manager = $this->registry->getManagerForClass(\get_class($object)); if (null !== $manager) { $manager->initializeObject($object); } } } Validator/DoctrineLoader.php 0000644 00000012120 15120140637 0012073 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine\Validator; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Mapping\MappingException as OrmMappingException; use Doctrine\Persistence\Mapping\MappingException; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\Valid; use Symfony\Component\Validator\Mapping\AutoMappingStrategy; use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Mapping\Loader\AutoMappingTrait; use Symfony\Component\Validator\Mapping\Loader\LoaderInterface; /** * Guesses and loads the appropriate constraints using Doctrine's metadata. * * @author Kévin Dunglas <dunglas@gmail.com> */ final class DoctrineLoader implements LoaderInterface { use AutoMappingTrait; private $entityManager; private $classValidatorRegexp; public function __construct(EntityManagerInterface $entityManager, string $classValidatorRegexp = null) { $this->entityManager = $entityManager; $this->classValidatorRegexp = $classValidatorRegexp; } /** * {@inheritdoc} */ public function loadClassMetadata(ClassMetadata $metadata): bool { $className = $metadata->getClassName(); try { $doctrineMetadata = $this->entityManager->getClassMetadata($className); } catch (MappingException|OrmMappingException $exception) { return false; } if (!$doctrineMetadata instanceof ClassMetadataInfo) { return false; } $loaded = false; $enabledForClass = $this->isAutoMappingEnabledForClass($metadata, $this->classValidatorRegexp); /* Available keys: - type - scale - length - unique - nullable - precision */ $existingUniqueFields = $this->getExistingUniqueFields($metadata); // Type and nullable aren't handled here, use the PropertyInfo Loader instead. foreach ($doctrineMetadata->fieldMappings as $mapping) { $enabledForProperty = $enabledForClass; $lengthConstraint = null; foreach ($metadata->getPropertyMetadata($mapping['fieldName']) as $propertyMetadata) { // Enabling or disabling auto-mapping explicitly always takes precedence if (AutoMappingStrategy::DISABLED === $propertyMetadata->getAutoMappingStrategy()) { continue 2; } if (AutoMappingStrategy::ENABLED === $propertyMetadata->getAutoMappingStrategy()) { $enabledForProperty = true; } foreach ($propertyMetadata->getConstraints() as $constraint) { if ($constraint instanceof Length) { $lengthConstraint = $constraint; } } } if (!$enabledForProperty) { continue; } if (true === ($mapping['unique'] ?? false) && !isset($existingUniqueFields[$mapping['fieldName']])) { $metadata->addConstraint(new UniqueEntity(['fields' => $mapping['fieldName']])); $loaded = true; } if (null === ($mapping['length'] ?? null) || null !== ($mapping['enumType'] ?? null) || !\in_array($mapping['type'], ['string', 'text'], true)) { continue; } if (null === $lengthConstraint) { if (isset($mapping['originalClass']) && !str_contains($mapping['declaredField'], '.')) { $metadata->addPropertyConstraint($mapping['declaredField'], new Valid()); $loaded = true; } elseif (property_exists($className, $mapping['fieldName'])) { $metadata->addPropertyConstraint($mapping['fieldName'], new Length(['max' => $mapping['length']])); $loaded = true; } } elseif (null === $lengthConstraint->max) { // If a Length constraint exists and no max length has been explicitly defined, set it $lengthConstraint->max = $mapping['length']; } } return $loaded; } private function getExistingUniqueFields(ClassMetadata $metadata): array { $fields = []; foreach ($metadata->getConstraints() as $constraint) { if (!$constraint instanceof UniqueEntity) { continue; } if (\is_string($constraint->fields)) { $fields[$constraint->fields] = true; } elseif (\is_array($constraint->fields) && 1 === \count($constraint->fields)) { $fields[$constraint->fields[0]] = true; } } return $fields; } } CHANGELOG.md 0000644 00000012620 15120140637 0006355 0 ustar 00 CHANGELOG ========= 5.4 --- * Add `DoctrineOpenTransactionLoggerMiddleware` to log when a transaction has been left open * Deprecate `PdoCacheAdapterDoctrineSchemaSubscriber` and add `DoctrineDbalCacheAdapterSchemaSubscriber` instead * `UniqueEntity` constraint retrieves a maximum of two entities if the default repository method is used. * Add support for the newer bundle structure to `AbstractDoctrineExtension::loadMappingInformation()` * Add argument `$bundleDir` to `AbstractDoctrineExtension::getMappingDriverBundleConfigDefaults()` * Add argument `$bundleDir` to `AbstractDoctrineExtension::getMappingResourceConfigDirectory()` 5.3 --- * Deprecate `UserLoaderInterface::loadUserByUsername()` in favor of `UserLoaderInterface::loadUserByIdentifier() * Deprecate `DoctrineTestHelper` and `TestRepositoryFactory` * [BC BREAK] Remove `UuidV*Generator` classes * Add `UuidGenerator` * Add support for the new security-core `TokenVerifierInterface` in `DoctrineTokenProvider`, fixing parallel requests handling in remember-me 5.2.0 ----- * added support for symfony/uid as `UlidType` and `UuidType` as Doctrine types * added `UlidGenerator`, `UuidV1Generator`, `UuidV4Generator` and `UuidV6Generator` 5.0.0 ----- * the `getMetadataDriverClass()` method is abstract and must be implemented by class extending `AbstractDoctrineExtension` * passing an `IdReader` to the `DoctrineChoiceLoader` when the query cannot be optimized with single id field, throws an exception; pass `null` instead * not explicitly passing an instance of `IdReader` to `DoctrineChoiceLoader` when it can optimize single id field, will not apply any optimization * `DoctrineExtractor` now requires an `EntityManagerInterface` on instantiation 4.4.0 ----- * [BC BREAK] using null as `$classValidatorRegexp` value in `DoctrineLoader::__construct` will not enable auto-mapping for all classes anymore, use `'{.*}'` instead. * added `DoctrineClearEntityManagerWorkerSubscriber` * deprecated `RegistryInterface`, use `Doctrine\Persistence\ManagerRegistry` * added support for invokable event listeners * added `getMetadataDriverClass` method to deprecate class parameters in service configuration files 4.3.0 ----- * changed guessing of DECIMAL to set the `input` option of `NumberType` to string * deprecated not passing an `IdReader` to the `DoctrineChoiceLoader` when query can be optimized with a single id field * deprecated passing an `IdReader` to the `DoctrineChoiceLoader` when entities have a composite id * added two Messenger middleware: `DoctrinePingConnectionMiddleware` and `DoctrineCloseConnectionMiddleware` 4.2.0 ----- * deprecated injecting `ClassMetadataFactory` in `DoctrineExtractor`, an instance of `EntityManagerInterface` should be injected instead * added support for `simple_array` type * the `DoctrineTransactionMiddlewareFactory` class has been removed 4.1.0 ----- * added support for datetime immutable types in form type guesser 4.0.0 ----- * the first constructor argument of the `DoctrineChoiceLoader` class must be an `ObjectManager` implementation * removed the `MergeDoctrineCollectionListener::onBind()` method * trying to reset a non-lazy manager service using the `ManagerRegistry::resetService()` method throws an exception * removed the `DoctrineParserCache` class 3.4.0 ----- * added support for doctrine/dbal v2.6 types * added cause of UniqueEntity constraint violation * deprecated `DbalSessionHandler` and `DbalSessionHandlerSchema` in favor of `Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler` 3.1.0 ----- * added "{{ value }}" message placeholder to UniqueEntityValidator * deprecated `MergeDoctrineCollectionListener::onBind` in favor of `MergeDoctrineCollectionListener::onSubmit` * deprecated passing `ChoiceListFactoryInterface` as first argument of `DoctrineChoiceLoader`'s constructor 3.0.0 ----- * removed `EntityChoiceList` * removed `$manager` (2nd) and `$class` (3th) arguments of `ORMQueryBuilderLoader` * removed passing a query builder closure to `ORMQueryBuilderLoader` * removed `loader` and `property` options of the `DoctrineType` 2.8.0 ----- * deprecated using the entity provider with a Doctrine repository implementing UserProviderInterface * added UserLoaderInterface for loading users through Doctrine. 2.7.0 ----- * added DoctrineChoiceLoader * deprecated EntityChoiceList * deprecated passing a query builder closure to ORMQueryBuilderLoader * deprecated $manager and $em arguments of ORMQueryBuilderLoader * added optional arguments $propertyAccessor and $choiceListFactory to DoctrineOrmExtension constructor * deprecated "loader" and "property" options of DoctrineType 2.4.0 ----- * deprecated DoctrineOrmTestCase class 2.2.0 ----- * added an optional PropertyAccessorInterface parameter to DoctrineType, EntityType and EntityChoiceList 2.1.0 ----- * added a default implementation of the ManagerRegistry * added a session storage for Doctrine DBAL * DoctrineOrmTypeGuesser now guesses "collection" for array Doctrine type * DoctrineType now caches its choice lists in order to improve performance * DoctrineType now uses ManagerRegistry::getManagerForClass() if the option "em" is not set * UniqueEntity validation constraint now accepts a "repositoryMethod" option that will be used to check for uniqueness instead of the default "findBy" * [BC BREAK] the DbalLogger::log() visibility has been changed from public to protected ContainerAwareEventManager.php 0000644 00000014372 15120140637 0012462 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine; use Doctrine\Common\EventArgs; use Doctrine\Common\EventManager; use Doctrine\Common\EventSubscriber; use Psr\Container\ContainerInterface; /** * Allows lazy loading of listener and subscriber services. * * @author Johannes M. Schmitt <schmittjoh@gmail.com> */ class ContainerAwareEventManager extends EventManager { /** * Map of registered listeners. * * <event> => <listeners> */ private $listeners = []; private $subscribers; private $initialized = []; private $initializedSubscribers = false; private $methods = []; private $container; /** * @param list<string|EventSubscriber|array{string[], string|object}> $subscriberIds List of subscribers, subscriber ids, or [events, listener] tuples */ public function __construct(ContainerInterface $container, array $subscriberIds = []) { $this->container = $container; $this->subscribers = $subscriberIds; } /** * {@inheritdoc} * * @return void */ public function dispatchEvent($eventName, EventArgs $eventArgs = null) { if (!$this->initializedSubscribers) { $this->initializeSubscribers(); } if (!isset($this->listeners[$eventName])) { return; } $eventArgs = $eventArgs ?? EventArgs::getEmptyInstance(); if (!isset($this->initialized[$eventName])) { $this->initializeListeners($eventName); } foreach ($this->listeners[$eventName] as $hash => $listener) { $listener->{$this->methods[$eventName][$hash]}($eventArgs); } } /** * {@inheritdoc} * * @return object[][] */ public function getListeners($event = null) { if (null === $event) { return $this->getAllListeners(); } if (!$this->initializedSubscribers) { $this->initializeSubscribers(); } if (!isset($this->initialized[$event])) { $this->initializeListeners($event); } return $this->listeners[$event]; } public function getAllListeners(): array { if (!$this->initializedSubscribers) { $this->initializeSubscribers(); } foreach ($this->listeners as $event => $listeners) { if (!isset($this->initialized[$event])) { $this->initializeListeners($event); } } return $this->listeners; } /** * {@inheritdoc} * * @return bool */ public function hasListeners($event) { if (!$this->initializedSubscribers) { $this->initializeSubscribers(); } return isset($this->listeners[$event]) && $this->listeners[$event]; } /** * {@inheritdoc} * * @return void */ public function addEventListener($events, $listener) { if (!$this->initializedSubscribers) { $this->initializeSubscribers(); } $hash = $this->getHash($listener); foreach ((array) $events as $event) { // Overrides listener if a previous one was associated already // Prevents duplicate listeners on same event (same instance only) $this->listeners[$event][$hash] = $listener; if (\is_string($listener)) { unset($this->initialized[$event]); } else { $this->methods[$event][$hash] = $this->getMethod($listener, $event); } } } /** * {@inheritdoc} * * @return void */ public function removeEventListener($events, $listener) { if (!$this->initializedSubscribers) { $this->initializeSubscribers(); } $hash = $this->getHash($listener); foreach ((array) $events as $event) { // Check if we actually have this listener associated if (isset($this->listeners[$event][$hash])) { unset($this->listeners[$event][$hash]); } if (isset($this->methods[$event][$hash])) { unset($this->methods[$event][$hash]); } } } public function addEventSubscriber(EventSubscriber $subscriber): void { if (!$this->initializedSubscribers) { $this->initializeSubscribers(); } parent::addEventSubscriber($subscriber); } public function removeEventSubscriber(EventSubscriber $subscriber): void { if (!$this->initializedSubscribers) { $this->initializeSubscribers(); } parent::removeEventSubscriber($subscriber); } private function initializeListeners(string $eventName) { $this->initialized[$eventName] = true; foreach ($this->listeners[$eventName] as $hash => $listener) { if (\is_string($listener)) { $this->listeners[$eventName][$hash] = $listener = $this->container->get($listener); $this->methods[$eventName][$hash] = $this->getMethod($listener, $eventName); } } } private function initializeSubscribers() { $this->initializedSubscribers = true; foreach ($this->subscribers as $subscriber) { if (\is_array($subscriber)) { $this->addEventListener(...$subscriber); continue; } if (\is_string($subscriber)) { $subscriber = $this->container->get($subscriber); } parent::addEventSubscriber($subscriber); } $this->subscribers = []; } /** * @param string|object $listener */ private function getHash($listener): string { if (\is_string($listener)) { return '_service_'.$listener; } return spl_object_hash($listener); } private function getMethod(object $listener, string $event): string { if (!method_exists($listener, $event) && method_exists($listener, '__invoke')) { return '__invoke'; } return $event; } } LICENSE 0000644 00000002054 15120140637 0005551 0 ustar 00 Copyright (c) 2004-present Fabien Potencier 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. ManagerRegistry.php 0000644 00000004631 15120140637 0010363 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bridge\Doctrine; use Doctrine\Persistence\AbstractManagerRegistry; use ProxyManager\Proxy\GhostObjectInterface; use ProxyManager\Proxy\LazyLoadingInterface; use Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator; use Symfony\Component\DependencyInjection\Container; /** * References Doctrine connections and entity/document managers. * * @author Lukas Kahwe Smith <smith@pooteeweet.org> */ abstract class ManagerRegistry extends AbstractManagerRegistry { /** * @var Container */ protected $container; /** * {@inheritdoc} * * @return object */ protected function getService($name) { return $this->container->get($name); } /** * {@inheritdoc} * * @return void */ protected function resetService($name) { if (!$this->container->initialized($name)) { return; } $manager = $this->container->get($name); if (!$manager instanceof LazyLoadingInterface) { throw new \LogicException('Resetting a non-lazy manager service is not supported. '.(interface_exists(LazyLoadingInterface::class) && class_exists(RuntimeInstantiator::class) ? sprintf('Declare the "%s" service as lazy.', $name) : 'Try running "composer require symfony/proxy-manager-bridge".')); } if ($manager instanceof GhostObjectInterface) { throw new \LogicException('Resetting a lazy-ghost-object manager service is not supported.'); } $manager->setProxyInitializer(\Closure::bind( function (&$wrappedInstance, LazyLoadingInterface $manager) use ($name) { if (isset($this->aliases[$name])) { $name = $this->aliases[$name]; } if (isset($this->fileMap[$name])) { $wrappedInstance = $this->load($this->fileMap[$name], false); } else { $wrappedInstance = $this->{$this->methodMap[$name]}(false); } $manager->setProxyInitializer(null); return true; }, $this->container, Container::class )); } } README.md 0000644 00000000711 15120140637 0006021 0 ustar 00 Doctrine Bridge =============== The Doctrine bridge provides integration for [Doctrine](http://www.doctrine-project.org/) with various Symfony components. Resources --------- * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) composer.json 0000644 00000005453 15120140637 0007274 0 ustar 00 { "name": "symfony/doctrine-bridge", "type": "symfony-bridge", "description": "Provides integration for Doctrine with various Symfony components", "keywords": [], "homepage": "https://symfony.com", "license": "MIT", "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "require": { "php": ">=7.2.5", "doctrine/event-manager": "~1.0", "doctrine/persistence": "^2|^3", "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php80": "^1.16", "symfony/service-contracts": "^1.1|^2|^3" }, "require-dev": { "symfony/stopwatch": "^4.4|^5.0|^6.0", "symfony/cache": "^5.4|^6.0", "symfony/config": "^4.4|^5.0|^6.0", "symfony/dependency-injection": "^4.4|^5.0|^6.0", "symfony/form": "^5.4.21|^6.2.7", "symfony/http-kernel": "^5.0|^6.0", "symfony/messenger": "^4.4|^5.0|^6.0", "symfony/doctrine-messenger": "^5.1|^6.0", "symfony/property-access": "^4.4|^5.0|^6.0", "symfony/property-info": "^5.0|^6.0", "symfony/proxy-manager-bridge": "^4.4|^5.0|^6.0", "symfony/security-core": "^5.3|^6.0", "symfony/expression-language": "^4.4|^5.0|^6.0", "symfony/uid": "^5.1|^6.0", "symfony/validator": "^5.2|^6.0", "symfony/translation": "^4.4|^5.0|^6.0", "symfony/var-dumper": "^4.4|^5.0|^6.0", "doctrine/annotations": "^1.10.4|^2", "doctrine/collections": "^1.0|^2.0", "doctrine/data-fixtures": "^1.1", "doctrine/dbal": "^2.13.1|^3.0", "doctrine/orm": "^2.7.4", "psr/log": "^1|^2|^3" }, "conflict": { "doctrine/dbal": "<2.13.1", "doctrine/lexer": "<1.1", "doctrine/orm": "<2.7.4", "symfony/cache": "<5.4", "symfony/dependency-injection": "<4.4", "symfony/form": "<5.4.21|>=6,<6.2.7", "symfony/http-kernel": "<5", "symfony/messenger": "<4.4", "symfony/property-info": "<5", "symfony/proxy-manager-bridge": "<4.4.19", "symfony/security-bundle": "<5", "symfony/security-core": "<5.3", "symfony/validator": "<5.2" }, "suggest": { "symfony/form": "", "symfony/validator": "", "symfony/property-info": "", "doctrine/data-fixtures": "", "doctrine/dbal": "", "doctrine/orm": "" }, "autoload": { "psr-4": { "Symfony\\Bridge\\Doctrine\\": "" }, "exclude-from-classmap": [ "/Tests/" ] }, "minimum-stability": "dev" }
Coded With 💗 by
0x6ick