Commit 4a5bb42e authored by Bernhard Schussek's avatar Bernhard Schussek
Browse files

Initial commit

parents
<?php
/*
* This file is part of the ÖWM API.
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Cwd\DataDoctrineORMBundle\Auditing;
use Cwd\DataBundle\Auditing\RecordsChangeTime;
use Cwd\DataBundle\Auditing\RecordsCreationTime;
use DateTimeImmutable;
use DateTimeZone;
use Doctrine\ORM\Event\LifecycleEventArgs;
class ORMChangeTimeRecorder
{
public function prePersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$now = self::nowWithMilliseconds();
if ($entity instanceof RecordsCreationTime) {
$entity->setCreatedAt($now, $override = false);
}
if ($entity instanceof RecordsChangeTime) {
$entity->setUpdatedAt($now, $override = false);
}
}
public function preUpdate(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if ($entity instanceof RecordsChangeTime) {
$entity->setUpdatedAt(self::nowWithMilliseconds(), $override = false);
}
}
private static function nowWithMilliseconds(): DateTimeImmutable
{
$time = sprintf('%.6f', microtime(true));
return DateTimeImmutable::createFromFormat('U.u', $time)
->setTimezone(new DateTimeZone(date_default_timezone_get()));
}
}
<?php
/*
* This file is part of the ÖWM API.
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Cwd\DataDoctrineORMBundle;
use Cwd\DataBundle\DependencyInjection\Compiler\AutoDiscoverRepositoryImplementationsPass;
use Cwd\DataBundle\DependencyInjection\Compiler\GenerateRepositoryClassesPass;
use Cwd\DataDoctrineORMBundle\DependencyInjection\Compiler\RegisterFunctionsPass;
use Cwd\DataDoctrineORMBundle\DependencyInjection\Compiler\RegisterTypesPass;
use Cwd\DataDoctrineORMBundle\DependencyInjection\CwdDataDoctrineORMExtension;
use Cwd\DataDoctrineORMBundle\DependencyInjection\ORMRepositoryServiceDefinitionFactory;
use Cwd\DataDoctrineORMBundle\Generator\ORMRepositoryClassGenerator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class CwdDataDoctrineORMBundle extends Bundle
{
public function getContainerExtension()
{
return new CwdDataDoctrineORMExtension('cwd_data_doctrine_orm');
}
public function build(ContainerBuilder $container)
{
$container->addCompilerPass(new GenerateRepositoryClassesPass(
ORMRepositoryClassGenerator::class
));
$container->addCompilerPass(new AutoDiscoverRepositoryImplementationsPass(
'cwd_data_doctrine_orm.implementation_dirs',
ORMRepositoryServiceDefinitionFactory::class
));
$container->addCompilerPass(new RegisterTypesPass());
$container->addCompilerPass(new RegisterFunctionsPass());
}
}
<?php
/*
* This file is part of the ÖWM API.
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Cwd\DataDoctrineORMBundle\DependencyInjection\Compiler;
use Cwd\DataBundle\Util\StringUtil;
use Doctrine\Common\Util\Inflector;
use Doctrine\DBAL\Types\Type;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;
use Webmozart\Assert\Assert;
final class RegisterFunctionsPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $containerBuilder): void
{
if (!$containerBuilder->hasParameter('cwd_data_doctrine_orm.string_function_dirs')
|| !$containerBuilder->hasParameter('cwd_data_doctrine_orm.numeric_function_dirs')) {
return;
}
$entityManager = $containerBuilder->getParameter('cwd_data_doctrine_orm.entity_manager');
$ormConfigDef = $containerBuilder->getDefinition(sprintf('doctrine.orm.%s_configuration', $entityManager));
$stringFunctionDirs = $containerBuilder->getParameter('cwd_data_doctrine_orm.string_function_dirs');
$numericFunctionDirs = $containerBuilder->getParameter('cwd_data_doctrine_orm.numeric_function_dirs');
$stringFunctionFiles = Finder::create()->files()->in(array_merge([
__DIR__.'/../../Func/Postgresql/String',
], $stringFunctionDirs));
$numericFunctionFiles = Finder::create()->files()->in(array_merge([
__DIR__.'/../../Func/Postgresql/Numeric',
], $numericFunctionDirs));
$processFunction = function (string $addFunctionMethod) use ($ormConfigDef, $containerBuilder) {
return function (SplFileInfo $file) use ($addFunctionMethod, $ormConfigDef, $containerBuilder) {
/** @var SplFileInfo $file */
$className = $this->getFunctionClassName($file);
if (null === $className) {
return;
}
$containerBuilder->addResource(new FileResource($file->getPathname()));
$name = Inflector::tableize(StringUtil::getShortClassName($className));
$ormConfigDef->addMethodCall($addFunctionMethod, [$name, $className]);
};
};
foreach ($stringFunctionFiles as $file) {
$processFunction('addCustomStringFunction')($file);
}
foreach ($numericFunctionFiles as $file) {
$processFunction('addCustomNumericFunction')($file);
}
}
/**
* Retrieves the type class name without loading the file.
*/
private function getFunctionClassName(SplFileInfo $file): ?string
{
$fileContents = $file->getContents();
Assert::same(
preg_match('/^namespace (?<namespace>[\p{L}\\\\u\_]+);\s*$/mu', $fileContents, $matches),
1
);
$namespace = $matches['namespace'];
if (1 !== preg_match('/^(?:final )?class (?<class>[\p{L}\\\\_]+) ?.*$/mu', $fileContents, $matches)) {
// May be an interface or an abstract class
return null;
}
$shortClassName = $matches['class'];
return $namespace.'\\'.$shortClassName;
}
}
<?php
/*
* This file is part of the ÖWM API.
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Cwd\DataDoctrineORMBundle\DependencyInjection\Compiler;
use Doctrine\DBAL\Types\Type;
use ReflectionClass;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;
use Webmozart\Assert\Assert;
final class RegisterTypesPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $containerBuilder): void
{
if (!$containerBuilder->hasParameter('cwd_data_doctrine_orm.type_dirs')) {
return;
}
$typeDirs = $containerBuilder->getParameter('cwd_data_doctrine_orm.type_dirs');
$files = Finder::create()->files()->in(array_merge([
__DIR__.'/../../Type',
], $typeDirs));
$typeClasses = [];
foreach ($files as $file) {
/** @var SplFileInfo $file */
$className = $this->getTypeClassName($file);
if (null === $className) {
continue;
}
$containerBuilder->addResource(new FileResource($file->getPathname()));
$typeClasses[] = $className;
}
$doctrineTypes = $this->createDoctrineTypes(...$typeClasses);
$containerBuilder->setParameter(
'doctrine.dbal.connection_factory.types',
// Merge the result: if a type is registered manually then it takes precedence over the auto-discovered type
array_merge(
$doctrineTypes,
$containerBuilder->getParameter('doctrine.dbal.connection_factory.types')
)
);
}
private function createDoctrineTypes(?string ...$typeClasses): array
{
$doctrineTypes = [];
$typeClasses = array_unique(array_filter($typeClasses));
foreach ($typeClasses as $typeClass) {
/** @var Type $type */
$type = (new ReflectionClass($typeClass))->newInstanceWithoutConstructor();
Assert::isInstanceOf($type, Type::class);
$doctrineTypes[$type->getName()] = [
'class' => $typeClass,
'commented' => true,
];
}
Assert::same(
count($typeClasses),
count($doctrineTypes),
'Expected to have as many doctrine types registered as doctrine type classes. Check that two types'
.' do not have the same name.'
);
return $doctrineTypes;
}
/**
* Retrieves the type class name without loading the file.
*/
private function getTypeClassName(SplFileInfo $file): ?string
{
$fileContents = $file->getContents();
Assert::same(
preg_match('/^namespace (?<namespace>[\p{L}\\\\u\_]+);\s*$/mu', $fileContents, $matches),
1
);
$namespace = $matches['namespace'];
if (1 !== preg_match('/^(?:final )?class (?<class>[\p{L}\\\\_]+) ?.*$/mu', $fileContents, $matches)) {
// May be an interface or an abstract class
return null;
}
$shortClassName = $matches['class'];
return $namespace.'\\'.$shortClassName;
}
}
<?php
/*
* This file is part of the ÖWM API.
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Cwd\DataDoctrineORMBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface
{
private $alias;
public function __construct($alias)
{
$this->alias = $alias;
}
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root($this->alias);
$rootNode
->addDefaultsIfNotSet()
->children()
->booleanNode('enabled')
->defaultFalse()
->end()
->scalarNode('entity_manager')
->defaultValue('default')
->end()
->arrayNode('implementation_dirs')
->scalarPrototype()->end()
->end()
->arrayNode('type_dirs')
->scalarPrototype()->end()
->end()
->arrayNode('string_function_dirs')
->scalarPrototype()->end()
->end()
->arrayNode('numeric_function_dirs')
->scalarPrototype()->end()
->end()
->end()
;
return $treeBuilder;
}
}
<?php
/*
* This file is part of the ÖWM API.
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Cwd\DataDoctrineORMBundle\DependencyInjection;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\ConfigurableExtension;
class CwdDataDoctrineORMExtension extends ConfigurableExtension
{
/**
* @var string
*/
private $alias;
public function __construct($alias)
{
$this->alias = $alias;
}
public function getAlias()
{
return $this->alias;
}
public function getConfiguration(array $config, ContainerBuilder $container)
{
return new Configuration($this->alias);
}
protected function loadInternal(array $mergedConfig, ContainerBuilder $container)
{
if (!$mergedConfig['enabled']) {
return;
}
$container->setParameter(
'cwd_data_doctrine_orm.entity_manager',
$mergedConfig['entity_manager']
);
$container->setParameter(
'cwd_data_doctrine_orm.implementation_dirs',
$mergedConfig['implementation_dirs']
);
$container->setParameter(
'cwd_data_doctrine_orm.type_dirs',
$mergedConfig['type_dirs']
);
$container->setParameter(
'cwd_data_doctrine_orm.string_function_dirs',
$mergedConfig['string_function_dirs']
);
$container->setParameter(
'cwd_data_doctrine_orm.numeric_function_dirs',
$mergedConfig['numeric_function_dirs']
);
$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
}
}
<?php
/*
* This file is part of the ÖWM API.
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Cwd\DataDoctrineORMBundle\DependencyInjection;
use Cwd\DataBundle\DependencyInjection\ServiceDefinitionFactory;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\ExpressionLanguage\Expression;
class ORMRepositoryServiceDefinitionFactory implements ServiceDefinitionFactory
{
public function createServiceDefinition(
string $className,
string $interfaceName,
string $aggregateClass,
string $aggregateId
): Definition {
$definition = new Definition($className, [$aggregateClass]);
$definition->setFactory([
new Expression(sprintf(
'service("doctrine").getManagerForClass("%s")',
addslashes($aggregateClass)
)),
'getRepository',
]);
return $definition;
}
}
<?php
/*
* This file is part of the ÖWM API.
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Cwd\DataDoctrineORMBundle\Driver;
use Doctrine\DBAL\Driver\PDOPgSql\Driver;
/**
* Reuses a PDO connection as long as the process is running.
*
* This driver is used for testing. An alternative could have been to use a
* persistent PDO, but PDO::ATTR_PERSISTENT cannot be used in combination with
* PDO::ATTR_STATEMENT_CLASS, which is automatically set by Doctrine in
* PDOConnection (the result is an exception).
*
* Since we don't need "real" persistent connections (a connection that is
* reused between processes) this solution suffices.
*/
class SharedPDOPgSqlDriver extends Driver
{
private static $pdo;
public function connect(array $params, $username = null, $password = null, array $driverOptions = [])
{
if (null === static::$pdo) {
static::$pdo = parent::connect($params, $username, $password, $driverOptions);
}
return static::$pdo;
}
}
<?php
/*
* This file is part of the ÖWM API.
*
* (c) 2016-2018 cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Cwd\DataDoctrineORMBundle\Func\Postgresql\Numeric;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\AST\PathExpression;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
/**
* Implements PosgreSQL's array_length() function for DQL.
*/
class ArrayLength extends FunctionNode
{
/**
* @var PathExpression
*/
public $stringPrimary;
/**
* @var PathExpression
*/
public $arithmeticPrimary;
/**
* {@inheritdoc}
*/
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->stringPrimary = $parser->StringPrimary();
$parser->match(Lexer::T_COMMA);
$this->arithmeticPrimary = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
/**
* {@inheritdoc}
*/
public function getSql(SqlWalker $sqlWalker)
{
return sprintf(
'array_length(%s, %s)',
$this->stringPrimary->dispatch($sqlWalker),
$this->arithmeticPrimary->dispatch($sqlWalker)
);
}
}