From 0a6596518706d2307fedbe3e32b00774637531a2 Mon Sep 17 00:00:00 2001 From: Kevin Papst Date: Tue, 16 Jan 2018 21:35:23 +0100 Subject: [PATCH] load doctrine extensions during kernel bootstrap #84 (#92) --- .env.dist | 1 - README.md | 3 +- .../packages/doctrine_extensions_mysql.yaml | 86 -------------- .../packages/doctrine_extensions_sqlite.yaml | 50 -------- config/packages/security.yaml | 3 +- config/services.yaml | 2 +- .../Compiler/DoctrineCompilerPass.php | 112 ++++++++++++++---- .../TimesheetSubscriber.php} | 4 +- src/Kernel.php | 5 +- src/Repository/Query/TimesheetQuery.php | 13 +- src/Repository/UserRepository.php | 19 ++- 11 files changed, 126 insertions(+), 172 deletions(-) delete mode 100644 config/packages/doctrine_extensions_mysql.yaml delete mode 100644 config/packages/doctrine_extensions_sqlite.yaml rename src/{EventListener/TimesheetListener.php => Doctrine/TimesheetSubscriber.php} (96%) diff --git a/.env.dist b/.env.dist index 8ed50a77..80f46b9f 100644 --- a/.env.dist +++ b/.env.dist @@ -4,7 +4,6 @@ # KIMAI DEFAULT ENV VARS DATABASE_PREFIX=kimai2_ -DATABASE_ENGINE=sqlite ###> symfony/framework-bundle ### APP_ENV=dev diff --git a/README.md b/README.md index a13a1c24..0bbb24f7 100644 --- a/README.md +++ b/README.md @@ -82,10 +82,9 @@ You can configure your database through your environment (e.g. Webserver, Cloud- $ cp .env.dist .env ``` -You have to adjust the following ENV values to your needs: +You can adjust the following ENV values to your needs: ``` DATABASE_PREFIX=kimai2_ -DATABASE_ENGINE=sqlite DATABASE_URL=sqlite:///%kernel.project_dir%/var/data/kimai.sqlite APP_ENV=dev APP_SECRET=some_random_secret_string_for_your_installation diff --git a/config/packages/doctrine_extensions_mysql.yaml b/config/packages/doctrine_extensions_mysql.yaml deleted file mode 100644 index 3006ff33..00000000 --- a/config/packages/doctrine_extensions_mysql.yaml +++ /dev/null @@ -1,86 +0,0 @@ -doctrine: - orm: - dql: - datetime_functions: - convert_tz: DoctrineExtensions\Query\Mysql\ConvertTz - date: DoctrineExtensions\Query\Mysql\Date - date_format: DoctrineExtensions\Query\Mysql\DateFormat - dateadd: DoctrineExtensions\Query\Mysql\DateAdd - datesub: DoctrineExtensions\Query\Mysql\DateSub - datediff: DoctrineExtensions\Query\Mysql\DateDiff - day: DoctrineExtensions\Query\Mysql\Day - dayname: DoctrineExtensions\Query\Mysql\DayName - dayofweek: DoctrineExtensions\Query\Mysql\DayOfWeek - dayofyear: DoctrineExtensions\Query\Mysql\DayOfYear - div: DoctrineExtensions\Query\Mysql\Div - from_unixtime: DoctrineExtensions\Query\Mysql\FromUnixtime - last_day: DoctrineExtensions\Query\Mysql\LastDay - minute: DoctrineExtensions\Query\Mysql\Minute - now: DoctrineExtensions\Query\Mysql\Now - month: DoctrineExtensions\Query\Mysql\Month - monthname: DoctrineExtensions\Query\Mysql\MonthName - second: DoctrineExtensions\Query\Mysql\Second - strtodate: DoctrineExtensions\Query\Mysql\StrToDate - time: DoctrineExtensions\Query\Mysql\Time - timediff: DoctrineExtensions\Query\Mysql\TimeDiff - timestampadd: DoctrineExtensions\Query\Mysql\TimestampAdd - timestampdiff: DoctrineExtensions\Query\Mysql\TimestampDiff - timetosec: DoctrineExtensions\Query\Mysql\TimeToSec - week: DoctrineExtensions\Query\Mysql\Week - weekday: DoctrineExtensions\Query\Mysql\WeekDay - year: DoctrineExtensions\Query\Mysql\Year - yearweek: DoctrineExtensions\Query\Mysql\YearWeek - unix_timestamp: DoctrineExtensions\Query\Mysql\UnixTimestamp - - numeric_functions: - acos: DoctrineExtensions\Query\Mysql\Acos - asin: DoctrineExtensions\Query\Mysql\Asin - atan2: DoctrineExtensions\Query\Mysql\Atan2 - atan: DoctrineExtensions\Query\Mysql\Atan - bit_count: DoctrineExtensions\Query\Mysql\BitCount - bit_xor: DoctrineExtensions\Query\Mysql\BitXor - ceil: DoctrineExtensions\Query\Mysql\Ceil - cos: DoctrineExtensions\Query\Mysql\Cos - cot: DoctrineExtensions\Query\Mysql\Cot - floor: DoctrineExtensions\Query\Mysql\Floor - hour: DoctrineExtensions\Query\Mysql\Hour - pi: DoctrineExtensions\Query\Mysql\Pi - power: DoctrineExtensions\Query\Mysql\Power - quarter: DoctrineExtensions\Query\Mysql\Quarter - rand: DoctrineExtensions\Query\Mysql\Rand - round: DoctrineExtensions\Query\Mysql\Round - stddev: DoctrineExtensions\Query\Mysql\StdDev - sin: DoctrineExtensions\Query\Mysql\Sin - std: DoctrineExtensions\Query\Mysql\Std - tan: DoctrineExtensions\Query\Mysql\Tan - - string_functions: - ascii: DoctrineExtensions\Query\Mysql\Ascii - binary: DoctrineExtensions\Query\Mysql\Binary - char_length: DoctrineExtensions\Query\Mysql\CharLength - concat_ws: DoctrineExtensions\Query\Mysql\ConcatWs - countif: DoctrineExtensions\Query\Mysql\CountIf - crc32: DoctrineExtensions\Query\Mysql\Crc32 - degrees: DoctrineExtensions\Query\Mysql\Degrees - field: DoctrineExtensions\Query\Mysql\Field - find_in_set: DoctrineExtensions\Query\Mysql\FindInSet - greatest: DoctrineExtensions\Query\Mysql\Greatest - group_concat: DoctrineExtensions\Query\Mysql\GroupConcat - ifelse: DoctrineExtensions\Query\Mysql\IfElse - ifnull: DoctrineExtensions\Query\Mysql\IfNull - least: DoctrineExtensions\Query\Mysql\Least - lpad: DoctrineExtensions\Query\Mysql\Lpad - match_against: DoctrineExtensions\Query\Mysql\MatchAgainst - md5: DoctrineExtensions\Query\Mysql\Md5 - nullif: DoctrineExtensions\Query\Mysql\NullIf - radians: DoctrineExtensions\Query\Mysql\Radians - regexp: DoctrineExtensions\Query\Mysql\Regexp - replace: DoctrineExtensions\Query\Mysql\Replace - rpad: DoctrineExtensions\Query\Mysql\Rpad - sha1: DoctrineExtensions\Query\Mysql\Sha1 - sha2: DoctrineExtensions\Query\Mysql\Sha2 - soundex: DoctrineExtensions\Query\Mysql\Soundex - substring_index: DoctrineExtensions\Query\Mysql\SubstringIndex - uuid_short: DoctrineExtensions\Query\Mysql\UuidShort - hex: DoctrineExtensions\Query\Mysql\Hex - unhex: DoctrineExtensions\Query\Mysql\Unhex diff --git a/config/packages/doctrine_extensions_sqlite.yaml b/config/packages/doctrine_extensions_sqlite.yaml deleted file mode 100644 index db315817..00000000 --- a/config/packages/doctrine_extensions_sqlite.yaml +++ /dev/null @@ -1,50 +0,0 @@ -doctrine: - orm: - dql: - datetime_functions: - date: DoctrineExtensions\Query\Sqlite\Date - date_format: DoctrineExtensions\Query\Sqlite\DateFormat - #dateadd: DoctrineExtensions\Query\Sqlite\DateAdd - #datediff: DoctrineExtensions\Query\Sqlite\DateDiff - day: DoctrineExtensions\Query\Sqlite\Day - dayname: DoctrineExtensions\Query\Sqlite\DayName - minute: DoctrineExtensions\Query\Sqlite\Minute - #month: DoctrineExtensions\Query\Sqlite\Month - #monthname: DoctrineExtensions\Query\Sqlite\MonthName - second: DoctrineExtensions\Query\Sqlite\Second - month: DoctrineExtensions\Query\Sqlite\Month - strftime: DoctrineExtensions\Query\Sqlite\StrfTime - #strtodate: DoctrineExtensions\Query\Sqlite\StrToDate - #time: DoctrineExtensions\Query\Sqlite\Time - #timestampadd: DoctrineExtensions\Query\Sqlite\TimestampAdd - #timestampdiff: DoctrineExtensions\Query\Sqlite\TimestampDiff - week: DoctrineExtensions\Query\Sqlite\Week - weekday: DoctrineExtensions\Query\Sqlite\WeekDay - year: DoctrineExtensions\Query\Sqlite\Year - - - string_functions: -# binary: DoctrineExtensions\Query\Sqlite\Binary -# char_length: DoctrineExtensions\Query\Sqlite\CharLength - concat_ws: DoctrineExtensions\Query\Sqlite\ConcatWs -# countif: DoctrineExtensions\Query\Sqlite\CountIf -# crc32: DoctrineExtensions\Query\Sqlite\Crc32 -# degrees: DoctrineExtensions\Query\Sqlite\Degrees -# field: DoctrineExtensions\Query\Sqlite\Field -# find_in_set: DoctrineExtensions\Query\Sqlite\FindInSet -# group_concat: DoctrineExtensions\Query\Sqlite\GroupConcat -# ifelse: DoctrineExtensions\Query\Sqlite\IfElse - ifnull: DoctrineExtensions\Query\Sqlite\IfNull -# match_against: DoctrineExtensions\Query\Sqlite\MatchAgainst -# md5: DoctrineExtensions\Query\Sqlite\Md5 -# nullif: DoctrineExtensions\Query\Sqlite\NullIf -# radians: DoctrineExtensions\Query\Sqlite\Radians -# regexp: DoctrineExtensions\Query\Sqlite\Regexp - replace: DoctrineExtensions\Query\Sqlite\Replace -# sha1: DoctrineExtensions\Query\Sqlite\Sha1 -# sha2: DoctrineExtensions\Query\Sqlite\Sha2 -# soundex: DoctrineExtensions\Query\Sqlite\Soundex -# uuid_short: DoctrineExtensions\Query\Sqlite\UuidShort - - numeric_functions: - round: DoctrineExtensions\Query\Sqlite\Round diff --git a/config/packages/security.yaml b/config/packages/security.yaml index dcf6ecaf..04680e58 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -5,7 +5,8 @@ security: # https://symfony.com/doc/current/book/security.html#where-do-users-come-from-user-providers providers: database_users: - entity: { class: App\Entity\User, property: username } + entity: + class: App\Entity\User firewalls: dev: diff --git a/config/services.yaml b/config/services.yaml index be004bf9..59548e78 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -59,7 +59,7 @@ services: # updates timesheet records, to make sure they are calculated and can't be manipulated by the users app.database_listener.timesheet: - class: App\EventListener\TimesheetListener + class: App\Doctrine\TimesheetSubscriber tags: - { name: doctrine.event_listener, event: prePersist, lazy: true } - { name: doctrine.event_listener, event: preUpdate, lazy: true } diff --git a/src/DependencyInjection/Compiler/DoctrineCompilerPass.php b/src/DependencyInjection/Compiler/DoctrineCompilerPass.php index 9aa15708..a397c7d7 100644 --- a/src/DependencyInjection/Compiler/DoctrineCompilerPass.php +++ b/src/DependencyInjection/Compiler/DoctrineCompilerPass.php @@ -24,37 +24,107 @@ use Symfony\Component\Yaml\Yaml; class DoctrineCompilerPass implements CompilerPassInterface { + /** + * @var string[] + */ + protected $allowedEngines = [ + 'mysql', + 'oracle', + 'postgres', + 'sqlite' + ]; + /** * @param ContainerBuilder $container + * @return array|false|null|string + * @throws \Exception + */ + protected function findEngine(ContainerBuilder $container) + { + $engine = null; + + // TODO - this does return the wrong connection. it used to be mysql, even if + // TODO - getenv('DATABASE_URL') returned an sqlite:// connection string + /* + $dbConfig = $container->getExtensionConfig('doctrine'); + if (isset($dbConfig[0]['dbal']['driver'])) { + $engine = str_replace('pdo_', '', $dbConfig[0]['dbal']['driver']); + } + */ + + if ($engine === null) { + $dbConfig = explode('://', getenv('DATABASE_URL')); + $engine = $dbConfig['0'] ?: null; + } + + if ($engine === null) { + $engine = getenv('DATABASE_ENGINE'); + } + + if ($engine === null) { + throw new \Exception( + 'Could not detect database engine. Please set the environment config DATABASE_ENGINE ' . + 'to one ' . implode(', ', $this->allowedEngines) . ', e.g. in your .env file: DATABASE_ENGINE=sqlite' + ); + } + + if (!in_array($engine, $this->allowedEngines)) { + throw new \Exception( + 'Unsupported database engine: ' . $engine . '. Kimai only supports one of: ' . + implode(', ', $this->allowedEngines) + ); + } + + return $engine; + } + + /** + * @param ContainerBuilder $container + * @return string + * @throws \Exception + */ + protected function getConfigFile(ContainerBuilder $container) + { + $engine = $this->findEngine($container); + + $configDir = realpath( + $container->getParameter('kernel.project_dir') . '/vendor/beberlei/DoctrineExtensions/config/' + ); + + $configFile = $configDir . '/' . $engine . '.yml'; + + if (!file_exists($configFile)) { + throw new \Exception('Could not find config file for database engine. Looked at ' . $configFile); + } + + return $configFile; + } + + /** + * @param ContainerBuilder $container + * @throws \Exception */ public function process(ContainerBuilder $container) { - $engine = $container->getParameter('database_engine'); - if (null === $engine) { - throw new ParameterNotFoundException('database_engine'); - } + $configFile = $this->getConfigFile($container); + $config = Yaml::parse(file_get_contents($configFile)); - $ormConfigDef = $container->getDefinition('doctrine.orm.default_configuration'); - - $configDir = realpath($container->getParameter('kernel.root_dir') . '/config/'); - $config = Yaml::parse(file_get_contents($configDir . '/' . $engine . '.yml')); - - if (!isset($config['doctrine']['orm']['dql'])) { - return; + if (!isset($config['doctrine']['orm']['dql']) || empty($config['doctrine']['orm']['dql'])) { + throw new \Exception('could not load custom Doctrine functions from: ' . $configFile); } $sql = $config['doctrine']['orm']['dql']; - if (!empty($sql)) { - foreach ($sql['string_functions'] as $name => $function) { - $ormConfigDef->addMethodCall('addCustomStringFunction', array($name, $function)); - } - foreach ($sql['numeric_functions'] as $name => $function) { - $ormConfigDef->addMethodCall('addCustomNumericFunction', array($name, $function)); - } - foreach ($sql['datetime_functions'] as $name => $function) { - $ormConfigDef->addMethodCall('addCustomDatetimeFunction', array($name, $function)); - } + $ormConfig = $container->getDefinition('doctrine.orm.default_configuration'); + + foreach ($sql['string_functions'] as $name => $function) { + $ormConfig->addMethodCall('addCustomStringFunction', array($name, $function)); + } + foreach ($sql['numeric_functions'] as $name => $function) { + $ormConfig->addMethodCall('addCustomNumericFunction', array($name, $function)); + } + foreach ($sql['datetime_functions'] as $name => $function) { + $ormConfig->addMethodCall('addCustomDatetimeFunction', array($name, $function)); } } } diff --git a/src/EventListener/TimesheetListener.php b/src/Doctrine/TimesheetSubscriber.php similarity index 96% rename from src/EventListener/TimesheetListener.php rename to src/Doctrine/TimesheetSubscriber.php index 43144c25..635af357 100644 --- a/src/EventListener/TimesheetListener.php +++ b/src/Doctrine/TimesheetSubscriber.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace App\EventListener; +namespace App\Doctrine; use App\Entity\UserPreference; use Doctrine\Common\EventSubscriber; @@ -22,7 +22,7 @@ use App\Entity\Timesheet; * * @author Kevin Papst */ -class TimesheetListener implements EventSubscriber +class TimesheetSubscriber implements EventSubscriber { /** diff --git a/src/Kernel.php b/src/Kernel.php index 4585c15a..303c347d 100644 --- a/src/Kernel.php +++ b/src/Kernel.php @@ -48,10 +48,7 @@ class Kernel extends BaseKernel $loader->load($confDir.'/services'.self::CONFIG_EXTS, 'glob'); $loader->load($confDir.'/services_'.$this->environment.self::CONFIG_EXTS, 'glob'); -// TODO active doctrine-extensions -// composer require beberlei/DoctrineExtensions -// $container->addCompilerPass(new DoctrineCompilerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -1000); - + $container->addCompilerPass(new DoctrineCompilerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -1000); } protected function configureRoutes(RouteCollectionBuilder $routes) diff --git a/src/Repository/Query/TimesheetQuery.php b/src/Repository/Query/TimesheetQuery.php index 24dd08e6..3175aeeb 100644 --- a/src/Repository/Query/TimesheetQuery.php +++ b/src/Repository/Query/TimesheetQuery.php @@ -25,9 +25,10 @@ use App\Entity\Project; class TimesheetQuery extends BaseQuery { - const STATE_ALL = 0; - const STATE_RUNNING = 1; - const STATE_STOPPED = 2; + const STATE_ALL = 1; + const STATE_RUNNING = 2; + const STATE_STOPPED = 3; + /** * Overwritten for different default order * @var string @@ -153,9 +154,15 @@ class TimesheetQuery extends BaseQuery */ public function setState($state) { + if (!is_int($state) && $state != (int) $state) { + return $this; + } + + $state = (int) $state; if (in_array($state, [self::STATE_ALL, self::STATE_RUNNING, self::STATE_STOPPED], true)) { $this->state = $state; } + return $this; } } diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php index 2c5dc258..0426213f 100644 --- a/src/Repository/UserRepository.php +++ b/src/Repository/UserRepository.php @@ -14,13 +14,14 @@ namespace App\Repository; use App\Entity\User; use App\Model\UserStatistic; use App\Repository\Query\UserQuery; +use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface; /** * Class UserRepository * * @author Kevin Papst */ -class UserRepository extends AbstractRepository +class UserRepository extends AbstractRepository implements UserLoaderInterface { /** @@ -65,4 +66,20 @@ class UserRepository extends AbstractRepository return $this->getPager($qb->getQuery(), $query->getPage(), $query->getPageSize()); } + + /** + * @param string $username + * @return mixed|null|\Symfony\Component\Security\Core\User\UserInterface + * @throws \Doctrine\ORM\NoResultException + * @throws \Doctrine\ORM\NonUniqueResultException + */ + public function loadUserByUsername($username) + { + return $this->createQueryBuilder('u') + ->where('u.username = :username') + ->orWhere('u.email = :username') + ->setParameter('username', $username) + ->getQuery() + ->getSingleResult(); + } }