Subida del módulo y tema de PrestaShop

This commit is contained in:
Kaloyan
2026-04-09 18:31:51 +02:00
parent 12c253296f
commit 16b3ff9424
39262 changed files with 7418797 additions and 0 deletions

View File

@@ -0,0 +1,132 @@
{
"name": "Doctrine Bundle",
"shortName": "DoctrineBundle",
"slug": "doctrine-bundle",
"versions": [
{
"name": "2.7",
"branchName": "2.7.x",
"slug": "latest",
"upcoming": true
},
{
"name": "2.6",
"branchName": "2.6.x",
"slug": "2.6",
"aliases": [
"current",
"stable"
],
"current": true,
"maintained": true
},
{
"name": "2.5",
"branchName": "2.5.x",
"slug": "2.5",
"maintained": false
},
{
"name": "2.4",
"branchName": "2.4.x",
"slug": "2.4",
"maintained": false
},
{
"name": "2.3",
"branchName": "2.3.x",
"slug": "2.3",
"maintained": false
},
{
"name": "2.2",
"branchName": "2.2.x",
"slug": "2.2",
"maintained": false
},
{
"name": "2.1",
"branchName": "2.1.x",
"slug": "2.1",
"maintained": false
},
{
"name": "2.0",
"branchName": "2.0.x",
"slug": "2.0",
"maintained": false
},
{
"name": "1.12",
"branchName": "1.12.x",
"slug": "1.12",
"maintained": false
},
{
"name": "1.11",
"branchName": "1.11.x",
"slug": "1.11",
"maintained": false
},
{
"name": "1.10",
"branchName": "1.10.x",
"slug": "1.10",
"maintained": false
},
{
"name": "1.9",
"branchName": "1.9.x",
"slug": "1.9",
"maintained": false
},
{
"name": "1.8",
"branchName": "1.8.x",
"slug": "1.8",
"maintained": false
},
{
"name": "1.7",
"branchName": "1.7.x",
"slug": "1.7",
"maintained": false
},
{
"name": "1.6",
"branchName": "1.6.x",
"slug": "1.6",
"maintained": false
},
{
"name": "1.5",
"branchName": "1.5.x",
"slug": "1.5",
"maintained": false
},
{
"name": "1.4",
"branchName": "1.4.x",
"slug": "1.4",
"maintained": false
},
{
"name": "1.3",
"branchName": "1.3.x",
"slug": "1.3",
"maintained": false
},
{
"name": "1.2",
"branchName": "1.2.x",
"slug": "1.2",
"maintained": false
},
{
"name": "1.1",
"branchName": "1.1.x",
"slug": "1.1",
"maintained": false
}
]
}

View File

@@ -0,0 +1,9 @@
branches:
- "2.5.x"
- "2.6.x"
- "2.7.x"
maintained_branches:
- "2.6.x"
- "2.7.x"
doc_dir: "Resources/doc/"
dev_branch: "2.7.x"

View File

@@ -0,0 +1,21 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Attribute;
use Attribute;
/**
* Service tag to autoconfigure entity listeners.
*/
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
class AsEntityListener
{
public function __construct(
public ?string $event = null,
public ?string $method = null,
public ?bool $lazy = null,
public ?string $entityManager = null,
public ?string $entity = null,
) {
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Attribute;
use Attribute;
#[Attribute(Attribute::TARGET_CLASS)]
class AsMiddleware
{
/** @param string[] $connections */
public function __construct(
public array $connections = [],
) {
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\CacheWarmer;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\Mapping\AbstractClassMetadataFactory;
use LogicException;
use Symfony\Bundle\FrameworkBundle\CacheWarmer\AbstractPhpFileCacheWarmer;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use function is_file;
class DoctrineMetadataCacheWarmer extends AbstractPhpFileCacheWarmer
{
/** @var EntityManagerInterface */
private $entityManager;
/** @var string */
private $phpArrayFile;
public function __construct(EntityManagerInterface $entityManager, string $phpArrayFile)
{
$this->entityManager = $entityManager;
$this->phpArrayFile = $phpArrayFile;
parent::__construct($phpArrayFile);
}
/**
* It must not be optional because it should be called before ProxyCacheWarmer which is not optional.
*/
public function isOptional(): bool
{
return false;
}
/** @param string $cacheDir */
protected function doWarmUp($cacheDir, ArrayAdapter $arrayAdapter): bool
{
// cache already warmed up, no needs to do it again
if (is_file($this->phpArrayFile)) {
return false;
}
$metadataFactory = $this->entityManager->getMetadataFactory();
if ($metadataFactory instanceof AbstractClassMetadataFactory && $metadataFactory->getLoadedMetadata()) {
throw new LogicException('DoctrineMetadataCacheWarmer must load metadata first, check priority of your warmers.');
}
$metadataFactory->setCache($arrayAdapter);
$metadataFactory->getAllMetadata();
return true;
}
}

View File

@@ -0,0 +1,121 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Command;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Sharding\PoolingShardConnection;
use InvalidArgumentException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Throwable;
use function array_merge;
use function in_array;
use function sprintf;
/**
* Database tool allows you to easily create your configured databases.
*
* @final
*/
class CreateDatabaseDoctrineCommand extends DoctrineCommand
{
protected function configure(): void
{
$this
->setName('doctrine:database:create')
->setDescription('Creates the configured database')
->addOption('shard', 's', InputOption::VALUE_REQUIRED, 'The shard connection to use for this command')
->addOption('connection', 'c', InputOption::VALUE_OPTIONAL, 'The connection to use for this command')
->addOption('if-not-exists', null, InputOption::VALUE_NONE, 'Don\'t trigger an error, when the database already exists')
->setHelp(<<<EOT
The <info>%command.name%</info> command creates the default connections database:
<info>php %command.full_name%</info>
You can also optionally specify the name of a connection to create the database for:
<info>php %command.full_name% --connection=default</info>
EOT
);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$connectionName = $input->getOption('connection');
if (empty($connectionName)) {
$connectionName = $this->getDoctrine()->getDefaultConnectionName();
}
$connection = $this->getDoctrineConnection($connectionName);
$ifNotExists = $input->getOption('if-not-exists');
$params = $connection->getParams();
if (isset($params['primary'])) {
$params = $params['primary'];
}
// Cannot inject `shard` option in parent::getDoctrineConnection
// cause it will try to connect to a non-existing database
if (isset($params['shards'])) {
$shards = $params['shards'];
// Default select global
$params = array_merge($params, $params['global'] ?? []);
unset($params['global']['dbname'], $params['global']['path'], $params['global']['url']);
if ($input->getOption('shard')) {
foreach ($shards as $i => $shard) {
if ($shard['id'] === (int) $input->getOption('shard')) {
// Select sharded database
$params = array_merge($params, $shard);
unset($params['shards'][$i]['dbname'], $params['shards'][$i]['path'], $params['shards'][$i]['url'], $params['id']);
break;
}
}
}
}
$hasPath = isset($params['path']);
$name = $hasPath ? $params['path'] : ($params['dbname'] ?? false);
if (! $name) {
throw new InvalidArgumentException("Connection does not contain a 'path' or 'dbname' parameter and cannot be created.");
}
// Need to get rid of _every_ occurrence of dbname from connection configuration and we have already extracted all relevant info from url
unset($params['dbname'], $params['path'], $params['url']);
$tmpConnection = DriverManager::getConnection($params);
if ($tmpConnection instanceof PoolingShardConnection) {
$tmpConnection->connect($input->getOption('shard'));
} else {
$tmpConnection->connect();
}
$shouldNotCreateDatabase = $ifNotExists && in_array($name, $tmpConnection->getSchemaManager()->listDatabases());
// Only quote if we don't have a path
if (! $hasPath) {
$name = $tmpConnection->getDatabasePlatform()->quoteSingleIdentifier($name);
}
$error = false;
try {
if ($shouldNotCreateDatabase) {
$output->writeln(sprintf('<info>Database <comment>%s</comment> for connection named <comment>%s</comment> already exists. Skipped.</info>', $name, $connectionName));
} else {
$tmpConnection->getSchemaManager()->createDatabase($name);
$output->writeln(sprintf('<info>Created database <comment>%s</comment> for connection named <comment>%s</comment></info>', $name, $connectionName));
}
} catch (Throwable $e) {
$output->writeln(sprintf('<error>Could not create database <comment>%s</comment> for connection named <comment>%s</comment></error>', $name, $connectionName));
$output->writeln(sprintf('<error>%s</error>', $e->getMessage()));
$error = true;
}
$tmpConnection->close();
return $error ? 1 : 0;
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Command;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Sharding\PoolingShardConnection;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Tools\EntityGenerator;
use Doctrine\Persistence\ManagerRegistry;
use LogicException;
use Symfony\Component\Console\Command\Command;
use function sprintf;
/**
* Base class for Doctrine console commands to extend from.
*
* @internal
*/
abstract class DoctrineCommand extends Command
{
/** @var ManagerRegistry */
private $doctrine;
public function __construct(ManagerRegistry $doctrine)
{
parent::__construct();
$this->doctrine = $doctrine;
}
/**
* get a doctrine entity generator
*
* @return EntityGenerator
*/
protected function getEntityGenerator()
{
$entityGenerator = new EntityGenerator();
$entityGenerator->setGenerateAnnotations(false);
$entityGenerator->setGenerateStubMethods(true);
$entityGenerator->setRegenerateEntityIfExists(false);
$entityGenerator->setUpdateEntityIfExists(true);
$entityGenerator->setNumSpaces(4);
$entityGenerator->setAnnotationPrefix('ORM\\');
return $entityGenerator;
}
/**
* Get a doctrine entity manager by symfony name.
*
* @param string $name
* @param int|null $shardId
*
* @return EntityManager
*/
protected function getEntityManager($name, $shardId = null)
{
$manager = $this->getDoctrine()->getManager($name);
if ($shardId) {
if (! $manager instanceof EntityManagerInterface) {
throw new LogicException(sprintf('Sharding is supported only in EntityManager of instance "%s".', EntityManagerInterface::class));
}
$connection = $manager->getConnection();
if (! $connection instanceof PoolingShardConnection) {
throw new LogicException(sprintf("Connection of EntityManager '%s' must implement shards configuration.", $name));
}
$connection->connect($shardId);
}
return $manager;
}
/**
* Get a doctrine dbal connection by symfony name.
*
* @param string $name
*
* @return Connection
*/
protected function getDoctrineConnection($name)
{
return $this->getDoctrine()->getConnection($name);
}
/** @return ManagerRegistry */
protected function getDoctrine()
{
return $this->doctrine;
}
}

View File

@@ -0,0 +1,132 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Command;
use Doctrine\DBAL\DriverManager;
use InvalidArgumentException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Throwable;
use function array_merge;
use function in_array;
use function sprintf;
/**
* Database tool allows you to easily drop your configured databases.
*
* @final
*/
class DropDatabaseDoctrineCommand extends DoctrineCommand
{
public const RETURN_CODE_NOT_DROP = 1;
public const RETURN_CODE_NO_FORCE = 2;
/**
* {@inheritDoc}
*/
protected function configure()
{
$this
->setName('doctrine:database:drop')
->setDescription('Drops the configured database')
->addOption('shard', 's', InputOption::VALUE_REQUIRED, 'The shard connection to use for this command')
->addOption('connection', 'c', InputOption::VALUE_OPTIONAL, 'The connection to use for this command')
->addOption('if-exists', null, InputOption::VALUE_NONE, 'Don\'t trigger an error, when the database doesn\'t exist')
->addOption('force', 'f', InputOption::VALUE_NONE, 'Set this parameter to execute this action')
->setHelp(<<<EOT
The <info>%command.name%</info> command drops the default connections database:
<info>php %command.full_name%</info>
The <info>--force</info> parameter has to be used to actually drop the database.
You can also optionally specify the name of a connection to drop the database for:
<info>php %command.full_name% --connection=default</info>
<error>Be careful: All data in a given database will be lost when executing this command.</error>
EOT
);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$connectionName = $input->getOption('connection');
if (empty($connectionName)) {
$connectionName = $this->getDoctrine()->getDefaultConnectionName();
}
$connection = $this->getDoctrineConnection($connectionName);
$ifExists = $input->getOption('if-exists');
$params = $connection->getParams();
if (isset($params['primary'])) {
$params = $params['primary'];
}
if (isset($params['shards'])) {
$shards = $params['shards'];
// Default select global
$params = array_merge($params, $params['global'] ?? []);
if ($input->getOption('shard')) {
foreach ($shards as $shard) {
if ($shard['id'] === (int) $input->getOption('shard')) {
// Select sharded database
$params = array_merge($params, $shard);
unset($params['id']);
break;
}
}
}
}
$name = $params['path'] ?? ($params['dbname'] ?? false);
if (! $name) {
throw new InvalidArgumentException("Connection does not contain a 'path' or 'dbname' parameter and cannot be dropped.");
}
unset($params['dbname'], $params['url']);
if (! $input->getOption('force')) {
$output->writeln('<error>ATTENTION:</error> This operation should not be executed in a production environment.');
$output->writeln('');
$output->writeln(sprintf('<info>Would drop the database <comment>%s</comment> for connection named <comment>%s</comment>.</info>', $name, $connectionName));
$output->writeln('Please run the operation with --force to execute');
$output->writeln('<error>All data will be lost!</error>');
return self::RETURN_CODE_NO_FORCE;
}
// Reopen connection without database name set
// as some vendors do not allow dropping the database connected to.
$connection->close();
$connection = DriverManager::getConnection($params);
$shouldDropDatabase = ! $ifExists || in_array($name, $connection->getSchemaManager()->listDatabases());
// Only quote if we don't have a path
if (! isset($params['path'])) {
$name = $connection->getDatabasePlatform()->quoteSingleIdentifier($name);
}
try {
if ($shouldDropDatabase) {
$connection->getSchemaManager()->dropDatabase($name);
$output->writeln(sprintf('<info>Dropped database <comment>%s</comment> for connection named <comment>%s</comment></info>', $name, $connectionName));
} else {
$output->writeln(sprintf('<info>Database <comment>%s</comment> for connection named <comment>%s</comment> doesn\'t exist. Skipped.</info>', $name, $connectionName));
}
return 0;
} catch (Throwable $e) {
$output->writeln(sprintf('<error>Could not drop database <comment>%s</comment> for connection named <comment>%s</comment></error>', $name, $connectionName));
$output->writeln(sprintf('<error>%s</error>', $e->getMessage()));
return self::RETURN_CODE_NOT_DROP;
}
}
}

View File

@@ -0,0 +1,165 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Command;
use Doctrine\ORM\Mapping\Driver\DatabaseDriver;
use Doctrine\ORM\Tools\Console\MetadataFilter;
use Doctrine\ORM\Tools\DisconnectedClassMetadataFactory;
use Doctrine\ORM\Tools\Export\ClassMetadataExporter;
use Doctrine\Persistence\ManagerRegistry;
use InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use function chmod;
use function dirname;
use function file_put_contents;
use function is_dir;
use function mkdir;
use function sprintf;
use function str_replace;
/**
* Import Doctrine ORM metadata mapping information from an existing database.
*
* @deprecated
*
* @final
*/
class ImportMappingDoctrineCommand extends DoctrineCommand
{
/** @var string[] */
private $bundles;
/** @param string[] $bundles */
public function __construct(ManagerRegistry $doctrine, array $bundles)
{
parent::__construct($doctrine);
$this->bundles = $bundles;
}
protected function configure(): void
{
$this
->setName('doctrine:mapping:import')
->addArgument('name', InputArgument::REQUIRED, 'The bundle or namespace to import the mapping information to')
->addArgument('mapping-type', InputArgument::OPTIONAL, 'The mapping type to export the imported mapping information to')
->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command')
->addOption('shard', null, InputOption::VALUE_REQUIRED, 'The shard connection to use for this command')
->addOption('filter', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'A string pattern used to match entities that should be mapped.')
->addOption('force', null, InputOption::VALUE_NONE, 'Force to overwrite existing mapping files.')
->addOption('path', null, InputOption::VALUE_REQUIRED, 'The path where the files would be generated (not used when a bundle is passed).')
->setDescription('Imports mapping information from an existing database')
->setHelp(<<<EOT
The <info>%command.name%</info> command imports mapping information
from an existing database:
Generate annotation mappings into the src/ directory using App as the namespace:
<info>php %command.full_name% App\\\Entity annotation --path=src/Entity</info>
Generate xml mappings into the config/doctrine/ directory using App as the namespace:
<info>php %command.full_name% App\\\Entity xml --path=config/doctrine</info>
Generate XML mappings into a bundle:
<info>php %command.full_name% "MyCustomBundle" xml</info>
You can also optionally specify which entity manager to import from with the
<info>--em</info> option:
<info>php %command.full_name% "MyCustomBundle" xml --em=default</info>
If you don't want to map every entity that can be found in the database, use the
<info>--filter</info> option. It will try to match the targeted mapped entity with the
provided pattern string.
<info>php %command.full_name% "MyCustomBundle" xml --filter=MyMatchedEntity</info>
Use the <info>--force</info> option, if you want to override existing mapping files:
<info>php %command.full_name% "MyCustomBundle" xml --force</info>
EOT
);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$type = $input->getArgument('mapping-type') ?: 'xml';
if ($type === 'yaml') {
$type = 'yml';
}
$namespaceOrBundle = $input->getArgument('name');
if (isset($this->bundles[$namespaceOrBundle])) {
$bundle = $this->getApplication()->getKernel()->getBundle($namespaceOrBundle);
$namespace = $bundle->getNamespace() . '\Entity';
$destPath = $bundle->getPath();
if ($type === 'annotation') {
$destPath .= '/Entity';
} else {
$destPath .= '/Resources/config/doctrine';
}
} else {
// assume a namespace has been passed
$namespace = $namespaceOrBundle;
$destPath = $input->getOption('path');
if ($destPath === null) {
throw new InvalidArgumentException('The --path option is required when passing a namespace (e.g. --path=src). If you intended to pass a bundle name, check your spelling.');
}
}
$cme = new ClassMetadataExporter();
$exporter = $cme->getExporter($type);
$exporter->setOverwriteExistingFiles($input->getOption('force'));
if ($type === 'annotation') {
$entityGenerator = $this->getEntityGenerator();
$exporter->setEntityGenerator($entityGenerator);
}
$em = $this->getEntityManager($input->getOption('em'), $input->getOption('shard'));
$databaseDriver = new DatabaseDriver($em->getConnection()->getSchemaManager());
$em->getConfiguration()->setMetadataDriverImpl($databaseDriver);
$emName = $input->getOption('em');
$emName = $emName ? $emName : 'default';
$cmf = new DisconnectedClassMetadataFactory();
$cmf->setEntityManager($em);
$metadata = $cmf->getAllMetadata();
$metadata = MetadataFilter::filter($metadata, $input->getOption('filter'));
if ($metadata) {
$output->writeln(sprintf('Importing mapping information from "<info>%s</info>" entity manager', $emName));
foreach ($metadata as $class) {
$className = $class->name;
$class->name = $namespace . '\\' . $className;
if ($type === 'annotation') {
$path = $destPath . '/' . str_replace('\\', '.', $className) . '.php';
} else {
$path = $destPath . '/' . str_replace('\\', '.', $className) . '.orm.' . $type;
}
$output->writeln(sprintf(' > writing <comment>%s</comment>', $path));
$code = $exporter->exportClassMetadata($class);
$dir = dirname($path);
if (! is_dir($dir)) {
mkdir($dir, 0775, true);
}
file_put_contents($path, $code);
chmod($path, 0664);
}
return 0;
}
$output->writeln('Database does not have any mapping information.');
$output->writeln('');
return 1;
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy;
use Doctrine\ORM\Tools\Console\Command\ClearCache\MetadataCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Command to clear the metadata cache of the various cache drivers.
*/
class ClearMetadataCacheDoctrineCommand extends MetadataCommand
{
protected function configure(): void
{
parent::configure();
$this
->setName('doctrine:cache:clear-metadata')
->setDescription('Clears all metadata cache for an entity manager');
if ($this->getDefinition()->hasOption('em')) {
return;
}
$this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
DoctrineCommandHelper::setApplicationEntityManager($this->getApplication(), $input->getOption('em'));
return parent::execute($input, $output);
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy;
use Doctrine\ORM\Tools\Console\Command\ClearCache\QueryCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Command to clear the query cache of the various cache drivers.
*/
class ClearQueryCacheDoctrineCommand extends QueryCommand
{
protected function configure(): void
{
parent::configure();
$this
->setName('doctrine:cache:clear-query')
->setDescription('Clears all query cache for an entity manager');
if ($this->getDefinition()->hasOption('em')) {
return;
}
$this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
DoctrineCommandHelper::setApplicationEntityManager($this->getApplication(), $input->getOption('em'));
return parent::execute($input, $output);
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy;
use Doctrine\ORM\Tools\Console\Command\ClearCache\ResultCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Command to clear the result cache of the various cache drivers.
*/
class ClearResultCacheDoctrineCommand extends ResultCommand
{
protected function configure(): void
{
parent::configure();
$this
->setName('doctrine:cache:clear-result')
->setDescription('Clears result cache for an entity manager');
if ($this->getDefinition()->hasOption('em')) {
return;
}
$this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
DoctrineCommandHelper::setApplicationEntityManager($this->getApplication(), $input->getOption('em'));
return parent::execute($input, $output);
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy;
use Doctrine\ORM\Tools\Console\Command\ClearCache\CollectionRegionCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Command to clear a collection cache region.
*/
class CollectionRegionDoctrineCommand extends CollectionRegionCommand
{
protected function configure(): void
{
parent::configure();
$this
->setName('doctrine:cache:clear-collection-region');
if ($this->getDefinition()->hasOption('em')) {
return;
}
$this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
DoctrineCommandHelper::setApplicationEntityManager($this->getApplication(), $input->getOption('em'));
return parent::execute($input, $output);
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy;
use Doctrine\ORM\Tools\Console\Command\ConvertMappingCommand;
use Doctrine\ORM\Tools\Export\Driver\AbstractExporter;
use Doctrine\ORM\Tools\Export\Driver\XmlExporter;
use Doctrine\ORM\Tools\Export\Driver\YamlExporter;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use function assert;
/**
* Convert Doctrine ORM metadata mapping information between the various supported
* formats.
*/
class ConvertMappingDoctrineCommand extends ConvertMappingCommand
{
/**
* {@inheritDoc}
*/
protected function configure()
{
parent::configure();
$this
->setName('doctrine:mapping:convert');
if ($this->getDefinition()->hasOption('em')) {
return;
}
$this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command');
}
/**
* {@inheritDoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
DoctrineCommandHelper::setApplicationEntityManager($this->getApplication(), $input->getOption('em'));
return parent::execute($input, $output);
}
/**
* @param string $toType
* @param string $destPath
*
* @return AbstractExporter
*/
protected function getExporter($toType, $destPath)
{
$exporter = parent::getExporter($toType, $destPath);
assert($exporter instanceof AbstractExporter);
if ($exporter instanceof XmlExporter) {
$exporter->setExtension('.orm.xml');
} elseif ($exporter instanceof YamlExporter) {
$exporter->setExtension('.orm.yml');
}
return $exporter;
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy;
use Doctrine\ORM\Tools\Console\Command\SchemaTool\CreateCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Command to execute the SQL needed to generate the database schema for
* a given entity manager.
*/
class CreateSchemaDoctrineCommand extends CreateCommand
{
protected function configure(): void
{
parent::configure();
$this
->setName('doctrine:schema:create')
->setDescription('Executes (or dumps) the SQL needed to generate the database schema');
if ($this->getDefinition()->hasOption('em')) {
return;
}
$this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
DoctrineCommandHelper::setApplicationEntityManager($this->getApplication(), $input->getOption('em'));
return parent::execute($input, $output);
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy;
use Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use function assert;
use function class_exists;
/**
* Provides some helper and convenience methods to configure doctrine commands in the context of bundles
* and multiple connections/entity managers.
*/
abstract class DoctrineCommandHelper
{
/**
* Convenience method to push the helper sets of a given entity manager into the application.
*
* @param string $emName
*/
public static function setApplicationEntityManager(Application $application, $emName)
{
$em = $application->getKernel()->getContainer()->get('doctrine')->getManager($emName);
assert($em instanceof EntityManagerInterface);
$helperSet = $application->getHelperSet();
if (class_exists(ConnectionHelper::class)) {
$helperSet->set(new ConnectionHelper($em->getConnection()), 'db');
}
$helperSet->set(new EntityManagerHelper($em), 'em');
}
/**
* Convenience method to push the helper sets of a given connection into the application.
*
* @param string $connName
*/
public static function setApplicationConnection(Application $application, $connName)
{
$connection = $application->getKernel()->getContainer()->get('doctrine')->getConnection($connName);
$helperSet = $application->getHelperSet();
$helperSet->set(new ConnectionHelper($connection), 'db');
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy;
use Doctrine\ORM\Tools\Console\Command\SchemaTool\DropCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Command to drop the database schema for a set of classes based on their mappings.
*/
class DropSchemaDoctrineCommand extends DropCommand
{
protected function configure(): void
{
parent::configure();
$this
->setName('doctrine:schema:drop')
->setDescription('Executes (or dumps) the SQL needed to drop the current database schema');
if ($this->getDefinition()->hasOption('em')) {
return;
}
$this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
DoctrineCommandHelper::setApplicationEntityManager($this->getApplication(), $input->getOption('em'));
return parent::execute($input, $output);
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy;
use Doctrine\ORM\Tools\Console\Command\EnsureProductionSettingsCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Ensure the Doctrine ORM is configured properly for a production environment.
*/
class EnsureProductionSettingsDoctrineCommand extends EnsureProductionSettingsCommand
{
protected function configure(): void
{
parent::configure();
$this
->setName('doctrine:ensure-production-settings');
if ($this->getDefinition()->hasOption('em')) {
return;
}
$this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
DoctrineCommandHelper::setApplicationEntityManager($this->getApplication(), $input->getOption('em'));
return parent::execute($input, $output);
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy;
use Doctrine\ORM\Tools\Console\Command\ClearCache\EntityRegionCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Command to clear a entity cache region.
*/
class EntityRegionCacheDoctrineCommand extends EntityRegionCommand
{
protected function configure(): void
{
parent::configure();
$this
->setName('doctrine:cache:clear-entity-region');
if ($this->getDefinition()->hasOption('em')) {
return;
}
$this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
DoctrineCommandHelper::setApplicationEntityManager($this->getApplication(), $input->getOption('em'));
return parent::execute($input, $output);
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy;
use Doctrine\DBAL\Tools\Console\Command\ImportCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use function trigger_deprecation;
/**
* Loads an SQL file and executes it.
*
* @deprecated Use a database client application instead.
*/
class ImportDoctrineCommand extends ImportCommand
{
/**
* {@inheritDoc}
*/
protected function configure()
{
parent::configure();
$this
->setName('doctrine:database:import')
->addOption('connection', null, InputOption::VALUE_OPTIONAL, 'The connection to use for this command');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
trigger_deprecation(
'doctrine/doctrine-bundle',
'2.2',
'The "%s" (doctrine:database:import) is deprecated, use a database client instead.',
self::class
);
DoctrineCommandHelper::setApplicationConnection($this->getApplication(), $input->getOption('connection'));
return parent::execute($input, $output);
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy;
use Doctrine\ORM\Tools\Console\Command\InfoCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Show information about mapped entities
*/
class InfoDoctrineCommand extends InfoCommand
{
protected function configure(): void
{
$this
->setName('doctrine:mapping:info');
if ($this->getDefinition()->hasOption('em')) {
return;
}
$this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
DoctrineCommandHelper::setApplicationEntityManager($this->getApplication(), $input->getOption('em'));
return parent::execute($input, $output);
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy;
use Doctrine\ORM\Tools\Console\Command\ClearCache\QueryRegionCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Command to clear a query cache region.
*/
class QueryRegionCacheDoctrineCommand extends QueryRegionCommand
{
protected function configure(): void
{
parent::configure();
$this
->setName('doctrine:cache:clear-query-region');
if ($this->getDefinition()->hasOption('em')) {
return;
}
$this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
DoctrineCommandHelper::setApplicationEntityManager($this->getApplication(), $input->getOption('em'));
return parent::execute($input, $output);
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy;
use Doctrine\ORM\Tools\Console\Command\RunDqlCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Execute a Doctrine DQL query and output the results.
*/
class RunDqlDoctrineCommand extends RunDqlCommand
{
protected function configure(): void
{
parent::configure();
$this
->setName('doctrine:query:dql')
->setHelp(<<<EOT
The <info>%command.name%</info> command executes the given DQL query and
outputs the results:
<info>php %command.full_name% "SELECT u FROM UserBundle:User u"</info>
You can also optional specify some additional options like what type of
hydration to use when executing the query:
<info>php %command.full_name% "SELECT u FROM UserBundle:User u" --hydrate=array</info>
Additionally you can specify the first result and maximum amount of results to
show:
<info>php %command.full_name% "SELECT u FROM UserBundle:User u" --first-result=0 --max-result=30</info>
EOT
);
if ($this->getDefinition()->hasOption('em')) {
return;
}
$this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
DoctrineCommandHelper::setApplicationEntityManager($this->getApplication(), $input->getOption('em'));
return parent::execute($input, $output);
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy;
use Doctrine\DBAL\Tools\Console\Command\RunSqlCommand;
use Doctrine\DBAL\Tools\Console\ConnectionProvider;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use function trigger_deprecation;
/**
* Execute a SQL query and output the results.
*
* @deprecated use Doctrine\DBAL\Tools\Console\Command\RunSqlCommand instead
*/
class RunSqlDoctrineCommand extends RunSqlCommand
{
/** @var ConnectionProvider|null */
private $connectionProvider;
public function __construct(?ConnectionProvider $connectionProvider = null)
{
parent::__construct($connectionProvider);
$this->connectionProvider = $connectionProvider;
}
protected function configure(): void
{
parent::configure();
$this
->setName('doctrine:query:sql')
->setHelp(<<<EOT
The <info>%command.name%</info> command executes the given SQL query and
outputs the results:
<info>php %command.full_name% "SELECT * FROM users"</info>
EOT
);
if ($this->getDefinition()->hasOption('connection')) {
return;
}
$this->addOption('connection', null, InputOption::VALUE_OPTIONAL, 'The connection to use for this command');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
trigger_deprecation(
'doctrine/doctrine-bundle',
'2.2',
'The "%s" (doctrine:query:sql) is deprecated, use dbal:run-sql command instead.',
self::class
);
if (! $this->connectionProvider) {
DoctrineCommandHelper::setApplicationConnection($this->getApplication(), $input->getOption('connection'));
// compatibility with doctrine/dbal 2.11+
// where this option is also present and unsupported before we are not switching to use a ConnectionProvider
$input->setOption('connection', null);
}
return parent::execute($input, $output);
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy;
use Doctrine\ORM\Tools\Console\Command\SchemaTool\UpdateCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Command to generate the SQL needed to update the database schema to match
* the current mapping information.
*/
class UpdateSchemaDoctrineCommand extends UpdateCommand
{
protected function configure(): void
{
parent::configure();
$this
->setName('doctrine:schema:update');
if ($this->getDefinition()->hasOption('em')) {
return;
}
$this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
DoctrineCommandHelper::setApplicationEntityManager($this->getApplication(), $input->getOption('em'));
return parent::execute($input, $output);
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy;
use Doctrine\ORM\Tools\Console\Command\ValidateSchemaCommand as DoctrineValidateSchemaCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Command to run Doctrine ValidateSchema() on the current mappings.
*/
class ValidateSchemaCommand extends DoctrineValidateSchemaCommand
{
protected function configure(): void
{
parent::configure();
$this
->setName('doctrine:schema:validate');
if ($this->getDefinition()->hasOption('em')) {
return;
}
$this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
DoctrineCommandHelper::setApplicationEntityManager($this->getApplication(), $input->getOption('em'));
return parent::execute($input, $output);
}
}

View File

@@ -0,0 +1,194 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Configuration;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Exception as DBALException;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\MySqlPlatform;
use Doctrine\DBAL\Types\Type;
use Doctrine\Deprecations\Deprecation;
use function array_merge;
use function defined;
use function is_subclass_of;
use function trigger_deprecation;
use const PHP_EOL;
/** @psalm-import-type Params from DriverManager */
class ConnectionFactory
{
/** @var mixed[][] */
private $typesConfig = [];
/** @var bool */
private $initialized = false;
/** @param mixed[][] $typesConfig */
public function __construct(array $typesConfig)
{
$this->typesConfig = $typesConfig;
}
/**
* Create a connection by name.
*
* @param mixed[] $params
* @param array<string, string> $mappingTypes
* @psalm-param Params $params
*
* @return Connection
*/
public function createConnection(array $params, ?Configuration $config = null, ?EventManager $eventManager = null, array $mappingTypes = [])
{
if (! $this->initialized) {
$this->initializeTypes();
}
$overriddenOptions = [];
if (isset($params['connection_override_options'])) {
trigger_deprecation('doctrine/doctrine-bundle', '2.4', 'The "connection_override_options" connection parameter is deprecated');
$overriddenOptions = $params['connection_override_options'];
unset($params['connection_override_options']);
}
if (! isset($params['pdo']) && (! isset($params['charset']) || $overriddenOptions || isset($params['dbname_suffix']))) {
$wrapperClass = null;
if (isset($params['wrapperClass'])) {
if (! is_subclass_of($params['wrapperClass'], Connection::class)) {
throw DBALException::invalidWrapperClass($params['wrapperClass']);
}
$wrapperClass = $params['wrapperClass'];
$params['wrapperClass'] = null;
}
$connection = DriverManager::getConnection($params, $config, $eventManager);
$params = $this->addDatabaseSuffix(array_merge($connection->getParams(), $overriddenOptions));
$driver = $connection->getDriver();
$platform = $driver->getDatabasePlatform();
if (! isset($params['charset'])) {
/** @psalm-suppress UndefinedClass AbstractMySQLPlatform exists since DBAL 3.x only */
if ($platform instanceof AbstractMySQLPlatform || $platform instanceof MySqlPlatform) {
$params['charset'] = 'utf8mb4';
/* PARAM_ASCII_STR_ARRAY is defined since doctrine/dbal 3.3
doctrine/dbal 3.3.2 adds support for the option "collation"
Checking for that constant will no longer be necessary
after dropping support for doctrine/dbal 2, since this
package requires doctrine/dbal 3.3.2 or higher. */
if (isset($params['defaultTableOptions']['collate']) && defined('Doctrine\DBAL\Connection::PARAM_ASCII_STR_ARRAY')) {
Deprecation::trigger(
'doctrine/doctrine-bundle',
'https://github.com/doctrine/dbal/issues/5214',
'The "collate" default table option is deprecated in favor of "collation" and will be removed in doctrine/doctrine-bundle 3.0. '
);
$params['defaultTableOptions']['collation'] = $params['defaultTableOptions']['collate'];
unset($params['defaultTableOptions']['collate']);
}
$collationOption = defined('Doctrine\DBAL\Connection::PARAM_ASCII_STR_ARRAY') ? 'collation' : 'collate';
if (! isset($params['defaultTableOptions'][$collationOption])) {
$params['defaultTableOptions'][$collationOption] = 'utf8mb4_unicode_ci';
}
} else {
$params['charset'] = 'utf8';
}
}
if ($wrapperClass !== null) {
$params['wrapperClass'] = $wrapperClass;
} else {
$wrapperClass = Connection::class;
}
$connection = new $wrapperClass($params, $driver, $config, $eventManager);
} else {
$connection = DriverManager::getConnection($params, $config, $eventManager);
}
if (! empty($mappingTypes)) {
$platform = $this->getDatabasePlatform($connection);
foreach ($mappingTypes as $dbType => $doctrineType) {
$platform->registerDoctrineTypeMapping($dbType, $doctrineType);
}
}
return $connection;
}
/**
* Try to get the database platform.
*
* This could fail if types should be registered to an predefined/unused connection
* and the platform version is unknown.
* For details have a look at DoctrineBundle issue #673.
*
* @throws DBALException
*/
private function getDatabasePlatform(Connection $connection): AbstractPlatform
{
try {
return $connection->getDatabasePlatform();
} catch (DriverException $driverException) {
throw new DBALException(
'An exception occurred while establishing a connection to figure out your platform version.' . PHP_EOL .
"You can circumvent this by setting a 'server_version' configuration value" . PHP_EOL . PHP_EOL .
'For further information have a look at:' . PHP_EOL .
'https://github.com/doctrine/DoctrineBundle/issues/673',
0,
$driverException
);
}
}
/**
* initialize the types
*/
private function initializeTypes(): void
{
foreach ($this->typesConfig as $typeName => $typeConfig) {
if (Type::hasType($typeName)) {
Type::overrideType($typeName, $typeConfig['class']);
} else {
Type::addType($typeName, $typeConfig['class']);
}
}
$this->initialized = true;
}
/**
* @param array<string, mixed> $params
*
* @return array<string, mixed>
*/
private function addDatabaseSuffix(array $params): array
{
if (isset($params['dbname']) && isset($params['dbname_suffix'])) {
$params['dbname'] .= $params['dbname_suffix'];
}
foreach ($params['replica'] ?? [] as $key => $replicaParams) {
if (! isset($replicaParams['dbname'], $replicaParams['dbname_suffix'])) {
continue;
}
$params['replica'][$key]['dbname'] .= $replicaParams['dbname_suffix'];
}
if (isset($params['primary']['dbname'], $params['primary']['dbname_suffix'])) {
$params['primary']['dbname'] .= $params['primary']['dbname_suffix'];
}
return $params;
}
}

View File

@@ -0,0 +1,174 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Controller;
use Doctrine\Bundle\DoctrineBundle\Registry;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\ForwardCompatibility\Result;
use Doctrine\DBAL\Platforms\OraclePlatform;
use Doctrine\DBAL\Platforms\SqlitePlatform;
use Doctrine\DBAL\Platforms\SQLServerPlatform;
use LogicException;
use PDO;
use PDOStatement;
use Symfony\Bridge\Doctrine\DataCollector\DoctrineDataCollector;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Profiler\Profiler;
use Symfony\Component\VarDumper\Cloner\Data;
use Throwable;
use Twig\Environment;
use function assert;
use function stripos;
/** @internal */
class ProfilerController
{
/** @var Environment */
private $twig;
/** @var Registry */
private $registry;
/** @var Profiler */
private $profiler;
public function __construct(Environment $twig, Registry $registry, Profiler $profiler)
{
$this->twig = $twig;
$this->registry = $registry;
$this->profiler = $profiler;
}
/**
* Renders the profiler panel for the given token.
*
* @param string $token The profiler token
* @param string $connectionName
* @param int $query
*
* @return Response A Response instance
*/
public function explainAction($token, $connectionName, $query)
{
$this->profiler->disable();
$profile = $this->profiler->loadProfile($token);
$collector = $profile->getCollector('db');
assert($collector instanceof DoctrineDataCollector);
$queries = $collector->getQueries();
if (! isset($queries[$connectionName][$query])) {
return new Response('This query does not exist.');
}
$query = $queries[$connectionName][$query];
if (! $query['explainable']) {
return new Response('This query cannot be explained.');
}
$connection = $this->registry->getConnection($connectionName);
assert($connection instanceof Connection);
try {
$platform = $connection->getDatabasePlatform();
if ($platform instanceof SqlitePlatform) {
$results = $this->explainSQLitePlatform($connection, $query);
} elseif ($platform instanceof SQLServerPlatform) {
$results = $this->explainSQLServerPlatform($connection, $query);
} elseif ($platform instanceof OraclePlatform) {
$results = $this->explainOraclePlatform($connection, $query);
} else {
$results = $this->explainOtherPlatform($connection, $query);
}
} catch (Throwable $e) {
return new Response('This query cannot be explained.');
}
return new Response($this->twig->render('@Doctrine/Collector/explain.html.twig', [
'data' => $results,
'query' => $query,
]));
}
/**
* @param mixed[] $query
*
* @return mixed[]
*/
private function explainSQLitePlatform(Connection $connection, array $query): array
{
$params = $query['params'];
if ($params instanceof Data) {
$params = $params->getValue(true);
}
return $connection->executeQuery('EXPLAIN QUERY PLAN ' . $query['sql'], $params, $query['types'])
->fetchAll(PDO::FETCH_ASSOC);
}
/**
* @param mixed[] $query
*
* @return mixed[]
*/
private function explainSQLServerPlatform(Connection $connection, array $query): array
{
if (stripos($query['sql'], 'SELECT') === 0) {
$sql = 'SET STATISTICS PROFILE ON; ' . $query['sql'] . '; SET STATISTICS PROFILE OFF;';
} else {
$sql = 'SET SHOWPLAN_TEXT ON; GO; SET NOEXEC ON; ' . $query['sql'] . '; SET NOEXEC OFF; GO; SET SHOWPLAN_TEXT OFF;';
}
$params = $query['params'];
if ($params instanceof Data) {
$params = $params->getValue(true);
}
$stmt = $connection->executeQuery($sql, $params, $query['types']);
// DBAL 2.13 "forward compatibility" BC break handling
if ($stmt instanceof Result) {
$stmt = $stmt->getIterator();
}
if (! $stmt instanceof PDOStatement) {
throw new LogicException('We need nextRowSet() functionality feature, which is not available with current DBAL driver');
}
$stmt->nextRowset();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* @param mixed[] $query
*
* @return mixed[]
*/
private function explainOtherPlatform(Connection $connection, array $query): array
{
$params = $query['params'];
if ($params instanceof Data) {
$params = $params->getValue(true);
}
return $connection->executeQuery('EXPLAIN ' . $query['sql'], $params, $query['types'])
->fetchAll(PDO::FETCH_ASSOC);
}
/**
* @param mixed[] $query
*
* @return mixed[]
*/
private function explainOraclePlatform(Connection $connection, array $query): array
{
$connection->executeQuery('EXPLAIN PLAN FOR ' . $query['sql']);
return $connection->executeQuery('SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY())')
->fetchAll(PDO::FETCH_ASSOC);
}
}

View File

@@ -0,0 +1,312 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\DataCollector;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\Cache\CacheConfiguration;
use Doctrine\ORM\Cache\Logging\CacheLoggerChain;
use Doctrine\ORM\Cache\Logging\StatisticsCacheLogger;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\ORM\Tools\SchemaValidator;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Persistence\Mapping\AbstractClassMetadataFactory;
use Symfony\Bridge\Doctrine\DataCollector\DoctrineDataCollector as BaseCollector;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Throwable;
use function array_map;
use function array_sum;
use function assert;
use function count;
use function usort;
/**
* @psalm-type QueryType = array{
* executionMS: float,
* explainable: bool,
* sql: string,
* params: ?array<array-key, mixed>,
* runnable: bool,
* types: ?array<array-key, Type|int|string|null>,
* }
* @psalm-type DataType = array{
* caches: array{
* enabled: bool,
* counts: array<"puts"|"hits"|"misses", int>,
* log_enabled: bool,
* regions: array<"puts"|"hits"|"misses", array<string, int>>,
* },
* connections: list<string>,
* entities: array<string, array<class-string, class-string>>,
* errors: array<string, array<class-string, list<string>>>,
* managers: list<string>,
* queries: array<string, list<QueryType>>,
* }
* @psalm-property DataType $data
*/
class DoctrineDataCollector extends BaseCollector
{
/** @var ManagerRegistry */
private $registry;
/** @var int|null */
private $invalidEntityCount;
/**
* @var mixed[][]
* @psalm-var ?array<string, list<QueryType&array{count: int, index: int, executionPercent: float}>>
*/
private $groupedQueries;
/** @var bool */
private $shouldValidateSchema;
public function __construct(ManagerRegistry $registry, bool $shouldValidateSchema = true)
{
$this->registry = $registry;
$this->shouldValidateSchema = $shouldValidateSchema;
parent::__construct($registry);
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, ?Throwable $exception = null)
{
parent::collect($request, $response, $exception);
$errors = [];
$entities = [];
$caches = [
'enabled' => false,
'log_enabled' => false,
'counts' => [
'puts' => 0,
'hits' => 0,
'misses' => 0,
],
'regions' => [
'puts' => [],
'hits' => [],
'misses' => [],
],
];
foreach ($this->registry->getManagers() as $name => $em) {
assert($em instanceof EntityManagerInterface);
if ($this->shouldValidateSchema) {
$entities[$name] = [];
$factory = $em->getMetadataFactory();
$validator = new SchemaValidator($em);
assert($factory instanceof AbstractClassMetadataFactory);
foreach ($factory->getLoadedMetadata() as $class) {
assert($class instanceof ClassMetadataInfo);
if (isset($entities[$name][$class->getName()])) {
continue;
}
$classErrors = $validator->validateClass($class);
$entities[$name][$class->getName()] = $class->getName();
if (empty($classErrors)) {
continue;
}
$errors[$name][$class->getName()] = $classErrors;
}
}
$emConfig = $em->getConfiguration();
assert($emConfig instanceof Configuration);
$slcEnabled = $emConfig->isSecondLevelCacheEnabled();
if (! $slcEnabled) {
continue;
}
$caches['enabled'] = true;
$cacheConfiguration = $emConfig->getSecondLevelCacheConfiguration();
assert($cacheConfiguration instanceof CacheConfiguration);
$cacheLoggerChain = $cacheConfiguration->getCacheLogger();
assert($cacheLoggerChain instanceof CacheLoggerChain || $cacheLoggerChain === null);
if (! $cacheLoggerChain || ! $cacheLoggerChain->getLogger('statistics')) {
continue;
}
$cacheLoggerStats = $cacheLoggerChain->getLogger('statistics');
assert($cacheLoggerStats instanceof StatisticsCacheLogger);
$caches['log_enabled'] = true;
$caches['counts']['puts'] += $cacheLoggerStats->getPutCount();
$caches['counts']['hits'] += $cacheLoggerStats->getHitCount();
$caches['counts']['misses'] += $cacheLoggerStats->getMissCount();
foreach ($cacheLoggerStats->getRegionsPut() as $key => $value) {
if (! isset($caches['regions']['puts'][$key])) {
$caches['regions']['puts'][$key] = 0;
}
$caches['regions']['puts'][$key] += $value;
}
foreach ($cacheLoggerStats->getRegionsHit() as $key => $value) {
if (! isset($caches['regions']['hits'][$key])) {
$caches['regions']['hits'][$key] = 0;
}
$caches['regions']['hits'][$key] += $value;
}
foreach ($cacheLoggerStats->getRegionsMiss() as $key => $value) {
if (! isset($caches['regions']['misses'][$key])) {
$caches['regions']['misses'][$key] = 0;
}
$caches['regions']['misses'][$key] += $value;
}
}
$this->data['entities'] = $entities;
$this->data['errors'] = $errors;
$this->data['caches'] = $caches;
$this->groupedQueries = null;
}
/** @return array<string, array<string, string>> */
public function getEntities()
{
return $this->data['entities'];
}
/** @return array<string, array<string, list<string>>> */
public function getMappingErrors()
{
return $this->data['errors'];
}
/** @return int */
public function getCacheHitsCount()
{
return $this->data['caches']['counts']['hits'];
}
/** @return int */
public function getCachePutsCount()
{
return $this->data['caches']['counts']['puts'];
}
/** @return int */
public function getCacheMissesCount()
{
return $this->data['caches']['counts']['misses'];
}
/** @return bool */
public function getCacheEnabled()
{
return $this->data['caches']['enabled'];
}
/**
* @return array<string, array<string, int>>
* @psalm-return array<"puts"|"hits"|"misses", array<string, int>>
*/
public function getCacheRegions()
{
return $this->data['caches']['regions'];
}
/** @return array<string, int> */
public function getCacheCounts()
{
return $this->data['caches']['counts'];
}
/** @return int */
public function getInvalidEntityCount()
{
if ($this->invalidEntityCount === null) {
$this->invalidEntityCount = array_sum(array_map('count', $this->data['errors']));
}
return $this->invalidEntityCount;
}
/**
* @return string[][]
* @psalm-return array<string, list<QueryType&array{count: int, index: int, executionPercent: float}>>
*/
public function getGroupedQueries()
{
if ($this->groupedQueries !== null) {
return $this->groupedQueries;
}
$this->groupedQueries = [];
$totalExecutionMS = 0;
foreach ($this->data['queries'] as $connection => $queries) {
$connectionGroupedQueries = [];
foreach ($queries as $i => $query) {
$key = $query['sql'];
if (! isset($connectionGroupedQueries[$key])) {
$connectionGroupedQueries[$key] = $query;
$connectionGroupedQueries[$key]['executionMS'] = 0;
$connectionGroupedQueries[$key]['count'] = 0;
$connectionGroupedQueries[$key]['index'] = $i; // "Explain query" relies on query index in 'queries'.
}
$connectionGroupedQueries[$key]['executionMS'] += $query['executionMS'];
$connectionGroupedQueries[$key]['count']++;
$totalExecutionMS += $query['executionMS'];
}
usort($connectionGroupedQueries, static function ($a, $b) {
if ($a['executionMS'] === $b['executionMS']) {
return 0;
}
return $a['executionMS'] < $b['executionMS'] ? 1 : -1;
});
$this->groupedQueries[$connection] = $connectionGroupedQueries;
}
foreach ($this->groupedQueries as $connection => $queries) {
foreach ($queries as $i => $query) {
$this->groupedQueries[$connection][$i]['executionPercent'] =
$this->executionTimePercentage($query['executionMS'], $totalExecutionMS);
}
}
return $this->groupedQueries;
}
private function executionTimePercentage(float $executionTimeMS, float $totalExecutionTimeMS): float
{
if (! $totalExecutionTimeMS) {
return 0;
}
return $executionTimeMS / $totalExecutionTimeMS * 100;
}
/** @return int */
public function getGroupedQueryCount()
{
$count = 0;
foreach ($this->getGroupedQueries() as $connectionGroupedQueries) {
$count += count($connectionGroupedQueries);
}
return $count;
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Dbal;
use Doctrine\DBAL\Schema\AbstractAsset;
use function in_array;
/** @deprecated Implement your own include/exclude mechanism */
class BlacklistSchemaAssetFilter
{
/** @var string[] */
private $blacklist;
/** @param string[] $blacklist */
public function __construct(array $blacklist)
{
$this->blacklist = $blacklist;
}
/** @param string|AbstractAsset $assetName */
public function __invoke($assetName): bool
{
if ($assetName instanceof AbstractAsset) {
$assetName = $assetName->getName();
}
return ! in_array($assetName, $this->blacklist, true);
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Dbal\Logging;
use Doctrine\DBAL\Logging\DebugStack;
use function array_shift;
use function debug_backtrace;
use const DEBUG_BACKTRACE_IGNORE_ARGS;
final class BacktraceLogger extends DebugStack
{
/**
* {@inheritdoc}
*/
public function startQuery($sql, ?array $params = null, ?array $types = null): void
{
parent::startQuery($sql, $params, $types);
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
// skip first since it's always the current method
array_shift($backtrace);
$this->queries[$this->currentQuery]['backtrace'] = $backtrace;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Dbal;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Tools\Console\ConnectionProvider;
use Doctrine\Persistence\AbstractManagerRegistry;
class ManagerRegistryAwareConnectionProvider implements ConnectionProvider
{
/** @var AbstractManagerRegistry */
private $managerRegistry;
public function __construct(AbstractManagerRegistry $managerRegistry)
{
$this->managerRegistry = $managerRegistry;
}
public function getDefaultConnection(): Connection
{
return $this->managerRegistry->getConnection();
}
public function getConnection(string $name): Connection
{
return $this->managerRegistry->getConnection($name);
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Dbal;
use Doctrine\DBAL\Schema\AbstractAsset;
use function preg_match;
class RegexSchemaAssetFilter
{
/** @var string */
private $filterExpression;
public function __construct(string $filterExpression)
{
$this->filterExpression = $filterExpression;
}
/** @param string|AbstractAsset $assetName */
public function __invoke($assetName): bool
{
if ($assetName instanceof AbstractAsset) {
$assetName = $assetName->getName();
}
return (bool) preg_match($this->filterExpression, $assetName);
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Dbal;
use Doctrine\DBAL\Schema\AbstractAsset;
/**
* Manages schema filters passed to Connection::setSchemaAssetsFilter()
*/
class SchemaAssetsFilterManager
{
/** @var callable[] */
private $schemaAssetFilters;
/** @param callable[] $schemaAssetFilters */
public function __construct(array $schemaAssetFilters)
{
$this->schemaAssetFilters = $schemaAssetFilters;
}
/** @param string|AbstractAsset $assetName */
public function __invoke($assetName): bool
{
foreach ($this->schemaAssetFilters as $schemaAssetFilter) {
if ($schemaAssetFilter($assetName) === false) {
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,135 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler;
use Doctrine\Common\Cache\CacheProvider;
use Doctrine\Common\Cache\Psr6\CacheAdapter;
use Doctrine\Common\Cache\Psr6\DoctrineProvider;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use function array_keys;
use function assert;
use function in_array;
use function is_a;
use function trigger_deprecation;
/** @internal */
final class CacheCompatibilityPass implements CompilerPassInterface
{
private const CONFIGURATION_TAG = 'doctrine.orm.configuration';
private const CACHE_METHODS_PSR6_SUPPORT = [
'setMetadataCache',
'setQueryCache',
'setResultCache',
];
public function process(ContainerBuilder $container): void
{
foreach (array_keys($container->findTaggedServiceIds(self::CONFIGURATION_TAG)) as $id) {
foreach ($container->getDefinition($id)->getMethodCalls() as $methodCall) {
if ($methodCall[0] === 'setSecondLevelCacheConfiguration') {
$this->updateSecondLevelCache($container, $methodCall[1][0]);
continue;
}
if (! in_array($methodCall[0], self::CACHE_METHODS_PSR6_SUPPORT, true)) {
continue;
}
$aliasId = (string) $methodCall[1][0];
$definitionId = (string) $container->getAlias($aliasId);
$this->wrapIfNecessary($container, $aliasId, $definitionId, true);
}
}
}
private function updateSecondLevelCache(ContainerBuilder $container, Definition $slcConfigDefinition): void
{
foreach ($slcConfigDefinition->getMethodCalls() as $methodCall) {
if ($methodCall[0] !== 'setCacheFactory') {
continue;
}
$factoryDefinition = $methodCall[1][0];
assert($factoryDefinition instanceof Definition);
$aliasId = (string) $factoryDefinition->getArgument(1);
$this->wrapIfNecessary($container, $aliasId, (string) $container->getAlias($aliasId), false);
foreach ($factoryDefinition->getMethodCalls() as $factoryMethodCall) {
if ($factoryMethodCall[0] !== 'setRegion') {
continue;
}
$regionDefinition = $container->getDefinition($factoryMethodCall[1][0]);
// Get inner service for FileLock
if ($regionDefinition->getClass() === '%doctrine.orm.second_level_cache.filelock_region.class%') {
$regionDefinition = $container->getDefinition($regionDefinition->getArgument(0));
}
// We don't know how to adjust custom region classes
if ($regionDefinition->getClass() !== '%doctrine.orm.second_level_cache.default_region.class%') {
continue;
}
$driverId = (string) $regionDefinition->getArgument(1);
if (! $container->hasAlias($driverId)) {
continue;
}
$this->wrapIfNecessary($container, $driverId, (string) $container->getAlias($driverId), false);
}
break;
}
}
private function createCompatibilityLayerDefinition(ContainerBuilder $container, string $definitionId, bool $shouldBePsr6): ?Definition
{
$definition = $container->getDefinition($definitionId);
while (! $definition->getClass() && $definition instanceof ChildDefinition) {
$definition = $container->findDefinition($definition->getParent());
}
if ($shouldBePsr6 === is_a($definition->getClass(), CacheItemPoolInterface::class, true)) {
return null;
}
$targetClass = CacheProvider::class;
$targetFactory = DoctrineProvider::class;
if ($shouldBePsr6) {
$targetClass = CacheItemPoolInterface::class;
$targetFactory = CacheAdapter::class;
trigger_deprecation(
'doctrine/doctrine-bundle',
'2.4',
'Configuring doctrine/cache is deprecated. Please update the cache service "%s" to use a PSR-6 cache.',
$definitionId
);
}
return (new Definition($targetClass))
->setFactory([$targetFactory, 'wrap'])
->addArgument(new Reference($definitionId));
}
private function wrapIfNecessary(ContainerBuilder $container, string $aliasId, string $definitionId, bool $shouldBePsr6): void
{
$compatibilityLayer = $this->createCompatibilityLayerDefinition($container, $definitionId, $shouldBePsr6);
if ($compatibilityLayer === null) {
return;
}
$compatibilityLayerId = $definitionId . '.compatibility_layer';
$container->setAlias($aliasId, $compatibilityLayerId);
$container->setDefinition($compatibilityLayerId, $compatibilityLayer);
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler;
use Symfony\Component\Cache\Adapter\DoctrineDbalAdapter;
use Symfony\Component\Cache\Adapter\PdoAdapter;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* Injects Doctrine DBAL and legacy PDO adapters into their schema subscribers.
*
* Must be run later after ResolveChildDefinitionsPass.
*/
class CacheSchemaSubscriberPass implements CompilerPassInterface
{
/**
* {@inheritDoc}
*/
public function process(ContainerBuilder $container)
{
// available in Symfony 5.4 and higher
/** @psalm-suppress UndefinedClass */
$this->injectAdapters($container, 'doctrine.orm.listeners.doctrine_dbal_cache_adapter_schema_subscriber', DoctrineDbalAdapter::class);
// available in Symfony 5.1 and up to Symfony 5.4 (deprecated)
$this->injectAdapters($container, 'doctrine.orm.listeners.pdo_cache_adapter_doctrine_schema_subscriber', PdoAdapter::class);
}
private function injectAdapters(ContainerBuilder $container, string $subscriberId, string $class)
{
if (! $container->hasDefinition($subscriberId)) {
return;
}
$subscriber = $container->getDefinition($subscriberId);
$cacheAdaptersReferences = [];
foreach ($container->getDefinitions() as $id => $definition) {
if ($definition->isAbstract() || $definition->isSynthetic()) {
continue;
}
if ($definition->getClass() !== $class) {
continue;
}
$cacheAdaptersReferences[] = new Reference($id);
}
$subscriber->replaceArgument(0, $cacheAdaptersReferences);
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use function sprintf;
/**
* Processes the doctrine.dbal.schema_filter
*/
class DbalSchemaFilterPass implements CompilerPassInterface
{
/**
* {@inheritDoc}
*/
public function process(ContainerBuilder $container)
{
$filters = $container->findTaggedServiceIds('doctrine.dbal.schema_filter');
$connectionFilters = [];
foreach ($filters as $id => $tagAttributes) {
foreach ($tagAttributes as $attributes) {
$name = $attributes['connection'] ?? $container->getParameter('doctrine.default_connection');
if (! isset($connectionFilters[$name])) {
$connectionFilters[$name] = [];
}
$connectionFilters[$name][] = new Reference($id);
}
}
foreach ($connectionFilters as $name => $references) {
$configurationId = sprintf('doctrine.dbal.%s_connection.configuration', $name);
if (! $container->hasDefinition($configurationId)) {
continue;
}
$definition = new ChildDefinition('doctrine.dbal.schema_asset_filter_manager');
$definition->setArgument(0, $references);
$id = sprintf('doctrine.dbal.%s_schema_asset_filter_manager', $name);
$container->setDefinition($id, $definition);
$container->findDefinition($configurationId)
->addMethodCall('setSchemaAssetsFilter', [new Reference($id)]);
}
}
}

View File

@@ -0,0 +1,180 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler;
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\ORM\Mapping\Driver\XmlDriver;
use Doctrine\ORM\Mapping\Driver\YamlDriver;
use Doctrine\Persistence\Mapping\Driver\PHPDriver;
use Doctrine\Persistence\Mapping\Driver\StaticPHPDriver;
use Doctrine\Persistence\Mapping\Driver\SymfonyFileLocator;
use Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass\RegisterMappingsPass;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
/**
* Class for Symfony bundles to configure mappings for model classes not in the
* auto-mapped folder.
*
* NOTE: alias is only supported by Symfony 2.6+ and will be ignored with older versions.
*/
class DoctrineOrmMappingsPass extends RegisterMappingsPass
{
/**
* You should not directly instantiate this class but use one of the
* factory methods.
*
* @param Definition|Reference $driver Driver DI definition or reference.
* @param string[] $namespaces List of namespaces handled by $driver.
* @param string[] $managerParameters Ordered list of container parameters that
* could hold the manager name.
* doctrine.default_entity_manager is appended
* automatically.
* @param string|false $enabledParameter If specified, the compiler pass only executes
* if this parameter is defined in the service
* container.
* @param string[] $aliasMap Map of alias to namespace.
*/
public function __construct($driver, array $namespaces, array $managerParameters, $enabledParameter = false, array $aliasMap = [])
{
$managerParameters[] = 'doctrine.default_entity_manager';
parent::__construct(
$driver,
$namespaces,
$managerParameters,
'doctrine.orm.%s_metadata_driver',
$enabledParameter,
'doctrine.orm.%s_configuration',
'addEntityNamespace',
$aliasMap
);
}
/**
* @param string[] $namespaces Hashmap of directory path to namespace.
* @param string[] $managerParameters List of parameters that could which object manager name
* your bundle uses. This compiler pass will automatically
* append the parameter name for the default entity manager
* to this list.
* @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[] $aliasMap Map of alias to namespace.
*
* @return self
*/
public static function createXmlMappingDriver(array $namespaces, array $managerParameters = [], $enabledParameter = false, array $aliasMap = [])
{
$locator = new Definition(SymfonyFileLocator::class, [$namespaces, '.orm.xml']);
$driver = new Definition(XmlDriver::class, [$locator]);
return new DoctrineOrmMappingsPass($driver, $namespaces, $managerParameters, $enabledParameter, $aliasMap);
}
/**
* @param string[] $namespaces Hashmap of directory path to namespace
* @param string[] $managerParameters List of parameters that could which object manager name
* your bundle uses. This compiler pass will automatically
* append the parameter name for the default entity manager
* to this list.
* @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[] $aliasMap Map of alias to namespace.
*
* @return self
*/
public static function createYamlMappingDriver(array $namespaces, array $managerParameters = [], $enabledParameter = false, array $aliasMap = [])
{
$locator = new Definition(SymfonyFileLocator::class, [$namespaces, '.orm.yml']);
$driver = new Definition(YamlDriver::class, [$locator]);
return new DoctrineOrmMappingsPass($driver, $namespaces, $managerParameters, $enabledParameter, $aliasMap);
}
/**
* @param string[] $namespaces Hashmap of directory path to namespace
* @param string[] $managerParameters List of parameters that could which object manager name
* your bundle uses. This compiler pass will automatically
* append the parameter name for the default entity manager
* to this list.
* @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[] $aliasMap Map of alias to namespace.
*
* @return self
*/
public static function createPhpMappingDriver(array $namespaces, array $managerParameters = [], $enabledParameter = false, array $aliasMap = [])
{
$locator = new Definition(SymfonyFileLocator::class, [$namespaces, '.php']);
$driver = new Definition(PHPDriver::class, [$locator]);
return new DoctrineOrmMappingsPass($driver, $namespaces, $managerParameters, $enabledParameter, $aliasMap);
}
/**
* @param string[] $namespaces List of namespaces that are handled with annotation mapping
* @param string[] $directories List of directories to look for annotated classes
* @param string[] $managerParameters List of parameters that could which object manager name
* your bundle uses. This compiler pass will automatically
* append the parameter name for the default entity manager
* to this list.
* @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[] $aliasMap Map of alias to namespace.
*
* @return self
*/
public static function createAnnotationMappingDriver(array $namespaces, array $directories, array $managerParameters = [], $enabledParameter = false, array $aliasMap = [])
{
$reader = new Reference('annotation_reader');
$driver = new Definition(AnnotationDriver::class, [$reader, $directories]);
return new DoctrineOrmMappingsPass($driver, $namespaces, $managerParameters, $enabledParameter, $aliasMap);
}
/**
* @param string[] $namespaces List of namespaces that are handled with annotation mapping
* @param string[] $directories List of directories to look for annotated classes
* @param string[] $managerParameters List of parameters that could which object manager name
* your bundle uses. This compiler pass will automatically
* append the parameter name for the default entity manager
* to this list.
* @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[] $aliasMap Map of alias to namespace.
*
* @return self
*/
public static function createAttributeMappingDriver(array $namespaces, array $directories, array $managerParameters = [], $enabledParameter = false, array $aliasMap = [])
{
$driver = new Definition(AttributeDriver::class, [$directories]);
return new DoctrineOrmMappingsPass($driver, $namespaces, $managerParameters, $enabledParameter, $aliasMap);
}
/**
* @param string[] $namespaces List of namespaces that are handled with static php mapping
* @param string[] $directories List of directories to look for static php mapping files
* @param string[] $managerParameters List of parameters that could which object manager name
* your bundle uses. This compiler pass will automatically
* append the parameter name for the default entity manager
* to this list.
* @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[] $aliasMap Map of alias to namespace.
*
* @return self
*/
public static function createStaticPhpMappingDriver(array $namespaces, array $directories, array $managerParameters = [], $enabledParameter = false, array $aliasMap = [])
{
$driver = new Definition(StaticPHPDriver::class, [$directories]);
return new DoctrineOrmMappingsPass($driver, $namespaces, $managerParameters, $enabledParameter, $aliasMap);
}
}

View File

@@ -0,0 +1,151 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler;
use Doctrine\Bundle\DoctrineBundle\Mapping\ContainerEntityListenerResolver;
use Doctrine\Bundle\DoctrineBundle\Mapping\EntityListenerServiceResolver;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use function is_a;
use function method_exists;
use function sprintf;
use function substr;
/**
* Class for Symfony bundles to register entity listeners
*/
class EntityListenerPass implements CompilerPassInterface
{
use PriorityTaggedServiceTrait;
/**
* {@inheritDoc}
*/
public function process(ContainerBuilder $container)
{
$resolvers = $this->findAndSortTaggedServices('doctrine.orm.entity_listener', $container);
$lazyServiceReferencesByResolver = [];
foreach ($resolvers as $reference) {
$id = $reference->__toString();
foreach ($container->getDefinition($id)->getTag('doctrine.orm.entity_listener') as $attributes) {
$name = $attributes['entity_manager'] ?? $container->getParameter('doctrine.default_entity_manager');
$entityManager = sprintf('doctrine.orm.%s_entity_manager', $name);
if (! $container->hasDefinition($entityManager)) {
continue;
}
$resolverId = sprintf('doctrine.orm.%s_entity_listener_resolver', $name);
if (! $container->has($resolverId)) {
continue;
}
$resolver = $container->findDefinition($resolverId);
$resolver->setPublic(true);
if (isset($attributes['entity']) && isset($attributes['event'])) {
$this->attachToListener($container, $name, $this->getConcreteDefinitionClass($container->findDefinition($id), $container, $id), $attributes);
}
$resolverClass = $this->getResolverClass($resolver, $container, $resolverId);
$resolverSupportsLazyListeners = is_a($resolverClass, EntityListenerServiceResolver::class, true);
$lazyByAttribute = isset($attributes['lazy']) && $attributes['lazy'];
if ($lazyByAttribute && ! $resolverSupportsLazyListeners) {
throw new InvalidArgumentException(sprintf(
'Lazy-loaded entity listeners can only be resolved by a resolver implementing %s.',
EntityListenerServiceResolver::class
));
}
if (! isset($attributes['lazy']) && $resolverSupportsLazyListeners || $lazyByAttribute) {
$listener = $container->findDefinition($id);
$resolver->addMethodCall('registerService', [$this->getConcreteDefinitionClass($listener, $container, $id), $id]);
// if the resolver uses the default class we will use a service locator for all listeners
if ($resolverClass === ContainerEntityListenerResolver::class) {
if (! isset($lazyServiceReferencesByResolver[$resolverId])) {
$lazyServiceReferencesByResolver[$resolverId] = [];
}
$lazyServiceReferencesByResolver[$resolverId][$id] = new Reference($id);
} else {
$listener->setPublic(true);
}
} else {
$resolver->addMethodCall('register', [new Reference($id)]);
}
}
}
foreach ($lazyServiceReferencesByResolver as $resolverId => $listenerReferences) {
$container->findDefinition($resolverId)->setArgument(0, ServiceLocatorTagPass::register($container, $listenerReferences));
}
}
/** @param array{entity: class-string, event: string} $attributes */
private function attachToListener(ContainerBuilder $container, string $name, string $class, array $attributes): void
{
$listenerId = sprintf('doctrine.orm.%s_listeners.attach_entity_listeners', $name);
if (! $container->has($listenerId)) {
return;
}
$args = [
$attributes['entity'],
$class,
$attributes['event'],
];
if (isset($attributes['method'])) {
$args[] = $attributes['method'];
} elseif (! method_exists($class, $attributes['event']) && method_exists($class, '__invoke')) {
$args[] = '__invoke';
}
$container->findDefinition($listenerId)->addMethodCall('addEntityListener', $args);
}
private function getResolverClass(Definition $resolver, ContainerBuilder $container, string $id): string
{
$resolverClass = $this->getConcreteDefinitionClass($resolver, $container, $id);
if (substr($resolverClass, 0, 1) === '%') {
// resolve container parameter first
$resolverClass = $container->getParameterBag()->resolveValue($resolverClass);
}
return $resolverClass;
}
private function getConcreteDefinitionClass(Definition $definition, ContainerBuilder $container, string $id): string
{
$class = $definition->getClass();
if ($class) {
return $class;
}
while ($definition instanceof ChildDefinition) {
$definition = $container->findDefinition($definition->getParent());
$class = $definition->getClass();
if ($class) {
return $class;
}
}
throw new InvalidArgumentException(sprintf('The service "%s" must define its class.', $id));
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler;
use Doctrine\Bundle\DoctrineBundle\Mapping\ClassMetadataFactory;
use Doctrine\Bundle\DoctrineBundle\Mapping\MappingDriver;
use Doctrine\ORM\Mapping\ClassMetadataFactory as ORMClassMetadataFactory;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use function array_combine;
use function array_keys;
use function array_map;
use function sprintf;
final class IdGeneratorPass implements CompilerPassInterface
{
public const ID_GENERATOR_TAG = 'doctrine.id_generator';
public const CONFIGURATION_TAG = 'doctrine.orm.configuration';
public function process(ContainerBuilder $container): void
{
$generatorIds = array_keys($container->findTaggedServiceIds(self::ID_GENERATOR_TAG));
// when ORM is not enabled
if (! $container->hasDefinition('doctrine.orm.configuration') || ! $generatorIds) {
return;
}
$generatorRefs = array_map(static function ($id) {
return new Reference($id);
}, $generatorIds);
$ref = ServiceLocatorTagPass::register($container, array_combine($generatorIds, $generatorRefs));
$container->setAlias('doctrine.id_generator_locator', new Alias((string) $ref, false));
foreach ($container->findTaggedServiceIds(self::CONFIGURATION_TAG) as $id => $tags) {
$configurationDef = $container->getDefinition($id);
$methodCalls = $configurationDef->getMethodCalls();
$metadataDriverImpl = null;
foreach ($methodCalls as $i => [$method, $arguments]) {
if ($method === 'setMetadataDriverImpl') {
$metadataDriverImpl = (string) $arguments[0];
}
if ($method !== 'setClassMetadataFactoryName') {
continue;
}
if ($arguments[0] !== ORMClassMetadataFactory::class && $arguments[0] !== ClassMetadataFactory::class) {
$class = $container->getReflectionClass($arguments[0]);
if ($class && $class->isSubclassOf(ClassMetadataFactory::class)) {
break;
}
continue 2;
}
$methodCalls[$i] = ['setClassMetadataFactoryName', [ClassMetadataFactory::class]];
}
if ($metadataDriverImpl === null) {
continue;
}
$configurationDef->setMethodCalls($methodCalls);
$container->register('.' . $metadataDriverImpl, MappingDriver::class)
->setDecoratedService($metadataDriverImpl)
->setArguments([
new Reference(sprintf('.%s.inner', $metadataDriverImpl)),
new Reference('doctrine.id_generator_locator'),
]);
}
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler;
use Doctrine\Bundle\DoctrineBundle\Middleware\ConnectionNameAwareInterface;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use function array_keys;
use function in_array;
use function is_subclass_of;
use function sprintf;
final class MiddlewaresPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if (! $container->hasParameter('doctrine.connections')) {
return;
}
$middlewareAbstractDefs = [];
$middlewareConnections = [];
foreach ($container->findTaggedServiceIds('doctrine.middleware') as $id => $tags) {
$middlewareAbstractDefs[$id] = $container->getDefinition($id);
// When a def has doctrine.middleware tags with connection attributes equal to connection names
// registration of this middleware is limited to the connections with these names
foreach ($tags as $tag) {
if (! isset($tag['connection'])) {
continue;
}
$middlewareConnections[$id][] = $tag['connection'];
}
}
foreach (array_keys($container->getParameter('doctrine.connections')) as $name) {
$middlewareDefs = [];
foreach ($middlewareAbstractDefs as $id => $abstractDef) {
if (isset($middlewareConnections[$id]) && ! in_array($name, $middlewareConnections[$id], true)) {
continue;
}
$middlewareDefs[] = $childDef = $container->setDefinition(
sprintf('%s.%s', $id, $name),
new ChildDefinition($id)
);
if (! is_subclass_of($abstractDef->getClass(), ConnectionNameAwareInterface::class)) {
continue;
}
$childDef->addMethodCall('setConnectionName', [$name]);
}
$container
->getDefinition(sprintf('doctrine.dbal.%s_connection.configuration', $name))
->addMethodCall('setMiddlewares', [$middlewareDefs]);
}
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/** @internal */
final class RemoveLoggingMiddlewarePass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if ($container->has('logger')) {
return;
}
$container->removeDefinition('doctrine.dbal.logging_middleware');
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler;
use Doctrine\Bundle\DoctrineBundle\Controller\ProfilerController;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/** @internal */
final class RemoveProfilerControllerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if ($container->has('twig') && $container->has('profiler')) {
return;
}
$container->removeDefinition(ProfilerController::class);
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use function array_combine;
use function array_keys;
use function array_map;
final class ServiceRepositoryCompilerPass implements CompilerPassInterface
{
public const REPOSITORY_SERVICE_TAG = 'doctrine.repository_service';
public function process(ContainerBuilder $container): void
{
// when ORM is not enabled
if (! $container->hasDefinition('doctrine.orm.container_repository_factory')) {
return;
}
$locatorDef = $container->getDefinition('doctrine.orm.container_repository_factory');
$repoServiceIds = array_keys($container->findTaggedServiceIds(self::REPOSITORY_SERVICE_TAG));
$repoReferences = array_map(static function ($id) {
return new Reference($id);
}, $repoServiceIds);
$ref = ServiceLocatorTagPass::register($container, array_combine($repoServiceIds, $repoReferences));
$locatorDef->replaceArgument(0, $ref);
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler;
use Symfony\Component\Cache\Adapter\DoctrineDbalAdapter;
use Symfony\Component\Cache\Adapter\PdoAdapter;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
use Symfony\Component\Lock\Store\DoctrineDbalStore;
use Symfony\Component\Lock\Store\PdoStore;
use Symfony\Component\Messenger\Bridge\Doctrine\Transport\Connection;
use Symfony\Component\Messenger\Transport\Doctrine\Connection as LegacyConnection;
use function array_keys;
/**
* Blacklist tables used by well-known Symfony classes.
*
* @deprecated Implement your own include/exclude mechanism
*/
class WellKnownSchemaFilterPass implements CompilerPassInterface
{
/**
* {@inheritDoc}
*/
public function process(ContainerBuilder $container)
{
$blacklist = [];
foreach ($container->getDefinitions() as $definition) {
if ($definition->isAbstract() || $definition->isSynthetic()) {
continue;
}
switch ($definition->getClass()) {
case DoctrineDbalAdapter::class:
case PdoAdapter::class:
$blacklist[] = $definition->getArguments()[3]['db_table'] ?? 'cache_items';
break;
case PdoSessionHandler::class:
$blacklist[] = $definition->getArguments()[1]['db_table'] ?? 'sessions';
break;
case DoctrineDbalStore::class:
case PdoStore::class:
$blacklist[] = $definition->getArguments()[1]['db_table'] ?? 'lock_keys';
break;
case LegacyConnection::class:
case Connection::class:
$blacklist[] = $definition->getArguments()[0]['table_name'] ?? 'messenger_messages';
break;
}
}
if (! $blacklist) {
return;
}
$definition = $container->getDefinition('doctrine.dbal.well_known_schema_asset_filter');
$definition->replaceArgument(0, $blacklist);
foreach (array_keys($container->getParameter('doctrine.connections')) as $name) {
$definition->addTag('doctrine.dbal.schema_filter', ['connection' => $name]);
}
}
}

View File

@@ -0,0 +1,803 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection;
use Doctrine\Common\Proxy\AbstractProxyFactory;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Mapping\ClassMetadataFactory;
use ReflectionClass;
use Symfony\Component\Config\Definition\BaseNode;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use function array_intersect_key;
use function array_key_exists;
use function array_keys;
use function array_pop;
use function assert;
use function class_exists;
use function constant;
use function count;
use function defined;
use function implode;
use function in_array;
use function is_array;
use function is_bool;
use function is_int;
use function is_string;
use function key;
use function method_exists;
use function reset;
use function sprintf;
use function strlen;
use function strpos;
use function strtoupper;
use function substr;
use function trigger_deprecation;
/**
* This class contains the configuration information for the bundle
*
* This information is solely responsible for how the different configuration
* sections are normalized, and merged.
*/
class Configuration implements ConfigurationInterface
{
/** @var bool */
private $debug;
/** @param bool $debug Whether to use the debug mode */
public function __construct(bool $debug)
{
$this->debug = $debug;
}
public function getConfigTreeBuilder(): TreeBuilder
{
$treeBuilder = new TreeBuilder('doctrine');
$rootNode = $treeBuilder->getRootNode();
$this->addDbalSection($rootNode);
$this->addOrmSection($rootNode);
return $treeBuilder;
}
/**
* Add DBAL section to configuration tree
*/
private function addDbalSection(ArrayNodeDefinition $node): void
{
$node
->children()
->arrayNode('dbal')
->beforeNormalization()
->ifTrue(static function ($v) {
return is_array($v) && ! array_key_exists('connections', $v) && ! array_key_exists('connection', $v);
})
->then(static function ($v) {
// Key that should not be rewritten to the connection config
$excludedKeys = ['default_connection' => true, 'types' => true, 'type' => true];
$connection = [];
foreach ($v as $key => $value) {
if (isset($excludedKeys[$key])) {
continue;
}
$connection[$key] = $v[$key];
unset($v[$key]);
}
$v['default_connection'] = isset($v['default_connection']) ? (string) $v['default_connection'] : 'default';
$v['connections'] = [$v['default_connection'] => $connection];
return $v;
})
->end()
->children()
->scalarNode('default_connection')->end()
->end()
->fixXmlConfig('type')
->children()
->arrayNode('types')
->useAttributeAsKey('name')
->prototype('array')
->beforeNormalization()
->ifString()
->then(static function ($v) {
return ['class' => $v];
})
->end()
->children()
->scalarNode('class')->isRequired()->end()
->booleanNode('commented')
->setDeprecated(
...$this->getDeprecationMsg('The doctrine-bundle type commenting features were removed; the corresponding config parameter was deprecated in 2.0 and will be dropped in 3.0.', '2.0')
)
->end()
->end()
->end()
->end()
->end()
->fixXmlConfig('connection')
->append($this->getDbalConnectionsNode())
->end();
}
/**
* Return the dbal connections node
*/
private function getDbalConnectionsNode(): ArrayNodeDefinition
{
$treeBuilder = new TreeBuilder('connections');
$node = $treeBuilder->getRootNode();
$connectionNode = $node
->requiresAtLeastOneElement()
->useAttributeAsKey('name')
->prototype('array');
assert($connectionNode instanceof ArrayNodeDefinition);
$this->configureDbalDriverNode($connectionNode);
$collationKey = defined('Doctrine\DBAL\Connection::PARAM_ASCII_STR_ARRAY')
? 'collate'
: 'collation';
$connectionNode
->fixXmlConfig('option')
->fixXmlConfig('mapping_type')
->fixXmlConfig('slave')
->fixXmlConfig('replica')
->fixXmlConfig('shard')
->fixXmlConfig('default_table_option')
->children()
->scalarNode('driver')->defaultValue('pdo_mysql')->end()
->scalarNode('platform_service')->end()
->booleanNode('auto_commit')->end()
->scalarNode('schema_filter')->end()
->booleanNode('logging')->defaultValue($this->debug)->end()
->booleanNode('profiling')->defaultValue($this->debug)->end()
->booleanNode('profiling_collect_backtrace')
->defaultValue(false)
->info('Enables collecting backtraces when profiling is enabled')
->end()
->booleanNode('profiling_collect_schema_errors')
->defaultValue(true)
->info('Enables collecting schema errors when profiling is enabled')
->end()
->scalarNode('server_version')->end()
->scalarNode('driver_class')->end()
->scalarNode('wrapper_class')->end()
->scalarNode('shard_manager_class')->end()
->scalarNode('shard_choser')->end()
->scalarNode('shard_choser_service')->end()
->booleanNode('keep_slave')
->setDeprecated(
...$this->getDeprecationMsg('The "keep_slave" configuration key is deprecated since doctrine-bundle 2.2. Use the "keep_replica" configuration key instead.', '2.2')
)
->end()
->booleanNode('keep_replica')->end()
->arrayNode('options')
->useAttributeAsKey('key')
->prototype('variable')->end()
->end()
->arrayNode('mapping_types')
->useAttributeAsKey('name')
->prototype('scalar')->end()
->end()
->arrayNode('default_table_options')
->info(sprintf(
"This option is used by the schema-tool and affects generated SQL. Possible keys include 'charset','%s', and 'engine'.",
$collationKey
))
->useAttributeAsKey('name')
->prototype('scalar')->end()
->end()
->end();
// dbal < 2.11
$slaveNode = $connectionNode
->children()
->arrayNode('slaves')
->setDeprecated(
...$this->getDeprecationMsg('The "slaves" configuration key will be renamed to "replicas" in doctrine-bundle 3.0. "slaves" is deprecated since doctrine-bundle 2.2.', '2.2')
)
->useAttributeAsKey('name')
->prototype('array');
$this->configureDbalDriverNode($slaveNode);
// dbal >= 2.11
$replicaNode = $connectionNode
->children()
->arrayNode('replicas')
->useAttributeAsKey('name')
->prototype('array');
$this->configureDbalDriverNode($replicaNode);
$shardNode = $connectionNode
->children()
->arrayNode('shards')
->prototype('array');
$shardNode
->children()
->integerNode('id')
->min(1)
->isRequired()
->end()
->end();
$this->configureDbalDriverNode($shardNode);
return $node;
}
/**
* Adds config keys related to params processed by the DBAL drivers
*
* These keys are available for replica configurations too.
*/
private function configureDbalDriverNode(ArrayNodeDefinition $node): void
{
$node
->validate()
->always(static function (array $values) {
if (! isset($values['url'])) {
return $values;
}
$urlConflictingOptions = ['host' => true, 'port' => true, 'user' => true, 'password' => true, 'path' => true, 'dbname' => true, 'unix_socket' => true, 'memory' => true];
$urlConflictingValues = array_keys(array_intersect_key($values, $urlConflictingOptions));
if ($urlConflictingValues) {
$tail = count($urlConflictingValues) > 1 ? sprintf('or "%s" options', array_pop($urlConflictingValues)) : 'option';
trigger_deprecation(
'doctrine/doctrine-bundle',
'2.4',
'Setting the "doctrine.dbal.%s" %s while the "url" one is defined is deprecated',
implode('", "', $urlConflictingValues),
$tail
);
}
return $values;
})
->end()
->children()
->scalarNode('url')->info('A URL with connection information; any parameter value parsed from this string will override explicitly set parameters')->end()
->scalarNode('dbname')->end()
->scalarNode('host')->info('Defaults to "localhost" at runtime.')->end()
->scalarNode('port')->info('Defaults to null at runtime.')->end()
->scalarNode('user')->info('Defaults to "root" at runtime.')->end()
->scalarNode('password')->info('Defaults to null at runtime.')->end()
->booleanNode('override_url')->setDeprecated(...$this->getDeprecationMsg('The "doctrine.dbal.override_url" configuration key is deprecated.', '2.4'))->end()
->scalarNode('dbname_suffix')->end()
->scalarNode('application_name')->end()
->scalarNode('charset')->end()
->scalarNode('path')->end()
->booleanNode('memory')->end()
->scalarNode('unix_socket')->info('The unix socket to use for MySQL')->end()
->booleanNode('persistent')->info('True to use as persistent connection for the ibm_db2 driver')->end()
->scalarNode('protocol')->info('The protocol to use for the ibm_db2 driver (default to TCPIP if omitted)')->end()
->booleanNode('service')
->info('True to use SERVICE_NAME as connection parameter instead of SID for Oracle')
->end()
->scalarNode('servicename')
->info(
'Overrules dbname parameter if given and used as SERVICE_NAME or SID connection parameter ' .
'for Oracle depending on the service parameter.'
)
->end()
->scalarNode('sessionMode')
->info('The session mode to use for the oci8 driver')
->end()
->scalarNode('server')
->info('The name of a running database server to connect to for SQL Anywhere.')
->end()
->scalarNode('default_dbname')
->info(
'Override the default database (postgres) to connect to for PostgreSQL connexion.'
)
->end()
->scalarNode('sslmode')
->info(
'Determines whether or with what priority a SSL TCP/IP connection will be negotiated with ' .
'the server for PostgreSQL.'
)
->end()
->scalarNode('sslrootcert')
->info(
'The name of a file containing SSL certificate authority (CA) certificate(s). ' .
'If the file exists, the server\'s certificate will be verified to be signed by one of these authorities.'
)
->end()
->scalarNode('sslcert')
->info(
'The path to the SSL client certificate file for PostgreSQL.'
)
->end()
->scalarNode('sslkey')
->info(
'The path to the SSL client key file for PostgreSQL.'
)
->end()
->scalarNode('sslcrl')
->info(
'The file name of the SSL certificate revocation list for PostgreSQL.'
)
->end()
->booleanNode('pooled')->info('True to use a pooled server with the oci8/pdo_oracle driver')->end()
->booleanNode('MultipleActiveResultSets')->info('Configuring MultipleActiveResultSets for the pdo_sqlsrv driver')->end()
->booleanNode('use_savepoints')->info('Use savepoints for nested transactions')->end()
->scalarNode('instancename')
->info(
'Optional parameter, complete whether to add the INSTANCE_NAME parameter in the connection.' .
' It is generally used to connect to an Oracle RAC server to select the name' .
' of a particular instance.'
)
->end()
->scalarNode('connectstring')
->info(
'Complete Easy Connect connection descriptor, see https://docs.oracle.com/database/121/NETAG/naming.htm.' .
'When using this option, you will still need to provide the user and password parameters, but the other ' .
'parameters will no longer be used. Note that when using this parameter, the getHost and getPort methods' .
' from Doctrine\DBAL\Connection will no longer function as expected.'
)
->end()
->end()
->beforeNormalization()
->ifTrue(static function ($v) {
return ! isset($v['sessionMode']) && isset($v['session_mode']);
})
->then(static function ($v) {
$v['sessionMode'] = $v['session_mode'];
unset($v['session_mode']);
return $v;
})
->end()
->beforeNormalization()
->ifTrue(static function ($v) {
return ! isset($v['MultipleActiveResultSets']) && isset($v['multiple_active_result_sets']);
})
->then(static function ($v) {
$v['MultipleActiveResultSets'] = $v['multiple_active_result_sets'];
unset($v['multiple_active_result_sets']);
return $v;
})
->end();
}
/**
* Add the ORM section to configuration tree
*/
private function addOrmSection(ArrayNodeDefinition $node): void
{
$node
->children()
->arrayNode('orm')
->beforeNormalization()
->ifTrue(static function ($v) {
if (! empty($v) && ! class_exists(EntityManager::class)) {
throw new LogicException('The doctrine/orm package is required when the doctrine.orm config is set.');
}
return $v === null || (is_array($v) && ! array_key_exists('entity_managers', $v) && ! array_key_exists('entity_manager', $v));
})
->then(static function ($v) {
$v = (array) $v;
// Key that should not be rewritten to the connection config
$excludedKeys = [
'default_entity_manager' => true,
'auto_generate_proxy_classes' => true,
'proxy_dir' => true,
'proxy_namespace' => true,
'resolve_target_entities' => true,
'resolve_target_entity' => true,
];
$entityManager = [];
foreach ($v as $key => $value) {
if (isset($excludedKeys[$key])) {
continue;
}
$entityManager[$key] = $v[$key];
unset($v[$key]);
}
$v['default_entity_manager'] = isset($v['default_entity_manager']) ? (string) $v['default_entity_manager'] : 'default';
$v['entity_managers'] = [$v['default_entity_manager'] => $entityManager];
return $v;
})
->end()
->children()
->scalarNode('default_entity_manager')->end()
->scalarNode('auto_generate_proxy_classes')->defaultValue(false)
->info('Auto generate mode possible values are: "NEVER", "ALWAYS", "FILE_NOT_EXISTS", "EVAL", "FILE_NOT_EXISTS_OR_CHANGED"')
->validate()
->ifTrue(function ($v) {
$generationModes = $this->getAutoGenerateModes();
if (is_int($v) && in_array($v, $generationModes['values']/*array(0, 1, 2, 3)*/)) {
return false;
}
if (is_bool($v)) {
return false;
}
if (is_string($v)) {
if (in_array(strtoupper($v), $generationModes['names']/*array('NEVER', 'ALWAYS', 'FILE_NOT_EXISTS', 'EVAL', 'FILE_NOT_EXISTS_OR_CHANGED')*/)) {
return false;
}
}
return true;
})
->thenInvalid('Invalid auto generate mode value %s')
->end()
->validate()
->ifString()
->then(static function ($v) {
return constant('Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_' . strtoupper($v));
})
->end()
->end()
->scalarNode('proxy_dir')->defaultValue('%kernel.cache_dir%/doctrine/orm/Proxies')->end()
->scalarNode('proxy_namespace')->defaultValue('Proxies')->end()
->end()
->fixXmlConfig('entity_manager')
->append($this->getOrmEntityManagersNode())
->fixXmlConfig('resolve_target_entity', 'resolve_target_entities')
->append($this->getOrmTargetEntityResolverNode())
->end()
->end();
}
/**
* Return ORM target entity resolver node
*/
private function getOrmTargetEntityResolverNode(): NodeDefinition
{
$treeBuilder = new TreeBuilder('resolve_target_entities');
$node = $treeBuilder->getRootNode();
$node
->useAttributeAsKey('interface')
->prototype('scalar')
->cannotBeEmpty()
->end();
return $node;
}
/**
* Return ORM entity listener node
*/
private function getOrmEntityListenersNode(): NodeDefinition
{
$treeBuilder = new TreeBuilder('entity_listeners');
$node = $treeBuilder->getRootNode();
$normalizer = static function ($mappings) {
$entities = [];
foreach ($mappings as $entityClass => $mapping) {
$listeners = [];
foreach ($mapping as $listenerClass => $listenerEvent) {
$events = [];
foreach ($listenerEvent as $eventType => $eventMapping) {
if ($eventMapping === null) {
$eventMapping = [null];
}
foreach ($eventMapping as $method) {
$events[] = [
'type' => $eventType,
'method' => $method,
];
}
}
$listeners[] = [
'class' => $listenerClass,
'event' => $events,
];
}
$entities[] = [
'class' => $entityClass,
'listener' => $listeners,
];
}
return ['entities' => $entities];
};
$node
->beforeNormalization()
// Yaml normalization
->ifTrue(static function ($v) {
return is_array(reset($v)) && is_string(key(reset($v)));
})
->then($normalizer)
->end()
->fixXmlConfig('entity', 'entities')
->children()
->arrayNode('entities')
->useAttributeAsKey('class')
->prototype('array')
->fixXmlConfig('listener')
->children()
->arrayNode('listeners')
->useAttributeAsKey('class')
->prototype('array')
->fixXmlConfig('event')
->children()
->arrayNode('events')
->prototype('array')
->children()
->scalarNode('type')->end()
->scalarNode('method')->defaultNull()->end()
->end()
->end()
->end()
->end()
->end()
->end()
->end()
->end()
->end()
->end();
return $node;
}
/**
* Return ORM entity manager node
*/
private function getOrmEntityManagersNode(): ArrayNodeDefinition
{
$treeBuilder = new TreeBuilder('entity_managers');
$node = $treeBuilder->getRootNode();
$node
->requiresAtLeastOneElement()
->useAttributeAsKey('name')
->prototype('array')
->addDefaultsIfNotSet()
->append($this->getOrmCacheDriverNode('query_cache_driver'))
->append($this->getOrmCacheDriverNode('metadata_cache_driver'))
->append($this->getOrmCacheDriverNode('result_cache_driver'))
->append($this->getOrmEntityListenersNode())
->children()
->scalarNode('connection')->end()
->scalarNode('class_metadata_factory_name')->defaultValue(ClassMetadataFactory::class)->end()
->scalarNode('default_repository_class')->defaultValue(EntityRepository::class)->end()
->scalarNode('auto_mapping')->defaultFalse()->end()
->scalarNode('naming_strategy')->defaultValue('doctrine.orm.naming_strategy.default')->end()
->scalarNode('quote_strategy')->defaultValue('doctrine.orm.quote_strategy.default')->end()
->scalarNode('entity_listener_resolver')->defaultNull()->end()
->scalarNode('repository_factory')->defaultValue('doctrine.orm.container_repository_factory')->end()
->end()
->children()
->arrayNode('second_level_cache')
->children()
->append($this->getOrmCacheDriverNode('region_cache_driver'))
->scalarNode('region_lock_lifetime')->defaultValue(60)->end()
->booleanNode('log_enabled')->defaultValue($this->debug)->end()
->scalarNode('region_lifetime')->defaultValue(3600)->end()
->booleanNode('enabled')->defaultValue(true)->end()
->scalarNode('factory')->end()
->end()
->fixXmlConfig('region')
->children()
->arrayNode('regions')
->useAttributeAsKey('name')
->prototype('array')
->children()
->append($this->getOrmCacheDriverNode('cache_driver'))
->scalarNode('lock_path')->defaultValue('%kernel.cache_dir%/doctrine/orm/slc/filelock')->end()
->scalarNode('lock_lifetime')->defaultValue(60)->end()
->scalarNode('type')->defaultValue('default')->end()
->scalarNode('lifetime')->defaultValue(0)->end()
->scalarNode('service')->end()
->scalarNode('name')->end()
->end()
->end()
->end()
->end()
->fixXmlConfig('logger')
->children()
->arrayNode('loggers')
->useAttributeAsKey('name')
->prototype('array')
->children()
->scalarNode('name')->end()
->scalarNode('service')->end()
->end()
->end()
->end()
->end()
->end()
->end()
->fixXmlConfig('hydrator')
->children()
->arrayNode('hydrators')
->useAttributeAsKey('name')
->prototype('scalar')->end()
->end()
->end()
->fixXmlConfig('mapping')
->children()
->arrayNode('mappings')
->useAttributeAsKey('name')
->prototype('array')
->beforeNormalization()
->ifString()
->then(static function ($v) {
return ['type' => $v];
})
->end()
->treatNullLike([])
->treatFalseLike(['mapping' => false])
->performNoDeepMerging()
->children()
->scalarNode('mapping')->defaultValue(true)->end()
->scalarNode('type')->end()
->scalarNode('dir')->end()
->scalarNode('alias')->end()
->scalarNode('prefix')->end()
->booleanNode('is_bundle')->end()
->end()
->end()
->end()
->arrayNode('dql')
->fixXmlConfig('string_function')
->fixXmlConfig('numeric_function')
->fixXmlConfig('datetime_function')
->children()
->arrayNode('string_functions')
->useAttributeAsKey('name')
->prototype('scalar')->end()
->end()
->arrayNode('numeric_functions')
->useAttributeAsKey('name')
->prototype('scalar')->end()
->end()
->arrayNode('datetime_functions')
->useAttributeAsKey('name')
->prototype('scalar')->end()
->end()
->end()
->end()
->end()
->fixXmlConfig('filter')
->children()
->arrayNode('filters')
->info('Register SQL Filters in the entity manager')
->useAttributeAsKey('name')
->prototype('array')
->beforeNormalization()
->ifString()
->then(static function ($v) {
return ['class' => $v];
})
->end()
->beforeNormalization()
// The content of the XML node is returned as the "value" key so we need to rename it
->ifTrue(static function ($v) {
return is_array($v) && isset($v['value']);
})
->then(static function ($v) {
$v['class'] = $v['value'];
unset($v['value']);
return $v;
})
->end()
->fixXmlConfig('parameter')
->children()
->scalarNode('class')->isRequired()->end()
->booleanNode('enabled')->defaultFalse()->end()
->arrayNode('parameters')
->useAttributeAsKey('name')
->prototype('variable')->end()
->end()
->end()
->end()
->end()
->end()
->end();
return $node;
}
/**
* Return a ORM cache driver node for an given entity manager
*/
private function getOrmCacheDriverNode(string $name): ArrayNodeDefinition
{
$treeBuilder = new TreeBuilder($name);
$node = $treeBuilder->getRootNode();
$node
->beforeNormalization()
->ifString()
->then(static function ($v): array {
return ['type' => $v];
})
->end()
->children()
->scalarNode('type')->defaultNull()->end()
->scalarNode('id')->end()
->scalarNode('pool')->end()
->end();
if ($name !== 'metadata_cache_driver') {
$node->addDefaultsIfNotSet();
}
return $node;
}
/**
* Find proxy auto generate modes for their names and int values
*
* @return array{names: list<string>, values: list<int>}
*/
private function getAutoGenerateModes(): array
{
$constPrefix = 'AUTOGENERATE_';
$prefixLen = strlen($constPrefix);
$refClass = new ReflectionClass(AbstractProxyFactory::class);
$constsArray = $refClass->getConstants();
$namesArray = [];
$valuesArray = [];
foreach ($constsArray as $key => $value) {
if (strpos($key, $constPrefix) !== 0) {
continue;
}
$namesArray[] = substr($key, $prefixLen);
$valuesArray[] = (int) $value;
}
return [
'names' => $namesArray,
'values' => $valuesArray,
];
}
/**
* Returns the correct deprecation param's as an array for setDeprecated.
*
* Symfony/Config v5.1 introduces a deprecation notice when calling
* setDeprecation() with less than 3 args and the getDeprecation method was
* introduced at the same time. By checking if getDeprecation() exists,
* we can determine the correct param count to use when calling setDeprecated.
*
* @return list<string>|array{0:string, 1: numeric-string, string}
*/
private function getDeprecationMsg(string $message, string $version): array
{
if (method_exists(BaseNode::class, 'getDeprecation')) {
return [
'doctrine/doctrine-bundle',
$version,
$message,
];
}
return [$message];
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,171 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\CacheCompatibilityPass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\CacheSchemaSubscriberPass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\DbalSchemaFilterPass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\EntityListenerPass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\IdGeneratorPass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\MiddlewaresPass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\RemoveLoggingMiddlewarePass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\RemoveProfilerControllerPass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\ServiceRepositoryCompilerPass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\WellKnownSchemaFilterPass;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\DBAL\Driver\Middleware;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Proxy\Autoloader;
use Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass\DoctrineValidationPass;
use Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass\RegisterEventListenersAndSubscribersPass;
use Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass\RegisterUidTypePass;
use Symfony\Bridge\Doctrine\DependencyInjection\Security\UserProvider\EntityFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension;
use Symfony\Component\Console\Application;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use function assert;
use function class_exists;
use function clearstatcache;
use function interface_exists;
use function spl_autoload_unregister;
class DoctrineBundle extends Bundle
{
/** @var callable|null */
private $autoloader;
/**
* {@inheritDoc}
*/
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new RegisterEventListenersAndSubscribersPass('doctrine.connections', 'doctrine.dbal.%s_connection.event_manager', 'doctrine'), PassConfig::TYPE_BEFORE_OPTIMIZATION);
if ($container->hasExtension('security')) {
$security = $container->getExtension('security');
if ($security instanceof SecurityExtension) {
$security->addUserProviderFactory(new EntityFactory('entity', 'doctrine.orm.security.user.provider'));
}
}
$container->addCompilerPass(new CacheCompatibilityPass());
$container->addCompilerPass(new DoctrineValidationPass('orm'));
$container->addCompilerPass(new EntityListenerPass());
$container->addCompilerPass(new ServiceRepositoryCompilerPass());
$container->addCompilerPass(new IdGeneratorPass());
$container->addCompilerPass(new WellKnownSchemaFilterPass());
$container->addCompilerPass(new DbalSchemaFilterPass());
$container->addCompilerPass(new CacheSchemaSubscriberPass(), PassConfig::TYPE_BEFORE_REMOVING, -10);
$container->addCompilerPass(new RemoveProfilerControllerPass());
/** @psalm-suppress UndefinedClass */
if (interface_exists(Middleware::class)) {
$container->addCompilerPass(new RemoveLoggingMiddlewarePass());
$container->addCompilerPass(new MiddlewaresPass());
}
if (! class_exists(RegisterUidTypePass::class)) {
return;
}
$container->addCompilerPass(new RegisterUidTypePass());
}
/**
* {@inheritDoc}
*/
public function boot()
{
// Register an autoloader for proxies to avoid issues when unserializing them
// when the ORM is used.
if (! $this->container->hasParameter('doctrine.orm.proxy_namespace')) {
return;
}
$namespace = (string) $this->container->getParameter('doctrine.orm.proxy_namespace');
$dir = (string) $this->container->getParameter('doctrine.orm.proxy_dir');
$proxyGenerator = null;
if ($this->container->getParameter('doctrine.orm.auto_generate_proxy_classes')) {
// See https://github.com/symfony/symfony/pull/3419 for usage of references
$container = &$this->container;
$proxyGenerator = static function ($proxyDir, $proxyNamespace, $class) use (&$container): void {
$originalClassName = ClassUtils::getRealClass($class);
$registry = $container->get('doctrine');
assert($registry instanceof Registry);
foreach ($registry->getManagers() as $em) {
assert($em instanceof EntityManagerInterface);
if (! $em->getConfiguration()->getAutoGenerateProxyClasses()) {
continue;
}
$metadataFactory = $em->getMetadataFactory();
if ($metadataFactory->isTransient($originalClassName)) {
continue;
}
$classMetadata = $metadataFactory->getMetadataFor($originalClassName);
$em->getProxyFactory()->generateProxyClasses([$classMetadata]);
clearstatcache(true, Autoloader::resolveFile($proxyDir, $proxyNamespace, $class));
break;
}
};
}
$this->autoloader = Autoloader::register($dir, $namespace, $proxyGenerator);
}
/**
* {@inheritDoc}
*/
public function shutdown()
{
if ($this->autoloader !== null) {
spl_autoload_unregister($this->autoloader);
$this->autoloader = null;
}
// Clear all entity managers to clear references to entities for GC
if ($this->container->hasParameter('doctrine.entity_managers')) {
foreach ($this->container->getParameter('doctrine.entity_managers') as $id) {
if (! $this->container->initialized($id)) {
continue;
}
$this->container->get($id)->clear();
}
}
// Close all connections to avoid reaching too many connections in the process when booting again later (tests)
if (! $this->container->hasParameter('doctrine.connections')) {
return;
}
foreach ($this->container->getParameter('doctrine.connections') as $id) {
if (! $this->container->initialized($id)) {
continue;
}
$this->container->get($id)->close();
}
}
/**
* {@inheritDoc}
*/
public function registerCommands(Application $application)
{
}
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\EventSubscriber;
use Doctrine\Common\EventSubscriber;
interface EventSubscriberInterface extends EventSubscriber
{
}

13
vendor/doctrine/doctrine-bundle/LICENSE vendored Normal file
View File

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

View File

@@ -0,0 +1,71 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query\Filter\SQLFilter;
/**
* Configurator for an EntityManager
*/
class ManagerConfigurator
{
/** @var string[] */
private $enabledFilters = [];
/** @var array<string,array<string,string>> */
private $filtersParameters = [];
/**
* @param string[] $enabledFilters
* @param array<string,array<string,string>> $filtersParameters
*/
public function __construct(array $enabledFilters, array $filtersParameters)
{
$this->enabledFilters = $enabledFilters;
$this->filtersParameters = $filtersParameters;
}
/**
* Create a connection by name.
*/
public function configure(EntityManagerInterface $entityManager)
{
$this->enableFilters($entityManager);
}
/**
* Enables filters for a given entity manager
*/
private function enableFilters(EntityManagerInterface $entityManager): void
{
if (empty($this->enabledFilters)) {
return;
}
$filterCollection = $entityManager->getFilters();
foreach ($this->enabledFilters as $filter) {
$filterObject = $filterCollection->enable($filter);
if ($filterObject === null) {
continue;
}
$this->setFilterParameters($filter, $filterObject);
}
}
/**
* Sets default parameters for a given filter
*/
private function setFilterParameters(string $name, SQLFilter $filter): void
{
if (empty($this->filtersParameters[$name])) {
return;
}
$parameters = $this->filtersParameters[$name];
foreach ($parameters as $paramName => $paramValue) {
$filter->setParameter($paramName, $paramValue);
}
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Mapping;
use Doctrine\ORM\Mapping\ClassMetadata;
class ClassMetadataCollection
{
/** @var string */
private $path;
/** @var string */
private $namespace;
/** @var ClassMetadata[] */
private $metadata;
/** @param ClassMetadata[] $metadata */
public function __construct(array $metadata)
{
$this->metadata = $metadata;
}
/** @return ClassMetadata[] */
public function getMetadata()
{
return $this->metadata;
}
/** @param string $path */
public function setPath($path)
{
$this->path = $path;
}
/** @return string */
public function getPath()
{
return $this->path;
}
/** @param string $namespace */
public function setNamespace($namespace)
{
$this->namespace = $namespace;
}
/** @return string */
public function getNamespace()
{
return $this->namespace;
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Mapping;
use Doctrine\ORM\Id\AbstractIdGenerator;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataFactory as BaseClassMetadataFactory;
use function assert;
class ClassMetadataFactory extends BaseClassMetadataFactory
{
/**
* {@inheritDoc}
*/
protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonSuperclassParents): void
{
parent::doLoadMetadata($class, $parent, $rootEntityFound, $nonSuperclassParents);
$customGeneratorDefinition = $class->customGeneratorDefinition;
if (! isset($customGeneratorDefinition['instance'])) {
return;
}
assert($customGeneratorDefinition['instance'] instanceof AbstractIdGenerator);
$class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_CUSTOM);
$class->setIdGenerator($customGeneratorDefinition['instance']);
unset($customGeneratorDefinition['instance']);
$class->setCustomGeneratorDefinition($customGeneratorDefinition);
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Mapping;
use InvalidArgumentException;
use Psr\Container\ContainerInterface;
use RuntimeException;
use function get_class;
use function gettype;
use function is_object;
use function sprintf;
use function trim;
/** @final */
class ContainerEntityListenerResolver implements EntityListenerServiceResolver
{
/** @var ContainerInterface */
private $container;
/** @var object[] Map to store entity listener instances. */
private $instances = [];
/** @var string[] Map to store registered service ids */
private $serviceIds = [];
/** @param ContainerInterface $container a service locator for listeners */
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* {@inheritdoc}
*/
public function clear($className = null)
{
if ($className === null) {
$this->instances = [];
return;
}
$className = $this->normalizeClassName($className);
unset($this->instances[$className]);
}
/**
* {@inheritdoc}
*/
public function register($object)
{
if (! is_object($object)) {
throw new InvalidArgumentException(sprintf('An object was expected, but got "%s".', gettype($object)));
}
$className = $this->normalizeClassName(get_class($object));
$this->instances[$className] = $object;
}
/**
* {@inheritdoc}
*/
public function registerService($className, $serviceId)
{
$this->serviceIds[$this->normalizeClassName($className)] = $serviceId;
}
/**
* {@inheritdoc}
*/
public function resolve($className)
{
$className = $this->normalizeClassName($className);
if (! isset($this->instances[$className])) {
if (isset($this->serviceIds[$className])) {
$this->instances[$className] = $this->resolveService($this->serviceIds[$className]);
} else {
$this->instances[$className] = new $className();
}
}
return $this->instances[$className];
}
/** @return object */
private function resolveService(string $serviceId)
{
if (! $this->container->has($serviceId)) {
throw new RuntimeException(sprintf('There is no service named "%s"', $serviceId));
}
return $this->container->get($serviceId);
}
private function normalizeClassName(string $className): string
{
return trim($className, '\\');
}
}

View File

@@ -0,0 +1,193 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Mapping;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\MappingException;
use Doctrine\ORM\Tools\DisconnectedClassMetadataFactory;
use Doctrine\Persistence\ManagerRegistry;
use ReflectionClass;
use RuntimeException;
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
use function array_pop;
use function class_exists;
use function dirname;
use function explode;
use function implode;
use function sprintf;
use function str_replace;
use function strpos;
/**
* This class provides methods to access Doctrine entity class metadata for a
* given bundle, namespace or entity class, for generation purposes
*/
class DisconnectedMetadataFactory
{
/** @var ManagerRegistry */
private $registry;
/** @param ManagerRegistry $registry A ManagerRegistry instance */
public function __construct(ManagerRegistry $registry)
{
$this->registry = $registry;
}
/**
* Gets the metadata of all classes of a bundle.
*
* @param BundleInterface $bundle A BundleInterface instance
*
* @return ClassMetadataCollection A ClassMetadataCollection instance
*
* @throws RuntimeException When bundle does not contain mapped entities.
*/
public function getBundleMetadata(BundleInterface $bundle)
{
$namespace = $bundle->getNamespace();
$metadata = $this->getMetadataForNamespace($namespace);
if (! $metadata->getMetadata()) {
throw new RuntimeException(sprintf('Bundle "%s" does not contain any mapped entities.', $bundle->getName()));
}
$path = $this->getBasePathForClass($bundle->getName(), $bundle->getNamespace(), $bundle->getPath());
$metadata->setPath($path);
$metadata->setNamespace($bundle->getNamespace());
return $metadata;
}
/**
* Gets the metadata of a class.
*
* @param string $class A class name
* @param string $path The path where the class is stored (if known)
*
* @return ClassMetadataCollection A ClassMetadataCollection instance
*
* @throws MappingException When class is not valid entity or mapped superclass.
*/
public function getClassMetadata($class, $path = null)
{
$metadata = $this->getMetadataForClass($class);
if (! $metadata->getMetadata()) {
throw MappingException::classIsNotAValidEntityOrMappedSuperClass($class);
}
$this->findNamespaceAndPathForMetadata($metadata, $path);
return $metadata;
}
/**
* Gets the metadata of all classes of a namespace.
*
* @param string $namespace A namespace name
* @param string $path The path where the class is stored (if known)
*
* @return ClassMetadataCollection A ClassMetadataCollection instance
*
* @throws RuntimeException When namespace not contain mapped entities.
*/
public function getNamespaceMetadata($namespace, $path = null)
{
$metadata = $this->getMetadataForNamespace($namespace);
if (! $metadata->getMetadata()) {
throw new RuntimeException(sprintf('Namespace "%s" does not contain any mapped entities.', $namespace));
}
$this->findNamespaceAndPathForMetadata($metadata, $path);
return $metadata;
}
/**
* Find and configure path and namespace for the metadata collection.
*
* @param string|null $path
*
* @throws RuntimeException When unable to determine the path.
*/
public function findNamespaceAndPathForMetadata(ClassMetadataCollection $metadata, $path = null)
{
$all = $metadata->getMetadata();
if (class_exists($all[0]->name)) {
$r = new ReflectionClass($all[0]->name);
$path = $this->getBasePathForClass($r->getName(), $r->getNamespaceName(), dirname($r->getFilename()));
$ns = $r->getNamespaceName();
} elseif ($path) {
// Get namespace by removing the last component of the FQCN
$nsParts = explode('\\', $all[0]->name);
array_pop($nsParts);
$ns = implode('\\', $nsParts);
} else {
throw new RuntimeException(sprintf('Unable to determine where to save the "%s" class (use the --path option).', $all[0]->name));
}
$metadata->setPath($path);
$metadata->setNamespace($ns);
}
/**
* Get a base path for a class
*
* @throws RuntimeException When base path not found.
*/
private function getBasePathForClass(string $name, string $namespace, string $path): string
{
$namespace = str_replace('\\', '/', $namespace);
$search = str_replace('\\', '/', $path);
$destination = str_replace('/' . $namespace, '', $search, $c);
if ($c !== 1) {
throw new RuntimeException(sprintf('Can\'t find base path for "%s" (path: "%s", destination: "%s").', $name, $path, $destination));
}
return $destination;
}
private function getMetadataForNamespace(string $namespace): ClassMetadataCollection
{
$metadata = [];
foreach ($this->getAllMetadata() as $m) {
if (strpos($m->name, $namespace) !== 0) {
continue;
}
$metadata[] = $m;
}
return new ClassMetadataCollection($metadata);
}
private function getMetadataForClass(string $entity): ClassMetadataCollection
{
foreach ($this->registry->getManagers() as $em) {
$cmf = new DisconnectedClassMetadataFactory();
$cmf->setEntityManager($em);
if (! $cmf->isTransient($entity)) {
return new ClassMetadataCollection([$cmf->getMetadataFor($entity)]);
}
}
return new ClassMetadataCollection([]);
}
/** @return ClassMetadata[] */
private function getAllMetadata(): array
{
$metadata = [];
foreach ($this->registry->getManagers() as $em) {
$cmf = new DisconnectedClassMetadataFactory();
$cmf->setEntityManager($em);
foreach ($cmf->getAllMetadata() as $m) {
$metadata[] = $m;
}
}
return $metadata;
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Mapping;
use Doctrine\ORM\Mapping\EntityListenerResolver;
interface EntityListenerServiceResolver extends EntityListenerResolver
{
/**
* @param string $className
* @param string $serviceId
*/
// phpcs:ignore
public function registerService($className, $serviceId);
}

View File

@@ -0,0 +1,68 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Mapping;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\Persistence\Mapping\ClassMetadata;
use Doctrine\Persistence\Mapping\Driver\MappingDriver as MappingDriverInterface;
use Psr\Container\ContainerInterface;
class MappingDriver implements MappingDriverInterface
{
/** @var MappingDriverInterface */
private $driver;
/** @var ContainerInterface */
private $idGeneratorLocator;
public function __construct(MappingDriverInterface $driver, ContainerInterface $idGeneratorLocator)
{
$this->driver = $driver;
$this->idGeneratorLocator = $idGeneratorLocator;
}
/**
* {@inheritDoc}
*/
public function getAllClassNames()
{
return $this->driver->getAllClassNames();
}
/**
* {@inheritDoc}
*/
public function isTransient($className): bool
{
return $this->driver->isTransient($className);
}
/**
* {@inheritDoc}
*/
public function loadMetadataForClass($className, ClassMetadata $metadata): void
{
$this->driver->loadMetadataForClass($className, $metadata);
if (
! $metadata instanceof ClassMetadataInfo
|| $metadata->generatorType !== ClassMetadataInfo::GENERATOR_TYPE_CUSTOM
|| ! isset($metadata->customGeneratorDefinition['class'])
|| ! $this->idGeneratorLocator->has($metadata->customGeneratorDefinition['class'])
) {
return;
}
$idGenerator = $this->idGeneratorLocator->get($metadata->customGeneratorDefinition['class']);
$metadata->setCustomGeneratorDefinition(['instance' => $idGenerator] + $metadata->customGeneratorDefinition);
$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_NONE);
}
/**
* Returns the inner driver
*/
public function getDriver(): MappingDriverInterface
{
return $this->driver;
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Middleware;
interface ConnectionNameAwareInterface
{
public function setConnectionName(string $name): void;
}

View File

@@ -0,0 +1,86 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\ORMException;
use Doctrine\ORM\Proxy\Proxy;
use ProxyManager\Proxy\LazyLoadingInterface;
use Psr\Container\ContainerInterface;
use Symfony\Bridge\Doctrine\ManagerRegistry;
use Symfony\Contracts\Service\ResetInterface;
use function array_keys;
use function assert;
/**
* References all Doctrine connections and entity managers in a given Container.
*/
class Registry extends ManagerRegistry implements ResetInterface
{
/**
* @param string[] $connections
* @param string[] $entityManagers
*/
public function __construct(ContainerInterface $container, array $connections, array $entityManagers, string $defaultConnection, string $defaultEntityManager)
{
$this->container = $container;
parent::__construct('ORM', $connections, $entityManagers, $defaultConnection, $defaultEntityManager, Proxy::class);
}
/**
* Resolves a registered namespace alias to the full namespace.
*
* This method looks for the alias in all registered entity managers.
*
* @see Configuration::getEntityNamespace
*
* @param string $alias The alias
*
* @return string The full namespace
*/
public function getAliasNamespace($alias)
{
foreach (array_keys($this->getManagers()) as $name) {
$objectManager = $this->getManager($name);
if (! $objectManager instanceof EntityManagerInterface) {
continue;
}
try {
return $objectManager->getConfiguration()->getEntityNamespace($alias);
} catch (ORMException $e) {
}
}
throw ORMException::unknownEntityNamespace($alias);
}
public function reset(): void
{
foreach ($this->getManagerNames() as $managerName => $serviceId) {
$this->resetOrClearManager($managerName, $serviceId);
}
}
private function resetOrClearManager(string $managerName, string $serviceId): void
{
if (! $this->container->initialized($serviceId)) {
return;
}
$manager = $this->container->get($serviceId);
assert($manager instanceof EntityManagerInterface);
if (! $manager instanceof LazyLoadingInterface || $manager->isOpen()) {
$manager->clear();
return;
}
$this->resetManager($managerName);
}
}

View File

@@ -0,0 +1,92 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Repository;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\ServiceRepositoryCompilerPass;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Repository\RepositoryFactory;
use Doctrine\Persistence\ObjectRepository;
use Psr\Container\ContainerInterface;
use RuntimeException;
use function class_exists;
use function is_a;
use function spl_object_hash;
use function sprintf;
/**
* Fetches repositories from the container or falls back to normal creation.
*/
final class ContainerRepositoryFactory implements RepositoryFactory
{
/** @var array<string, ObjectRepository> */
private $managedRepositories = [];
/** @var ContainerInterface */
private $container;
/** @param ContainerInterface $container A service locator containing the repositories */
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* {@inheritdoc}
*/
public function getRepository(EntityManagerInterface $entityManager, $entityName): ObjectRepository
{
$metadata = $entityManager->getClassMetadata($entityName);
$repositoryServiceId = $metadata->customRepositoryClassName;
$customRepositoryName = $metadata->customRepositoryClassName;
if ($customRepositoryName !== null) {
// fetch from the container
if ($this->container->has($customRepositoryName)) {
$repository = $this->container->get($customRepositoryName);
if (! $repository instanceof ObjectRepository) {
throw new RuntimeException(sprintf('The service "%s" must implement ObjectRepository (or extend a base class, like ServiceEntityRepository).', $repositoryServiceId));
}
return $repository;
}
// if not in the container but the class/id implements the interface, throw an error
if (is_a($customRepositoryName, ServiceEntityRepositoryInterface::class, true)) {
throw new RuntimeException(sprintf('The "%s" entity repository implements "%s", but its service could not be found. Make sure the service exists and is tagged with "%s".', $customRepositoryName, ServiceEntityRepositoryInterface::class, ServiceRepositoryCompilerPass::REPOSITORY_SERVICE_TAG));
}
if (! class_exists($customRepositoryName)) {
throw new RuntimeException(sprintf('The "%s" entity has a repositoryClass set to "%s", but this is not a valid class. Check your class naming. If this is meant to be a service id, make sure this service exists and is tagged with "%s".', $metadata->name, $customRepositoryName, ServiceRepositoryCompilerPass::REPOSITORY_SERVICE_TAG));
}
// allow the repository to be created below
}
return $this->getOrCreateRepository($entityManager, $metadata);
}
/**
* @param ClassMetadata<TEntity> $metadata
*
* @return ObjectRepository<TEntity>
*
* @template TEntity of object
*/
private function getOrCreateRepository(
EntityManagerInterface $entityManager,
ClassMetadata $metadata
): ObjectRepository {
$repositoryHash = $metadata->getName() . spl_object_hash($entityManager);
if (isset($this->managedRepositories[$repositoryHash])) {
return $this->managedRepositories[$repositoryHash];
}
$repositoryClassName = $metadata->customRepositoryClassName ?: $entityManager->getConfiguration()->getDefaultRepositoryClassName();
/** @psalm-var ObjectRepository<TEntity> */
return $this->managedRepositories[$repositoryHash] = new $repositoryClassName($entityManager, $metadata);
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Repository;
use Doctrine\ORM\EntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use LogicException;
use function sprintf;
/**
* Optional EntityRepository base class with a simplified constructor (for autowiring).
*
* To use in your class, inject the "registry" service and call
* the parent constructor. For example:
*
* class YourEntityRepository extends ServiceEntityRepository
* {
* public function __construct(ManagerRegistry $registry)
* {
* parent::__construct($registry, YourEntity::class);
* }
* }
*
* @template T of object
* @template-extends EntityRepository<T>
*/
class ServiceEntityRepository extends EntityRepository implements ServiceEntityRepositoryInterface
{
/**
* @param string $entityClass The class name of the entity this repository manages
* @psalm-param class-string<T> $entityClass
*/
public function __construct(ManagerRegistry $registry, string $entityClass)
{
$manager = $registry->getManagerForClass($entityClass);
if ($manager === null) {
throw new LogicException(sprintf(
'Could not find the entity manager for class "%s". Check your Doctrine configuration to make sure it is configured to load this entitys metadata.',
$entityClass
));
}
parent::__construct($manager, $manager->getClassMetadata($entityClass));
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Repository;
/**
* This interface signals that your repository should be loaded from the container.
*/
interface ServiceEntityRepositoryInterface
{
}

View File

@@ -0,0 +1,116 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="doctrine.dbal.logger.chain.class">Doctrine\DBAL\Logging\LoggerChain</parameter>
<parameter key="doctrine.dbal.logger.profiling.class">Doctrine\DBAL\Logging\DebugStack</parameter>
<parameter key="doctrine.dbal.logger.class">Symfony\Bridge\Doctrine\Logger\DbalLogger</parameter>
<parameter key="doctrine.dbal.configuration.class">Doctrine\DBAL\Configuration</parameter>
<parameter key="doctrine.data_collector.class">Doctrine\Bundle\DoctrineBundle\DataCollector\DoctrineDataCollector</parameter>
<parameter key="doctrine.dbal.connection.event_manager.class">Symfony\Bridge\Doctrine\ContainerAwareEventManager</parameter>
<parameter key="doctrine.dbal.connection_factory.class">Doctrine\Bundle\DoctrineBundle\ConnectionFactory</parameter>
<parameter key="doctrine.dbal.events.mysql_session_init.class">Doctrine\DBAL\Event\Listeners\MysqlSessionInit</parameter>
<parameter key="doctrine.dbal.events.oracle_session_init.class">Doctrine\DBAL\Event\Listeners\OracleSessionInit</parameter>
<parameter key="doctrine.class">Doctrine\Bundle\DoctrineBundle\Registry</parameter>
<parameter key="doctrine.entity_managers" type="collection"></parameter>
<parameter key="doctrine.default_entity_manager"></parameter>
</parameters>
<services>
<service id="Doctrine\DBAL\Driver\Connection" alias="database_connection" public="false" />
<service id="Doctrine\DBAL\Connection" alias="database_connection" public="false" />
<service id="doctrine.dbal.logger.chain" class="%doctrine.dbal.logger.chain.class%" public="false" abstract="true" >
<!-- TODO: deprecate this service in 2.3.0 -->
</service>
<service id="doctrine.dbal.logger.profiling" class="%doctrine.dbal.logger.profiling.class%" public="false" abstract="true" />
<service id="doctrine.dbal.logger.backtrace" class="Doctrine\Bundle\DoctrineBundle\Dbal\Logging\BacktraceLogger" public="false" abstract="true" />
<service id="doctrine.dbal.logger" class="%doctrine.dbal.logger.class%" public="false">
<tag name="monolog.logger" channel="doctrine" />
<argument type="service" id="logger" on-invalid="null" />
<argument type="service" id="debug.stopwatch" on-invalid="null" />
</service>
<service id="data_collector.doctrine" class="%doctrine.data_collector.class%" public="false">
<tag name="data_collector" template="@Doctrine/Collector/db.html.twig" id="db" priority="250" />
<argument type="service" id="doctrine" />
<argument>true</argument>
</service>
<service id="doctrine.dbal.connection_factory" class="%doctrine.dbal.connection_factory.class%">
<argument>%doctrine.dbal.connection_factory.types%</argument>
</service>
<service id="doctrine.dbal.connection" class="Doctrine\DBAL\Connection" abstract="true">
<factory service="doctrine.dbal.connection_factory" method="createConnection" />
</service>
<service id="doctrine.dbal.connection.event_manager" class="%doctrine.dbal.connection.event_manager.class%" public="false" abstract="true">
<argument type="service" id="service_container" />
</service>
<service id="doctrine.dbal.connection.configuration" class="%doctrine.dbal.configuration.class%" public="false" abstract="true" />
<service id="doctrine" class="%doctrine.class%" public="true">
<argument type="service" id="service_container" />
<argument>%doctrine.connections%</argument>
<argument>%doctrine.entity_managers%</argument>
<argument>%doctrine.default_connection%</argument>
<argument>%doctrine.default_entity_manager%</argument>
<tag name="kernel.reset" method="reset" />
</service>
<service id="Doctrine\Persistence\ManagerRegistry" alias="doctrine" public="false" />
<service id="Doctrine\Common\Persistence\ManagerRegistry" alias="doctrine" public="false" />
<service id="doctrine.twig.doctrine_extension" class="Doctrine\Bundle\DoctrineBundle\Twig\DoctrineExtension" public="false">
<tag name="twig.extension" />
</service>
<service id="doctrine.dbal.schema_asset_filter_manager" class="Doctrine\Bundle\DoctrineBundle\Dbal\SchemaAssetsFilterManager" public="false" abstract="true">
<!-- schema assets filters -->
</service>
<service id="doctrine.dbal.well_known_schema_asset_filter" class="Doctrine\Bundle\DoctrineBundle\Dbal\BlacklistSchemaAssetFilter" public="false">
<argument type="collection" />
</service>
<!-- commands -->
<service id="doctrine.database_create_command" class="Doctrine\Bundle\DoctrineBundle\Command\CreateDatabaseDoctrineCommand">
<argument type="service" id="doctrine" />
<tag name="console.command" command="doctrine:database:create" />
</service>
<service id="doctrine.database_drop_command" class="Doctrine\Bundle\DoctrineBundle\Command\DropDatabaseDoctrineCommand">
<argument type="service" id="doctrine" />
<tag name="console.command" command="doctrine:database:drop" />
</service>
<service id="doctrine.query_sql_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\RunSqlDoctrineCommand">
<argument type="service" id="Doctrine\Bundle\DoctrineBundle\Dbal\ManagerRegistryAwareConnectionProvider" on-invalid="null" />
<tag name="console.command" command="doctrine:query:sql" />
</service>
<service id="Doctrine\DBAL\Tools\Console\Command\RunSqlCommand">
<argument type="service" id="Doctrine\Bundle\DoctrineBundle\Dbal\ManagerRegistryAwareConnectionProvider" on-invalid="null" />
<tag name="console.command" command="dbal:run-sql" />
</service>
<service id="Doctrine\Bundle\DoctrineBundle\Controller\ProfilerController">
<argument type="service" id="twig" />
<argument type="service" id="doctrine" />
<argument type="service" id="profiler" />
<tag name="controller.service_arguments" />
</service>
</services>
</container>

View File

@@ -0,0 +1,59 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<!--
The following service isn't prefixed by the "doctrine.orm" namespace in order for end-users to just use
the "doctrine_transaction" shortcut in message buses middleware config
-->
<service id="messenger.middleware.doctrine_transaction" class="Symfony\Bridge\Doctrine\Messenger\DoctrineTransactionMiddleware" abstract="true" public="false">
<argument type="service" id="doctrine" />
</service>
<!--
The following service isn't prefixed by the "doctrine.orm" namespace in order for end-users to just use
the "doctrine_ping_connection" shortcut in message buses middleware config
-->
<service id="messenger.middleware.doctrine_ping_connection" class="Symfony\Bridge\Doctrine\Messenger\DoctrinePingConnectionMiddleware" abstract="true" public="false">
<argument type="service" id="doctrine" />
</service>
<!--
The following service isn't prefixed by the "doctrine.orm" namespace in order for end-users to just use
the "doctrine_close_connection" shortcut in message buses middleware config
-->
<service id="messenger.middleware.doctrine_close_connection" class="Symfony\Bridge\Doctrine\Messenger\DoctrineCloseConnectionMiddleware" abstract="true" public="false">
<argument type="service" id="doctrine" />
</service>
<!--
The following service isn't prefixed by the "doctrine.orm" namespace in order for end-users to just use
the "doctrine_open_transaction_logger" shortcut in message buses middleware config
-->
<service id="messenger.middleware.doctrine_open_transaction_logger" class="Symfony\Bridge\Doctrine\Messenger\DoctrineOpenTransactionLoggerMiddleware" abstract="true" public="false">
<argument type="service" id="doctrine" />
<argument>null</argument>
<argument type="service" id="logger" />
</service>
<!--
The following service isn't tagged as transport factory because the class may not exist.
The tag is added conditionally in DoctrineExtension.
-->
<service id="messenger.transport.doctrine.factory" class="Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineTransportFactory" public="false">
<argument type="service" id="doctrine" />
</service>
<service id="doctrine.orm.messenger.event_subscriber.doctrine_clear_entity_manager" class="Symfony\Bridge\Doctrine\Messenger\DoctrineClearEntityManagerWorkerSubscriber" public="false">
<tag name="kernel.event_subscriber" />
<argument type="service" id="doctrine" />
</service>
<service id="doctrine.orm.messenger.doctrine_schema_subscriber" class="Symfony\Bridge\Doctrine\SchemaListener\MessengerTransportDoctrineSchemaSubscriber">
<argument type="tagged" tag="messenger.receiver" />
<tag name="doctrine.event_subscriber" />
</service>
</services>
</container>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="doctrine.dbal.logging_middleware" class="Doctrine\DBAL\Logging\Middleware" abstract="true">
<argument type="service" id="logger" />
<tag name="monolog.logger" channel="doctrine" />
</service>
</services>
</container>

View File

@@ -0,0 +1,237 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="doctrine.orm.configuration.class">Doctrine\ORM\Configuration</parameter>
<parameter key="doctrine.orm.entity_manager.class">Doctrine\ORM\EntityManager</parameter>
<parameter key="doctrine.orm.manager_configurator.class">Doctrine\Bundle\DoctrineBundle\ManagerConfigurator</parameter>
<!-- cache -->
<parameter key="doctrine.orm.cache.array.class">Doctrine\Common\Cache\ArrayCache</parameter>
<parameter key="doctrine.orm.cache.apc.class">Doctrine\Common\Cache\ApcCache</parameter>
<parameter key="doctrine.orm.cache.memcache.class">Doctrine\Common\Cache\MemcacheCache</parameter>
<parameter key="doctrine.orm.cache.memcache_host">localhost</parameter>
<parameter key="doctrine.orm.cache.memcache_port">11211</parameter>
<parameter key="doctrine.orm.cache.memcache_instance.class">Memcache</parameter>
<parameter key="doctrine.orm.cache.memcached.class">Doctrine\Common\Cache\MemcachedCache</parameter>
<parameter key="doctrine.orm.cache.memcached_host">localhost</parameter>
<parameter key="doctrine.orm.cache.memcached_port">11211</parameter>
<parameter key="doctrine.orm.cache.memcached_instance.class">Memcached</parameter>
<parameter key="doctrine.orm.cache.redis.class">Doctrine\Common\Cache\RedisCache</parameter>
<parameter key="doctrine.orm.cache.redis_host">localhost</parameter>
<parameter key="doctrine.orm.cache.redis_port">6379</parameter>
<parameter key="doctrine.orm.cache.redis_instance.class">Redis</parameter>
<parameter key="doctrine.orm.cache.xcache.class">Doctrine\Common\Cache\XcacheCache</parameter>
<parameter key="doctrine.orm.cache.wincache.class">Doctrine\Common\Cache\WinCacheCache</parameter>
<parameter key="doctrine.orm.cache.zenddata.class">Doctrine\Common\Cache\ZendDataCache</parameter>
<!-- metadata -->
<parameter key="doctrine.orm.metadata.driver_chain.class">Doctrine\Persistence\Mapping\Driver\MappingDriverChain</parameter>
<parameter key="doctrine.orm.metadata.annotation.class">Doctrine\ORM\Mapping\Driver\AnnotationDriver</parameter>
<parameter key="doctrine.orm.metadata.xml.class">Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver</parameter>
<parameter key="doctrine.orm.metadata.yml.class">Doctrine\ORM\Mapping\Driver\SimplifiedYamlDriver</parameter>
<parameter key="doctrine.orm.metadata.php.class">Doctrine\ORM\Mapping\Driver\PHPDriver</parameter>
<parameter key="doctrine.orm.metadata.staticphp.class">Doctrine\ORM\Mapping\Driver\StaticPHPDriver</parameter>
<parameter key="doctrine.orm.metadata.attribute.class">Doctrine\ORM\Mapping\Driver\AttributeDriver</parameter>
<!-- cache warmer -->
<parameter key="doctrine.orm.proxy_cache_warmer.class">Symfony\Bridge\Doctrine\CacheWarmer\ProxyCacheWarmer</parameter>
<!-- form field factory guesser -->
<parameter key="form.type_guesser.doctrine.class">Symfony\Bridge\Doctrine\Form\DoctrineOrmTypeGuesser</parameter>
<!-- validator -->
<parameter key="doctrine.orm.validator.unique.class">Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntityValidator</parameter>
<parameter key="doctrine.orm.validator_initializer.class">Symfony\Bridge\Doctrine\Validator\DoctrineInitializer</parameter>
<!-- security -->
<parameter key="doctrine.orm.security.user.provider.class">Symfony\Bridge\Doctrine\Security\User\EntityUserProvider</parameter>
<!-- listeners -->
<parameter key="doctrine.orm.listeners.resolve_target_entity.class">Doctrine\ORM\Tools\ResolveTargetEntityListener</parameter>
<parameter key="doctrine.orm.listeners.attach_entity_listeners.class">Doctrine\ORM\Tools\AttachEntityListenersListener</parameter>
<!-- naming strategy -->
<parameter key="doctrine.orm.naming_strategy.default.class">Doctrine\ORM\Mapping\DefaultNamingStrategy</parameter>
<parameter key="doctrine.orm.naming_strategy.underscore.class">Doctrine\ORM\Mapping\UnderscoreNamingStrategy</parameter>
<!-- quote strategy -->
<parameter key="doctrine.orm.quote_strategy.default.class">Doctrine\ORM\Mapping\DefaultQuoteStrategy</parameter>
<parameter key="doctrine.orm.quote_strategy.ansi.class">Doctrine\ORM\Mapping\AnsiQuoteStrategy</parameter>
<!-- entity listener resolver -->
<parameter key="doctrine.orm.entity_listener_resolver.class">Doctrine\Bundle\DoctrineBundle\Mapping\ContainerEntityListenerResolver</parameter>
<!-- second level cache -->
<parameter key="doctrine.orm.second_level_cache.default_cache_factory.class">Doctrine\ORM\Cache\DefaultCacheFactory</parameter>
<parameter key="doctrine.orm.second_level_cache.default_region.class">Doctrine\ORM\Cache\Region\DefaultRegion</parameter>
<parameter key="doctrine.orm.second_level_cache.filelock_region.class">Doctrine\ORM\Cache\Region\FileLockRegion</parameter>
<parameter key="doctrine.orm.second_level_cache.logger_chain.class">Doctrine\ORM\Cache\Logging\CacheLoggerChain</parameter>
<parameter key="doctrine.orm.second_level_cache.logger_statistics.class">Doctrine\ORM\Cache\Logging\StatisticsCacheLogger</parameter>
<parameter key="doctrine.orm.second_level_cache.cache_configuration.class">Doctrine\ORM\Cache\CacheConfiguration</parameter>
<parameter key="doctrine.orm.second_level_cache.regions_configuration.class">Doctrine\ORM\Cache\RegionsConfiguration</parameter>
</parameters>
<services>
<service id="Doctrine\ORM\EntityManagerInterface" alias="doctrine.orm.entity_manager" public="false" />
<!--- Internal Annotation Metadata Reader Service alias, use annotation_reader service -->
<service id="doctrine.orm.metadata.annotation_reader" alias="annotation_reader" public="false" />
<service id="doctrine.orm.proxy_cache_warmer" class="%doctrine.orm.proxy_cache_warmer.class%" public="false">
<tag name="kernel.cache_warmer" />
<argument type="service" id="doctrine" />
</service>
<service id="form.type_guesser.doctrine" class="%form.type_guesser.doctrine.class%">
<tag name="form.type_guesser" />
<argument type="service" id="doctrine" />
</service>
<service id="form.type.entity" class="Symfony\Bridge\Doctrine\Form\Type\EntityType">
<tag name="form.type" alias="entity" />
<argument type="service" id="doctrine" />
</service>
<service id="doctrine.orm.configuration" class="%doctrine.orm.configuration.class%" abstract="true" public="false" />
<service id="doctrine.orm.entity_manager.abstract" class="%doctrine.orm.entity_manager.class%" abstract="true" lazy="true">
<factory class="%doctrine.orm.entity_manager.class%" method="create" />
</service>
<service id="doctrine.orm.container_repository_factory" class="Doctrine\Bundle\DoctrineBundle\Repository\ContainerRepositoryFactory" public="false">
<argument type="service">
<service class="Symfony\Component\DependencyInjection\ServiceLocator">
<argument type="collection" />
</service>
</argument>
</service>
<!-- The configurator cannot be a private service -->
<service id="doctrine.orm.manager_configurator.abstract" class="%doctrine.orm.manager_configurator.class%" abstract="true">
<argument type="collection" />
<argument type="collection" />
</service>
<!-- validator -->
<service id="doctrine.orm.validator.unique" class="%doctrine.orm.validator.unique.class%">
<tag name="validator.constraint_validator" alias="doctrine.orm.validator.unique" />
<argument type="service" id="doctrine" />
</service>
<service id="doctrine.orm.validator_initializer" class="%doctrine.orm.validator_initializer.class%">
<tag name="validator.initializer" />
<argument type="service" id="doctrine" />
</service>
<!-- security -->
<service id="doctrine.orm.security.user.provider" class="%doctrine.orm.security.user.provider.class%" abstract="true" public="false">
<argument type="service" id="doctrine" />
</service>
<!-- listeners -->
<service id="doctrine.orm.listeners.resolve_target_entity" class="%doctrine.orm.listeners.resolve_target_entity.class%" public="false" />
<service id="doctrine.orm.listeners.doctrine_dbal_cache_adapter_schema_subscriber" class="Symfony\Bridge\Doctrine\SchemaListener\DoctrineDbalCacheAdapterSchemaSubscriber">
<argument type="collection" /> <!-- DoctrineDbalAdapter instances -->
<tag name="doctrine.event_subscriber" />
</service>
<service id="doctrine.orm.listeners.pdo_cache_adapter_doctrine_schema_subscriber" class="Symfony\Bridge\Doctrine\SchemaListener\PdoCacheAdapterDoctrineSchemaSubscriber">
<argument type="collection" /> <!-- PdoAdapter instances -->
<tag name="doctrine.event_subscriber" />
</service>
<service id="doctrine.orm.listeners.doctrine_token_provider_schema_subscriber" class="Symfony\Bridge\Doctrine\SchemaListener\RememberMeTokenProviderDoctrineSchemaSubscriber">
<argument type="tagged" tag="security.remember_me_handler" />
<tag name="doctrine.event_subscriber" />
</service>
<!-- naming strategy -->
<service id="doctrine.orm.naming_strategy.default" class="%doctrine.orm.naming_strategy.default.class%" public="false" />
<service id="doctrine.orm.naming_strategy.underscore" class="%doctrine.orm.naming_strategy.underscore.class%" public="false" />
<service id="doctrine.orm.naming_strategy.underscore_number_aware" class="%doctrine.orm.naming_strategy.underscore.class%" public="false">
<argument type="constant">CASE_LOWER</argument>
<argument>true</argument>
</service>
<!-- quote strategy -->
<service id="doctrine.orm.quote_strategy.default" class="%doctrine.orm.quote_strategy.default.class%" public="false" />
<service id="doctrine.orm.quote_strategy.ansi" class="%doctrine.orm.quote_strategy.ansi.class%" public="false" />
<!-- custom id generators -->
<service id="doctrine.ulid_generator" class="Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator">
<argument type="service" id="ulid.factory" on-invalid="ignore" />
<tag name="doctrine.id_generator" />
</service>
<service id="doctrine.uuid_generator" class="Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator">
<argument type="service" id="uuid.factory" on-invalid="ignore" />
<tag name="doctrine.id_generator" />
</service>
<!-- commands -->
<service id="doctrine.cache_clear_metadata_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\ClearMetadataCacheDoctrineCommand">
<tag name="console.command" command="doctrine:cache:clear-metadata" />
</service>
<service id="doctrine.cache_clear_query_cache_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\ClearQueryCacheDoctrineCommand">
<tag name="console.command" command="doctrine:cache:clear-query" />
</service>
<service id="doctrine.cache_clear_result_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\ClearResultCacheDoctrineCommand">
<tag name="console.command" command="doctrine:cache:clear-result" />
</service>
<service id="doctrine.cache_collection_region_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\CollectionRegionDoctrineCommand">
<tag name="console.command" command="doctrine:cache:clear-collection-region" />
</service>
<service id="doctrine.mapping_convert_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\ConvertMappingDoctrineCommand">
<tag name="console.command" command="doctrine:mapping:convert" />
</service>
<service id="doctrine.schema_create_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\CreateSchemaDoctrineCommand">
<tag name="console.command" command="doctrine:schema:create" />
</service>
<service id="doctrine.schema_drop_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\DropSchemaDoctrineCommand">
<tag name="console.command" command="doctrine:schema:drop" />
</service>
<service id="doctrine.ensure_production_settings_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\EnsureProductionSettingsDoctrineCommand">
<tag name="console.command" command="doctrine:ensure-production-settings" />
</service>
<service id="doctrine.clear_entity_region_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\EntityRegionCacheDoctrineCommand">
<tag name="console.command" command="doctrine:cache:clear-entity-region" />
</service>
<service id="doctrine.mapping_info_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\InfoDoctrineCommand">
<tag name="console.command" command="doctrine:mapping:info" />
</service>
<service id="doctrine.clear_query_region_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\QueryRegionCacheDoctrineCommand">
<tag name="console.command" command="doctrine:cache:clear-query-region" />
</service>
<service id="doctrine.query_dql_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\RunDqlDoctrineCommand">
<tag name="console.command" command="doctrine:query:dql" />
</service>
<service id="doctrine.schema_update_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\UpdateSchemaDoctrineCommand">
<tag name="console.command" command="doctrine:schema:update" />
</service>
<service id="doctrine.schema_validate_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\ValidateSchemaCommand">
<tag name="console.command" command="doctrine:schema:validate" />
</service>
<service id="doctrine.mapping_import_command" class="Doctrine\Bundle\DoctrineBundle\Command\ImportMappingDoctrineCommand">
<argument type="service" id="doctrine" />
<argument>%kernel.bundles%</argument>
<tag name="console.command" command="doctrine:mapping:import" />
</service>
</services>
</container>

View File

@@ -0,0 +1,276 @@
<?xml version="1.0" encoding="UTF-8" ?>
<xsd:schema xmlns="http://symfony.com/schema/dic/doctrine"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://symfony.com/schema/dic/doctrine"
elementFormDefault="qualified">
<xsd:element name="config">
<xsd:complexType>
<xsd:all>
<xsd:element name="dbal" type="dbal" minOccurs="0" maxOccurs="1" />
<xsd:element name="orm" type="orm" minOccurs="0" maxOccurs="1" />
</xsd:all>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="named_scalar">
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<!-- DBAL configuration -->
<xsd:attributeGroup name="connection-config">
<xsd:attribute name="driver" type="xsd:string" />
<xsd:attribute name="driver-class" type="xsd:string" />
<xsd:attribute name="wrapper-class" type="xsd:string" />
<xsd:attribute name="keep-slave" type="xsd:string" />
<xsd:attribute name="keep-replica" type="xsd:string" />
<xsd:attribute name="platform-service" type="xsd:string" />
<xsd:attribute name="shard-choser" type="xsd:string" />
<xsd:attribute name="shard-choser-service" type="xsd:string" />
<xsd:attribute name="auto-commit" type="xsd:string" />
<xsd:attribute name="schema-filter" type="xsd:string" />
<xsd:attribute name="logging" type="xsd:string" default="false" />
<xsd:attribute name="profiling" type="xsd:string" default="false" />
<xsd:attribute name="profiling-collect-backtrace" type="xsd:string" default="false" />
<xsd:attribute name="profiling-collect-schema-errors" type="xsd:string" default="true" />
<xsd:attribute name="server-version" type="xsd:string" />
<xsd:attribute name="use-savepoints" type="xsd:boolean" />
<xsd:attributeGroup ref="driver-config" />
</xsd:attributeGroup>
<xsd:attributeGroup name="driver-config">
<xsd:attribute name="url" type="xsd:string" />
<xsd:attribute name="dbname" type="xsd:string" />
<xsd:attribute name="host" type="xsd:string" />
<xsd:attribute name="port" type="xsd:string" />
<xsd:attribute name="user" type="xsd:string" />
<xsd:attribute name="password" type="xsd:string" />
<xsd:attribute name="override-url" type="xsd:boolean" />
<xsd:attribute name="dbname-suffix" type="xsd:string" />
<xsd:attribute name="application-name" type="xsd:string" />
<xsd:attribute name="path" type="xsd:string" />
<xsd:attribute name="unix-socket" type="xsd:string" />
<xsd:attribute name="memory" type="xsd:string" />
<xsd:attribute name="charset" type="xsd:string" />
<xsd:attribute name="persistent" type="xsd:string" />
<xsd:attribute name="protocol" type="xsd:string" />
<xsd:attribute name="server" type="xsd:string" />
<xsd:attribute name="service" type="xsd:string" />
<xsd:attribute name="servicename" type="xsd:string" />
<xsd:attribute name="session-mode" type="xsd:string" />
<xsd:attribute name="default_dbname" type="xsd:string" />
<xsd:attribute name="sslmode" type="xsd:string" />
<xsd:attribute name="sslrootcert" type="xsd:string" />
<xsd:attribute name="sslcert" type="xsd:string" />
<xsd:attribute name="sslkey" type="xsd:string" />
<xsd:attribute name="sslcrl" type="xsd:string" />
<xsd:attribute name="pooled" type="xsd:string" />
<xsd:attribute name="multiple-active-result-sets" type="xsd:string" />
<xsd:attribute name="connectstring" type="xsd:string" />
<xsd:attribute name="instancename" type="xsd:string" />
</xsd:attributeGroup>
<xsd:group name="connection-child-config">
<xsd:choice>
<xsd:element name="option" type="option" />
<xsd:element name="mapping-type" type="named_scalar" />
<xsd:element name="slave" type="replica" />
<xsd:element name="replica" type="replica" />
<xsd:element name="shard" type="shard" />
<xsd:element name="default-table-option" type="named_scalar" />
</xsd:choice>
</xsd:group>
<xsd:complexType name="dbal">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="connection" type="connection" />
<xsd:element name="type" type="named_scalar" />
<xsd:group ref="connection-child-config" />
</xsd:choice>
<xsd:attribute name="default-connection" type="xsd:string" />
<xsd:attributeGroup ref="connection-config" />
</xsd:complexType>
<xsd:complexType name="option">
<xsd:complexContent>
<xsd:extension base="xsd:anyType">
<xsd:attribute name="key" type="xsd:string" use="required" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="connection">
<xsd:group ref="connection-child-config" minOccurs="0" maxOccurs="unbounded" />
<xsd:attribute name="name" type="xsd:string" use="required" />
<xsd:attributeGroup ref="connection-config" />
</xsd:complexType>
<xsd:complexType name="replica">
<xsd:attribute name="name" type="xsd:string" use="required" />
<xsd:attributeGroup ref="driver-config" />
</xsd:complexType>
<xsd:complexType name="shard">
<xsd:attribute name="id" type="xsd:integer" use="required" />
<xsd:attributeGroup ref="driver-config" />
</xsd:complexType>
<!-- ORM configuration -->
<xsd:complexType name="mapping">
<xsd:attribute name="name" type="xsd:string" use="required" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="dir" type="xsd:string" />
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="prefix" type="xsd:string" />
<xsd:attribute name="is-bundle" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="orm">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="entity-manager" type="entity_manager" />
<xsd:element name="resolve-target-entity" type="resolve_target_entity" minOccurs="0" maxOccurs="unbounded" />
<xsd:group ref="entity-manager-child-config" />
</xsd:choice>
<xsd:attribute name="default-entity-manager" type="xsd:string" />
<xsd:attribute name="proxy-dir" type="xsd:string" />
<xsd:attribute name="proxy-namespace" type="xsd:string" />
<xsd:attribute name="auto-generate-proxy-classes" type="xsd:string" default="false" />
<xsd:attributeGroup ref="entity-manager-config" />
</xsd:complexType>
<xsd:complexType name="resolve_target_entity">
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="interface" type="xsd:string" use="required" />
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<xsd:simpleType name="cache_driver_type">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="pool"/>
<xsd:enumeration value="service"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="cache_driver">
<xsd:attribute name="type" type="cache_driver_type" default="pool" />
<xsd:attribute name="id" type="xsd:string" />
<xsd:attribute name="pool" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="entity_listeners">
<xsd:choice minOccurs="1">
<xsd:element name="entity" type="entity_listeners_entity" minOccurs="1" maxOccurs="unbounded" />
</xsd:choice>
</xsd:complexType>
<xsd:complexType name="entity_listeners_entity">
<xsd:choice minOccurs="1">
<xsd:element name="listener" type="entity_listeners_listener" minOccurs="1" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="class" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="entity_listeners_listener">
<xsd:choice minOccurs="1">
<xsd:element name="event" type="entity_listeners_event" minOccurs="1" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="class" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="entity_listeners_event">
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="method" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="entity_manager">
<xsd:group ref="entity-manager-child-config" minOccurs="0" maxOccurs="unbounded" />
<xsd:attribute name="name" type="xsd:string" use="required" />
<xsd:attributeGroup ref="entity-manager-config" />
</xsd:complexType>
<xsd:group name="entity-manager-child-config">
<xsd:choice>
<xsd:element name="mapping" type="mapping" />
<xsd:element name="metadata-cache-driver" type="cache_driver" minOccurs="0" maxOccurs="1" />
<xsd:element name="result-cache-driver" type="cache_driver" minOccurs="0" maxOccurs="1" />
<xsd:element name="query-cache-driver" type="cache_driver" minOccurs="0" maxOccurs="1" />
<xsd:element name="dql" type="dql" minOccurs="0" maxOccurs="1" />
<xsd:element name="hydrator" type="named_scalar" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="filter" type="filter" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="entity-listeners" type="entity_listeners" minOccurs="0" maxOccurs="1" />
<xsd:element name="second-level-cache" type="second-level-cache" minOccurs="0" maxOccurs="1" />
</xsd:choice>
</xsd:group>
<xsd:attributeGroup name="entity-manager-config">
<xsd:attribute name="auto-mapping" type="xsd:string" />
<xsd:attribute name="connection" type="xsd:string" />
<xsd:attribute name="default-repository-class" type="xsd:string" />
<xsd:attribute name="class-metadata-factory-name" type="xsd:string" />
<xsd:attribute name="naming-strategy" type="xsd:string" />
<xsd:attribute name="quote-strategy" type="xsd:string" />
<xsd:attribute name="entity-listener-resolver" type="xsd:string" />
<xsd:attribute name="repository-factory" type="xsd:string" />
</xsd:attributeGroup>
<xsd:complexType name="filter" mixed="true">
<xsd:choice minOccurs="0">
<xsd:element name="parameter" type="named_scalar" minOccurs="0" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="name" type="xsd:string" use="required" />
<xsd:attribute name="class" type="xsd:string" />
<xsd:attribute name="enabled" type="xsd:boolean" />
</xsd:complexType>
<xsd:complexType name="second-level-cache-region" mixed="true">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="cache-driver" type="cache_driver" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="service" type="xsd:string" />
<xsd:attribute name="lifetime" type="xsd:integer" />
<xsd:attribute name="lock-lifetime" type="xsd:integer" />
<xsd:attribute name="cache-driver" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="second-level-cache-logger" mixed="true">
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="service" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="second-level-cache" mixed="true">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="logger" type="second-level-cache-logger" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="region" type="second-level-cache-region" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="region-cache-driver" type="cache_driver" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:attribute name="enabled" type="xsd:boolean" default="true"/>
<xsd:attribute name="log-enabled" type="xsd:boolean" default="true"/>
<xsd:attribute name="factory" type="xsd:string" />
<xsd:attribute name="query-validator" type="xsd:string" />
<xsd:attribute name="region-lifetime" type="xsd:integer" />
<xsd:attribute name="region-lock-lifetime" type="xsd:integer" />
</xsd:complexType>
<xsd:complexType name="dql">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="string-function" type="named_scalar" />
<xsd:element name="numeric-function" type="named_scalar" />
<xsd:element name="datetime-function" type="named_scalar" />
</xsd:choice>
</xsd:complexType>
</xsd:schema>

View File

@@ -0,0 +1,471 @@
{% extends request.isXmlHttpRequest ? '@WebProfiler/Profiler/ajax_layout.html.twig' : '@WebProfiler/Profiler/layout.html.twig' %}
{% import _self as helper %}
{% block toolbar %}
{% if collector.querycount > 0 or collector.invalidEntityCount > 0 %}
{% set icon %}
{% set status = collector.invalidEntityCount > 0 ? 'red' : collector.querycount > 50 ? 'yellow' %}
{{ include('@Doctrine/Collector/icon.svg') }}
{% if collector.querycount == 0 and collector.invalidEntityCount > 0 %}
<span class="sf-toolbar-value">{{ collector.invalidEntityCount }}</span>
<span class="sf-toolbar-label">errors</span>
{% else %}
<span class="sf-toolbar-value">{{ collector.querycount }}</span>
<span class="sf-toolbar-info-piece-additional-detail">
<span class="sf-toolbar-label">in</span>
<span class="sf-toolbar-value">{{ '%0.2f'|format(collector.time * 1000) }}</span>
<span class="sf-toolbar-label">ms</span>
</span>
{% endif %}
{% endset %}
{% set text %}
<div class="sf-toolbar-info-piece">
<b>Database Queries</b>
<span class="sf-toolbar-status {{ collector.querycount > 50 ? 'sf-toolbar-status-yellow' : '' }}">{{ collector.querycount }}</span>
</div>
<div class="sf-toolbar-info-piece">
<b>Different statements</b>
<span class="sf-toolbar-status">{{ collector.groupedQueryCount }}</span>
</div>
<div class="sf-toolbar-info-piece">
<b>Query time</b>
<span>{{ '%0.2f'|format(collector.time * 1000) }} ms</span>
</div>
<div class="sf-toolbar-info-piece">
<b>Invalid entities</b>
<span class="sf-toolbar-status {{ collector.invalidEntityCount > 0 ? 'sf-toolbar-status-red' : '' }}">{{ collector.invalidEntityCount }}</span>
</div>
{% if collector.cacheEnabled %}
<div class="sf-toolbar-info-piece">
<b>Cache hits</b>
<span class="sf-toolbar-status sf-toolbar-status-green">{{ collector.cacheHitsCount }}</span>
</div>
<div class="sf-toolbar-info-piece">
<b>Cache misses</b>
<span class="sf-toolbar-status {{ collector.cacheMissesCount > 0 ? 'sf-toolbar-status-yellow' : '' }}">{{ collector.cacheMissesCount }}</span>
</div>
<div class="sf-toolbar-info-piece">
<b>Cache puts</b>
<span class="sf-toolbar-status {{ collector.cachePutsCount > 0 ? 'sf-toolbar-status-yellow' : '' }}">{{ collector.cachePutsCount }}</span>
</div>
{% else %}
<div class="sf-toolbar-info-piece">
<b>Second Level Cache</b>
<span class="sf-toolbar-status">disabled</span>
</div>
{% endif %}
{% endset %}
{{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url, status: status|default('') }) }}
{% endif %}
{% endblock %}
{% block menu %}
<span class="label {{ collector.invalidEntityCount > 0 ? 'label-status-error' }} {{ collector.querycount == 0 ? 'disabled' }}">
<span class="icon">{{ include('@Doctrine/Collector/icon.svg') }}</span>
<strong>Doctrine</strong>
{% if collector.invalidEntityCount %}
<span class="count">
<span>{{ collector.invalidEntityCount }}</span>
</span>
{% endif %}
</span>
{% endblock %}
{% block panel %}
{% if 'explain' == page %}
{{ render(controller('Doctrine\\Bundle\\DoctrineBundle\\Controller\\ProfilerController::explainAction', {
token: token,
panel: 'db',
connectionName: request.query.get('connection'),
query: request.query.get('query')
})) }}
{% else %}
{{ block('queries') }}
{% endif %}
{% endblock %}
{% block queries %}
<style>
.time-container { position: relative; }
.time-container .nowrap { position: relative; z-index: 1; text-shadow: 0 0 2px #fff; }
.time-bar { display: block; position: absolute; top: 0; left: 0; bottom: 0; background: #e0e0e0; }
.sql-runnable.sf-toggle-content.sf-toggle-visible { display: flex; flex-direction: column; }
.sql-runnable button { align-self: end; }
</style>
<h2>Query Metrics</h2>
<div class="metrics">
<div class="metric">
<span class="value">{{ collector.querycount }}</span>
<span class="label">Database Queries</span>
</div>
<div class="metric">
<span class="value">{{ collector.groupedQueryCount }}</span>
<span class="label">Different statements</span>
</div>
<div class="metric">
<span class="value">{{ '%0.2f'|format(collector.time * 1000) }} ms</span>
<span class="label">Query time</span>
</div>
<div class="metric">
<span class="value">{{ collector.invalidEntityCount }}</span>
<span class="label">Invalid entities</span>
</div>
{% if collector.cacheEnabled %}
<div class="metric">
<span class="value">{{ collector.cacheHitsCount }}</span>
<span class="label">Cache hits</span>
</div>
<div class="metric">
<span class="value">{{ collector.cacheMissesCount }}</span>
<span class="label">Cache misses</span>
</div>
<div class="metric">
<span class="value">{{ collector.cachePutsCount }}</span>
<span class="label">Cache puts</span>
</div>
{% endif %}
</div>
{% set group_queries = request.query.getBoolean('group') %}
{% if group_queries %}
<h2>Grouped Statements</h2>
<p><a href="{{ path('_profiler', { panel: 'db', token: token }) }}">Show all queries</a></p>
{% else %}
<h2>Queries</h2>
<p><a href="{{ path('_profiler', { panel: 'db', token: token, group: true }) }}">Group similar statements</a></p>
{% endif %}
{% for connection, queries in collector.queries %}
{% if collector.connections|length > 1 %}
<h3>{{ connection }} <small>connection</small></h3>
{% endif %}
{% if queries is empty %}
<div class="empty">
<p>No database queries were performed.</p>
</div>
{% else %}
{% if group_queries %}
{% set queries = collector.groupedQueries[connection] %}
{% endif %}
<table class="alt queries-table">
<thead>
<tr>
{% if group_queries %}
<th class="nowrap" onclick="javascript:sortTable(this, 0, 'queries-{{ loop.index }}')" data-sort-direction="1" style="cursor: pointer;">Time<span class="text-muted">&#9660;</span></th>
<th class="nowrap" onclick="javascript:sortTable(this, 1, 'queries-{{ loop.index }}')" style="cursor: pointer;">Count<span></span></th>
{% else %}
<th class="nowrap" onclick="javascript:sortTable(this, 0, 'queries-{{ loop.index }}')" data-sort-direction="-1" style="cursor: pointer;">#<span class="text-muted">&#9650;</span></th>
<th class="nowrap" onclick="javascript:sortTable(this, 1, 'queries-{{ loop.index }}')" style="cursor: pointer;">Time<span></span></th>
{% endif %}
<th style="width: 100%;">Info</th>
</tr>
</thead>
<tbody id="queries-{{ loop.index }}">
{% for i, query in queries %}
{% set i = group_queries ? query.index : i %}
<tr id="queryNo-{{ i }}-{{ loop.parent.loop.index }}">
{% if group_queries %}
<td class="time-container">
<span class="time-bar" style="width:{{ '%0.2f'|format(query.executionPercent) }}%"></span>
<span class="nowrap">{{ '%0.2f'|format(query.executionMS * 1000) }}&nbsp;ms<br />({{ '%0.2f'|format(query.executionPercent) }}%)</span>
</td>
<td class="nowrap">{{ query.count }}</td>
{% else %}
<td class="nowrap">{{ loop.index }}</td>
<td class="nowrap">{{ '%0.2f'|format(query.executionMS * 1000) }}&nbsp;ms</td>
{% endif %}
<td>
{{ query.sql|doctrine_prettify_sql }}
<div>
<strong class="font-normal text-small">Parameters</strong>: {{ profiler_dump(query.params, 2) }}
</div>
<div class="text-small font-normal">
<a href="#" class="sf-toggle link-inverse" data-toggle-selector="#formatted-query-{{ i }}-{{ loop.parent.loop.index }}" data-toggle-alt-content="Hide formatted query">View formatted query</a>
{% if query.runnable %}
&nbsp;&nbsp;
<a href="#" class="sf-toggle link-inverse" data-toggle-selector="#original-query-{{ i }}-{{ loop.parent.loop.index }}" data-toggle-alt-content="Hide runnable query">View runnable query</a>
{% endif %}
{% if query.explainable %}
&nbsp;&nbsp;
<a class="link-inverse" href="{{ path('_profiler', { panel: 'db', token: token, page: 'explain', connection: connection, query: i }) }}" onclick="return explain(this);" data-target-id="explain-{{ i }}-{{ loop.parent.loop.index }}">Explain query</a>
{% endif %}
{% if query.backtrace is defined %}
&nbsp;&nbsp;
<a href="#" class="sf-toggle link-inverse" data-toggle-selector="#backtrace-{{ i }}-{{ loop.parent.loop.index }}" data-toggle-alt-content="Hide query backtrace">View query backtrace</a>
{% endif %}
</div>
<div id="formatted-query-{{ i }}-{{ loop.parent.loop.index }}" class="sql-runnable hidden">
{{ query.sql|doctrine_format_sql(highlight = true) }}
<button class="btn btn-sm hidden" data-clipboard-text="{{ query.sql|doctrine_format_sql(highlight = false)|e('html_attr') }}">Copy</button>
</div>
{% if query.runnable %}
<div id="original-query-{{ i }}-{{ loop.parent.loop.index }}" class="sql-runnable hidden">
{% set runnable_sql = (query.sql ~ ';')|doctrine_replace_query_parameters(query.params) %}
{{ runnable_sql|doctrine_prettify_sql }}
<button class="btn btn-sm hidden" data-clipboard-text="{{ runnable_sql|e('html_attr') }}">Copy</button>
</div>
{% endif %}
{% if query.explainable %}
<div id="explain-{{ i }}-{{ loop.parent.loop.index }}" class="sql-explain"></div>
{% endif %}
{% if query.backtrace is defined %}
<div id="backtrace-{{ i }}-{{ loop.parent.loop.index }}" class="hidden">
<table>
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">File/Call</th>
</tr>
</thead>
<tbody>
{% for trace in query.backtrace %}
<tr>
<td>{{ loop.index }}</td>
<td>
<span class="text-small">
{% set line_number = trace.line|default(1) %}
{% if trace.file is defined %}
<a href="{{ trace.file|file_link(line_number) }}">
{% endif %}
{{- trace.class|default ~ (trace.class is defined ? trace.type|default('::')) -}}
<span class="status-warning">{{ trace.function }}</span>
{% if trace.file is defined %}
</a>
{% endif %}
(line {{ line_number }})
</span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% endfor %}
<h2>Database Connections</h2>
{% if not collector.connections %}
<div class="empty">
<p>There are no configured database connections.</p>
</div>
{% else %}
{{ helper.render_simple_table('Name', 'Service', collector.connections) }}
{% endif %}
<h2>Entity Managers</h2>
{% if not collector.managers %}
<div class="empty">
<p>There are no configured entity managers.</p>
</div>
{% else %}
{{ helper.render_simple_table('Name', 'Service', collector.managers) }}
{% endif %}
<h2>Second Level Cache</h2>
{% if not collector.cacheEnabled %}
<div class="empty">
<p>Second Level Cache is not enabled.</p>
</div>
{% else %}
{% if not collector.cacheCounts %}
<div class="empty">
<p>Second level cache information is not available.</p>
</div>
{% else %}
<div class="metrics">
<div class="metric">
<span class="value">{{ collector.cacheCounts.hits }}</span>
<span class="label">Hits</span>
</div>
<div class="metric">
<span class="value">{{ collector.cacheCounts.misses }}</span>
<span class="label">Misses</span>
</div>
<div class="metric">
<span class="value">{{ collector.cacheCounts.puts }}</span>
<span class="label">Puts</span>
</div>
</div>
{% if collector.cacheRegions.hits %}
<h3>Number of cache hits</h3>
{{ helper.render_simple_table('Region', 'Hits', collector.cacheRegions.hits) }}
{% endif %}
{% if collector.cacheRegions.misses %}
<h3>Number of cache misses</h3>
{{ helper.render_simple_table('Region', 'Misses', collector.cacheRegions.misses) }}
{% endif %}
{% if collector.cacheRegions.puts %}
<h3>Number of cache puts</h3>
{{ helper.render_simple_table('Region', 'Puts', collector.cacheRegions.puts) }}
{% endif %}
{% endif %}
{% endif %}
{% if collector.entities|length > 0 %}
<h2>Entities Mapping</h2>
{% for manager, classes in collector.entities %}
{% if collector.managers|length > 1 %}
<h3>{{ manager }} <small>entity manager</small></h3>
{% endif %}
{% if classes is empty %}
<div class="empty">
<p>No loaded entities.</p>
</div>
{% else %}
<table>
<thead>
<tr>
<th scope="col">Class</th>
<th scope="col">Mapping errors</th>
</tr>
</thead>
<tbody>
{% for class in classes %}
{% set contains_errors = collector.mappingErrors[manager] is defined and collector.mappingErrors[manager][class] is defined %}
<tr class="{{ contains_errors ? 'status-error' }}">
<td>{{ class }}</td>
<td class="font-normal">
{% if contains_errors %}
<ul>
{% for error in collector.mappingErrors[manager][class] %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% else %}
No errors.
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% endfor %}
{% endif %}
<script type="text/javascript">//<![CDATA[
function explain(link) {
"use strict";
var targetId = link.getAttribute('data-target-id');
var targetElement = document.getElementById(targetId);
if (targetElement.style.display != 'block') {
Sfjs.load(targetId, link.href, null, function(xhr, el) {
el.innerHTML = 'An error occurred while loading the query explanation.';
});
targetElement.style.display = 'block';
link.innerHTML = 'Hide query explanation';
} else {
targetElement.style.display = 'none';
link.innerHTML = 'Explain query';
}
return false;
}
function sortTable(header, column, targetId) {
"use strict";
var direction = parseInt(header.getAttribute('data-sort-direction')) || 1,
items = [],
target = document.getElementById(targetId),
rows = target.children,
headers = header.parentElement.children,
i;
for (i = 0; i < rows.length; ++i) {
items.push(rows[i]);
}
for (i = 0; i < headers.length; ++i) {
headers[i].removeAttribute('data-sort-direction');
if (headers[i].children.length > 0) {
headers[i].children[0].innerHTML = '';
}
}
header.setAttribute('data-sort-direction', (-1*direction).toString());
header.children[0].innerHTML = direction > 0 ? '<span class="text-muted">&#9650;</span>' : '<span class="text-muted">&#9660;</span>';
items.sort(function(a, b) {
return direction * (parseFloat(a.children[column].innerHTML) - parseFloat(b.children[column].innerHTML));
});
for (i = 0; i < items.length; ++i) {
Sfjs.removeClass(items[i], i % 2 ? 'even' : 'odd');
Sfjs.addClass(items[i], i % 2 ? 'odd' : 'even');
target.appendChild(items[i]);
}
}
if (navigator.clipboard) {
document.querySelectorAll('[data-clipboard-text]').forEach(function(button) {
Sfjs.removeClass(button, 'hidden');
button.addEventListener('click', function() {
navigator.clipboard.writeText(button.getAttribute('data-clipboard-text'));
})
});
}
//]]></script>
{% endblock %}
{% macro render_simple_table(label1, label2, data) %}
<table>
<thead>
<tr>
<th scope="col" class="key">{{ label1 }}</th>
<th scope="col">{{ label2 }}</th>
</tr>
</thead>
<tbody>
{% for key, value in data %}
<tr>
<th scope="row">{{ key }}</th>
<td>{{ value }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endmacro %}

View File

@@ -0,0 +1,28 @@
{% if data[0]|length > 1 %}
{# The platform returns a table for the explanation (e.g. MySQL), display all columns #}
<table style="margin: 5px 0;">
<thead>
<tr>
{% for label in data[0]|keys %}
<th>{{ label }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in data %}
<tr>
{% for key, item in row %}
<td>{{ item|replace({',': ', '}) }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
{# The Platform returns a single column for a textual explanation (e.g. PostgreSQL), display all lines #}
<pre style="margin: 5px 0;">
{%- for row in data -%}
{{ row|first }}{{ "\n" }}
{%- endfor -%}
</pre>
{% endif %}

View File

@@ -0,0 +1,4 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="24" height="24" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
<path fill="#AAAAAA" d="M5,8h14c1.7,0,3-1.3,3-3s-1.3-3-3-3H5C3.3,2,2,3.3,2,5S3.3,8,5,8z M18,3.6c0.8,0,1.5,0.7,1.5,1.5S18.8,6.6,18,6.6s-1.5-0.7-1.5-1.5S17.2,3.6,18,3.6z M19,9H5c-1.7,0-3,1.3-3,3s1.3,3,3,3h14c1.7,0,3-1.3,3-3S20.7,9,19,9z M18,13.6
c-0.8,0-1.5-0.7-1.5-1.5s0.7-1.5,1.5-1.5s1.5,0.7,1.5,1.5S18.8,13.6,18,13.6z M19,16H5c-1.7,0-3,1.3-3,3s1.3,3,3,3h14c1.7,0,3-1.3,3-3S20.7,16,19,16z M18,20.6c-0.8,0-1.5-0.7-1.5-1.5s0.7-1.5,1.5-1.5s1.5,0.7,1.5,1.5S18.8,20.6,18,20.6z"/>
</svg>

After

Width:  |  Height:  |  Size: 659 B

View File

@@ -0,0 +1,206 @@
<?php
namespace Doctrine\Bundle\DoctrineBundle\Twig;
use Doctrine\SqlFormatter\HtmlHighlighter;
use Doctrine\SqlFormatter\NullHighlighter;
use Doctrine\SqlFormatter\SqlFormatter;
use Symfony\Component\VarDumper\Cloner\Data;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use function addslashes;
use function array_key_exists;
use function bin2hex;
use function implode;
use function is_array;
use function is_bool;
use function is_object;
use function is_string;
use function method_exists;
use function preg_match;
use function preg_replace_callback;
use function sprintf;
use function strtoupper;
use function substr;
use function trigger_deprecation;
/**
* This class contains the needed functions in order to do the query highlighting
*/
class DoctrineExtension extends AbstractExtension
{
/** @var SqlFormatter */
private $sqlFormatter;
/**
* Define our functions
*
* @return TwigFilter[]
*/
public function getFilters()
{
return [
new TwigFilter('doctrine_pretty_query', [$this, 'formatQuery'], ['is_safe' => ['html'], 'deprecated' => true]),
new TwigFilter('doctrine_prettify_sql', [$this, 'prettifySql'], ['is_safe' => ['html']]),
new TwigFilter('doctrine_format_sql', [$this, 'formatSql'], ['is_safe' => ['html']]),
new TwigFilter('doctrine_replace_query_parameters', [$this, 'replaceQueryParameters']),
];
}
/**
* Escape parameters of a SQL query
* DON'T USE THIS FUNCTION OUTSIDE ITS INTENDED SCOPE
*
* @internal
*
* @param mixed $parameter
*
* @return string
*/
public static function escapeFunction($parameter)
{
$result = $parameter;
switch (true) {
// Check if result is non-unicode string using PCRE_UTF8 modifier
case is_string($result) && ! preg_match('//u', $result):
$result = '0x' . strtoupper(bin2hex($result));
break;
case is_string($result):
$result = "'" . addslashes($result) . "'";
break;
case is_array($result):
foreach ($result as &$value) {
$value = static::escapeFunction($value);
}
$result = implode(', ', $result) ?: 'NULL';
break;
case is_object($result) && method_exists($result, '__toString'):
$result = addslashes($result->__toString());
break;
case $result === null:
$result = 'NULL';
break;
case is_bool($result):
$result = $result ? '1' : '0';
break;
}
return $result;
}
/**
* Return a query with the parameters replaced
*
* @param string $query
* @param mixed[]|Data $parameters
*
* @return string
*/
public function replaceQueryParameters($query, $parameters)
{
if ($parameters instanceof Data) {
$parameters = $parameters->getValue(true);
}
$i = 0;
if (! array_key_exists(0, $parameters) && array_key_exists(1, $parameters)) {
$i = 1;
}
return preg_replace_callback(
'/\?|((?<!:):[a-z0-9_]+)/i',
static function ($matches) use ($parameters, &$i) {
$key = substr($matches[0], 1);
if (! array_key_exists($i, $parameters) && ($key === false || ! array_key_exists($key, $parameters))) {
return $matches[0];
}
$value = array_key_exists($i, $parameters) ? $parameters[$i] : $parameters[$key];
$result = DoctrineExtension::escapeFunction($value);
$i++;
return $result;
},
$query
);
}
/**
* Formats and/or highlights the given SQL statement.
*
* @param string $sql
* @param bool $highlightOnly If true the query is not formatted, just highlighted
*
* @return string
*/
public function formatQuery($sql, $highlightOnly = false)
{
trigger_deprecation(
'doctrine/doctrine-bundle',
'2.1',
'The "%s()" method is deprecated and will be removed in doctrine-bundle 3.0.',
__METHOD__
);
$this->setUpSqlFormatter(true, true);
if ($highlightOnly) {
return $this->sqlFormatter->highlight($sql);
}
return sprintf(
'<div class="highlight highlight-sql"><pre>%s</pre></div>',
$this->sqlFormatter->format($sql)
);
}
public function prettifySql(string $sql): string
{
$this->setUpSqlFormatter();
return $this->sqlFormatter->highlight($sql);
}
public function formatSql(string $sql, bool $highlight): string
{
$this->setUpSqlFormatter($highlight);
return $this->sqlFormatter->format($sql);
}
private function setUpSqlFormatter(bool $highlight = true, bool $legacy = false): void
{
$this->sqlFormatter = new SqlFormatter($highlight ? new HtmlHighlighter([
HtmlHighlighter::HIGHLIGHT_PRE => 'class="highlight highlight-sql"',
HtmlHighlighter::HIGHLIGHT_QUOTE => 'class="string"',
HtmlHighlighter::HIGHLIGHT_BACKTICK_QUOTE => 'class="string"',
HtmlHighlighter::HIGHLIGHT_RESERVED => 'class="keyword"',
HtmlHighlighter::HIGHLIGHT_BOUNDARY => 'class="symbol"',
HtmlHighlighter::HIGHLIGHT_NUMBER => 'class="number"',
HtmlHighlighter::HIGHLIGHT_WORD => 'class="word"',
HtmlHighlighter::HIGHLIGHT_ERROR => 'class="error"',
HtmlHighlighter::HIGHLIGHT_COMMENT => 'class="comment"',
HtmlHighlighter::HIGHLIGHT_VARIABLE => 'class="variable"',
], ! $legacy) : new NullHighlighter());
}
/**
* Get the name of the extension
*
* @return string
*/
public function getName()
{
return 'doctrine_extension';
}
}

View File

@@ -0,0 +1,31 @@
<?xml version="1.0"?>
<ruleset>
<arg name="basepath" value="."/>
<arg name="extensions" value="php"/>
<arg name="parallel" value="80"/>
<arg name="cache" value=".phpcs-cache"/>
<arg name="colors"/>
<!-- Ignore warnings, show progress of the run and show sniff names -->
<arg value="nps"/>
<config name="php_version" value="70100"/>
<file>.</file>
<exclude-pattern>vendor/*</exclude-pattern>
<rule ref="Doctrine">
<exclude name="SlevomatCodingStandard.TypeHints.DeclareStrictTypes"/>
<exclude name="SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint"/> <!-- we can do it in doctrine-bundle 3.0-->
<exclude name="SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint"/> <!-- we can do it in doctrine-bundle 3.0 -->
<exclude name="SlevomatCodingStandard.Classes.SuperfluousInterfaceNaming.SuperfluousSuffix"/>
<exclude name="SlevomatCodingStandard.Classes.SuperfluousAbstractClassNaming.SuperfluousPrefix"/>
</rule>
<rule ref="PSR1.Classes.ClassDeclaration.MultipleClasses">
<exclude-pattern>Tests/*</exclude-pattern>
</rule>
<rule ref="Squiz.Classes.ClassFileName.NoMatch">
<exclude-pattern>Tests/*</exclude-pattern>
</rule>
</ruleset>

View File

@@ -0,0 +1,57 @@
<?xml version="1.0"?>
<psalm
errorLevel="4"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<plugins>
<pluginClass class="Psalm\SymfonyPsalmPlugin\Plugin"/>
<pluginClass class="Psalm\PhpUnitPlugin\Plugin"/>
</plugins>
<projectFiles>
<ignoreFiles>
<directory name="vendor"/>
<!-- Deprecated classes, not worth fixing -->
<file name="Command/ImportMappingDoctrineCommand.php"/>
<file name="DependencyInjection/Compiler/WellKnownSchemaFilterPass.php"/>
</ignoreFiles>
<directory name="CacheWarmer"/>
<directory name="Command"/>
<directory name="Controller"/>
<directory name="DataCollector"/>
<directory name="Dbal"/>
<directory name="DependencyInjection"/>
<directory name="EventSubscriber"/>
<directory name="Mapping"/>
<directory name="Repository"/>
<directory name="Tests"/>
<directory name="Twig"/>
<file name="ConnectionFactory.php"/>
<file name="DoctrineBundle.php"/>
<file name="ManagerConfigurator.php"/>
<file name="Registry.php"/>
</projectFiles>
<issueHandlers>
<InvalidArrayOffset>
<errorLevel type="suppress">
<!-- requires a release of https://github.com/doctrine/dbal/pull/5261 -->
<file name="Tests/ConnectionFactoryTest.php"/>
</errorLevel>
</InvalidArrayOffset>
<UndefinedClass>
<errorLevel type="suppress">
<!-- We use the "Foo" namespace in unit tests. We are aware that those classes don't exist. -->
<referencedClass name="Foo\*"/>
</errorLevel>
</UndefinedClass>
<UndefinedDocblockClass>
<errorLevel type="suppress">
<!-- https://github.com/symfony/symfony/issues/45609 -->
<referencedClass name="UnitEnum" />
<directory name="DependencyInjection"/>
<directory name="Tests/DependencyInjection"/>
</errorLevel>
</UndefinedDocblockClass>
</issueHandlers>
</psalm>