diff --git a/composer.json b/composer.json index 47c32fda9..02030cb97 100644 --- a/composer.json +++ b/composer.json @@ -40,15 +40,16 @@ "ext-tokenizer": "*", "ext-ctype": "*", "ext-mbstring": "*", - "container-interop/service-provider": "^0.4", "cache/array-adapter": "^1.0.0", "cache/void-adapter": "^1.0.0", "cakephp/chronos": "^1.1.4", + "container-interop/service-provider": "^0.4", "danielstjules/stringy": "^3.0.0", "doctrine/inflector": "^1.3.0", "dragonmantank/cron-expression": "^2.1.0", "egulias/email-validator": "^2.1.0", "enqueue/null": "^0.8.0", + "jdorn/sql-formatter": "^1.2.17", "league/flysystem": "^1.0.44", "monolog/monolog": "^1.24.0", "narrowspark/arr": "^2.1.0", @@ -89,6 +90,10 @@ "viserio/contract": "self.version", "viserio/cookie": "self.version", "viserio/cron": "self.version", + "viserio/doctrine-doctrine-provider": "self.version", + "viserio/doctrine-extensions-bridge": "self.version", + "viserio/doctrine-migration-bridge": "self.version", + "viserio/doctrine-testing-bridge": "self.version", "viserio/events": "self.version", "viserio/exception": "self.version", "viserio/filesystem": "self.version", @@ -131,6 +136,9 @@ "cache/namespaced-cache": "^1.0", "cache/session-handler": "^1.0", "doctrine/dbal": "^2.5", + "doctrine/data-fixtures": "^1.3.1", + "doctrine/migrations": "^2.0.0-beta2", + "doctrine/orm": "^2.6.3", "enqueue/dbal": "^0.8", "enqueue/fs": "^0.8", "enqueue/pheanstalk": "^0.8", @@ -139,7 +147,10 @@ "erusev/parsedown": "^1.7", "erusev/parsedown-extra": "^0.7", "filp/whoops": "^2.2.0", + "fzaninotto/faker": "^1.8.0", + "gedmo/doctrine-extensions": "^2.4.36", "guzzlehttp/guzzle": "^6.3.0", + "http-interop/http-factory-tests": "^0.5.0", "league/flysystem-aws-s3-v3": "^1.0.19", "league/flysystem-cached-adapter": "^1.0.6", "league/flysystem-sftp": "^1.0", @@ -150,16 +161,16 @@ "mockery/mockery": "^1.2.0", "mouf/picotainer": "^1.1.0", "narrowspark/automatic-common": "^0.9.0", + "narrowspark/coding-standard": "^1.4.0", "narrowspark/testing-helper": "^7.0.0", "nyholm/nsa": "^1.1", "pda/pheanstalk": "^3.1", "php-amqplib/php-amqplib": "^2.6", "phpstan/phpstan-php-parser": "^0.10.0", "phpunit/phpunit": "^7.5.0", - "http-interop/http-factory-tests": "^0.5.0", "predis/predis": "^1.0", - "roave/security-advisories": "dev-master", "spatie/flysystem-dropbox": "^1.0", + "ocramius/proxy-manager": "^2.2.2", "symfony/phpunit-bridge": "^4.2.0", "symfony/var-dumper": "^4.2.0", "symfony/yaml": "^4.2.0", @@ -179,7 +190,7 @@ "Viserio\\Provider\\": "src/Viserio/Provider/" }, "files": [ - "src/Viserio/Component/Support/helper.php" + "src/Viserio/Component/Support/helper.php" ], "exclude-from-classmap": [ "src/Viserio/Component/**/Tests/", @@ -196,11 +207,11 @@ "minimum-stability": "dev", "prefer-stable": true, "scripts": { + "changelog": "./build/changelog.sh", "coverage": "phpunit --coverage-html=\"build/logs\"", "cs": "php-cs-fixer fix", "phpstan": "phpstan analyse -c phpstan.neon -l 7 src/Viserio --memory-limit=-1", - "test": "phpunit", - "changelog": "./build/changelog.sh" + "test": "phpunit" }, "support": { "issues": "https://github.com/narrowspark/framework/issues", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index d3de61c06..bfe21235c 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -26,6 +26,22 @@ + + ./src/Viserio/Bridge/Doctrine/DBAL/Tests + + + ./src/Viserio/Bridge/Doctrine/Extensions/Tests + + + ./src/Viserio/Bridge/Doctrine/Migration/Tests + + + ./src/Viserio/Bridge/Doctrine/ORM/Tests + + + ./src/Viserio/Bridge/Doctrine/Testing/Tests + + ./src/Viserio/Bridge/Monolog/Tests diff --git a/src/Viserio/Bridge/Doctrine/Contract/Migration/Exception/Exception.php b/src/Viserio/Bridge/Doctrine/Contract/Migration/Exception/Exception.php new file mode 100644 index 000000000..1af8119e5 --- /dev/null +++ b/src/Viserio/Bridge/Doctrine/Contract/Migration/Exception/Exception.php @@ -0,0 +1,10 @@ +migrations = $migrations; + } + + /** + * Get all unavailable migrations. + * + * @return array + */ + public function getMigrations(): array + { + return $this->migrations; + } +} diff --git a/src/Viserio/Bridge/Doctrine/Contract/Migration/Exception/InvalidArgumentException.php b/src/Viserio/Bridge/Doctrine/Contract/Migration/Exception/InvalidArgumentException.php new file mode 100644 index 000000000..9b4ab15ba --- /dev/null +++ b/src/Viserio/Bridge/Doctrine/Contract/Migration/Exception/InvalidArgumentException.php @@ -0,0 +1,9 @@ +table = $table; + } + + /** + * Build a new table. + * + * @return \Doctrine\DBAL\Schema\Table + */ + public function build(): Table + { + return new Table( + $this->table, + $this->getColumns(), + $this->getIndices() + ); + } + + /** + * Create a new table column. + * + * @param string $name + * @param string $type + * @param bool $autoincrement + * + * @return \Doctrine\DBAL\Schema\Column + */ + protected function createColumn(string $name, string $type, bool $autoincrement = false): Column + { + $column = new Column($name, Type::getType($type)); + $column->setAutoincrement($autoincrement); + + return $column; + } + + /** + * @param string $name + * @param array $columns + * @param bool $unique + * @param bool $primary + * + * @return \Doctrine\DBAL\Schema\Index + */ + protected function index(string $name, array $columns, bool $unique = false, bool $primary = false): Index + { + return new Index($name, $columns, $unique, $primary); + } + + /** + * @return \Doctrine\DBAL\Schema\Column[] + */ + abstract protected function getColumns(): array; + + /** + * @return \Doctrine\DBAL\Schema\Index[] + */ + abstract protected function getIndices(): array; +} diff --git a/src/Viserio/Bridge/Doctrine/ORM/Commands/AbstractDoctrineCommand.php b/src/Viserio/Bridge/Doctrine/ORM/Commands/AbstractDoctrineCommand.php new file mode 100644 index 000000000..0ac564236 --- /dev/null +++ b/src/Viserio/Bridge/Doctrine/ORM/Commands/AbstractDoctrineCommand.php @@ -0,0 +1,66 @@ +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 null|int $shardId + * + * @return \Doctrine\ORM\EntityManager + */ + protected function getEntityManager(string $name, ?int $shardId = null): EntityManager + { + $manager = $this->container->get('doctrine')->getManager($name); + + if ($shardId) { + if (! $manager->getConnection() instanceof PoolingShardConnection) { + throw new LogicException(\sprintf("Connection of EntityManager '%s' must implement shards configuration.", $name)); + } + + $manager->getConnection()->connect($shardId); + } + + return $manager; + } + + /** + * Get a doctrine dbal connection by symfony name. + * + * @param string $name + * + * @return \Doctrine\DBAL\Connection + */ + protected function getDoctrineConnection(string $name): Connection + { + return $this->container->get('doctrine')->getConnection($name); + } +} diff --git a/src/Viserio/Bridge/Doctrine/ORM/Commands/CreateDatabaseDoctrineCommand.php b/src/Viserio/Bridge/Doctrine/ORM/Commands/CreateDatabaseDoctrineCommand.php new file mode 100644 index 000000000..e69de29bb diff --git a/src/Viserio/Bridge/Doctrine/ORM/Commands/DropDatabaseDoctrineCommand.php b/src/Viserio/Bridge/Doctrine/ORM/Commands/DropDatabaseDoctrineCommand.php new file mode 100644 index 000000000..e69de29bb diff --git a/src/Viserio/Bridge/Doctrine/ORM/Commands/GenerateEntitiesDoctrineCommand.php b/src/Viserio/Bridge/Doctrine/ORM/Commands/GenerateEntitiesDoctrineCommand.php new file mode 100644 index 000000000..e69de29bb diff --git a/src/Viserio/Bridge/Doctrine/ORM/Commands/ImportMappingDoctrineCommand.php b/src/Viserio/Bridge/Doctrine/ORM/Commands/ImportMappingDoctrineCommand.php new file mode 100644 index 000000000..e69de29bb diff --git a/src/Viserio/Bridge/Doctrine/ORM/Commands/Proxies/ClearMetadataCacheDoctrineCommand.php b/src/Viserio/Bridge/Doctrine/ORM/Commands/Proxies/ClearMetadataCacheDoctrineCommand.php new file mode 100644 index 000000000..e69de29bb diff --git a/src/Viserio/Bridge/Doctrine/ORM/Configuration/CacheManager.php b/src/Viserio/Bridge/Doctrine/ORM/Configuration/CacheManager.php new file mode 100644 index 000000000..0fdb96a03 --- /dev/null +++ b/src/Viserio/Bridge/Doctrine/ORM/Configuration/CacheManager.php @@ -0,0 +1,32 @@ + 'annotations', + 'drivers' => [ + 'fluent' => [ + 'mappings' => [], + ], + 'annotations' => [ + 'simple' => false, + 'paths' => [], + ], + ], + ]; + } + + /** + * {@inheritdoc} + */ + public static function getDimensions(): array + { + return ['viserio', 'doctrine', self::getConfigName()]; + } + + /** + * Create an instance of the annotation meta driver. + * + * @param array $config + * + * @return \Doctrine\ORM\Mapping\Driver\AnnotationDriver + */ + protected function createAnnotationsDriver(array $config): array + { + $configuration = new Configuration(); + + return [ + 'driver' => $configuration->newDefaultAnnotationDriver( + $config['paths'], + $config['simple'] + ), + 'meta_factory' => ClassMetadataFactory::class, + ]; + } + + /** + * Create an instance of the xml meta driver. + * + * @param array $config + * + * @return \Doctrine\ORM\Mapping\Driver\XmlDriver + */ + protected function createXmlDriver(array $config): array + { + return [ + 'driver' => new XmlDriver( + $config['paths'], + $config['extension'] ?? XmlDriver::DEFAULT_FILE_EXTENSION + ), + 'meta_factory' => ClassMetadataFactory::class, + ]; + } + + /** + * Create an instance of the yaml meta driver. + * + * @param array $config + * + * @return \Doctrine\ORM\Mapping\Driver\YamlDriver + */ + protected function createYamlDriver(array $config): array + { + return [ + 'driver' => new YamlDriver( + $config['paths'], + $config['extension'] ?? YamlDriver::DEFAULT_FILE_EXTENSION + ), + 'meta_factory' => ClassMetadataFactory::class, + ]; + } + + /** + * Create an instance of the simplified yaml meta driver. + * + * @param array $config + * + * @return \Doctrine\ORM\Mapping\Driver\SimplifiedYamlDriver + */ + protected function createSimplifiedYamlDriver(array $config): array + { + return [ + 'driver' => new SimplifiedYamlDriver( + $config['paths'], + $config['extension'] ?? SimplifiedYamlDriver::DEFAULT_FILE_EXTENSION + ), + 'meta_factory' => ClassMetadataFactory::class, + ]; + } + + /** + * Create an instance of the simplified xml meta driver. + * + * @param array $config + * + * @return \Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver + */ + protected function createSimplifiedXmlDriver(array $config): array + { + return [ + 'driver' => new SimplifiedXmlDriver( + $config['paths'], + $config['extension'] ?? SimplifiedXmlDriver::DEFAULT_FILE_EXTENSION + ), + 'meta_factory' => ClassMetadataFactory::class, + ]; + } + + /** + * Create an instance of the static php meta driver. + * + * @param array $config + * + * @return \Doctrine\Common\Persistence\Mapping\Driver\StaticPHPDriver + */ + protected function createStaticPhpDriver(array $config): array + { + return [ + 'driver' => new StaticPHPDriver($config['paths']), + 'meta_factory' => ClassMetadataFactory::class, + ]; + } + + /** + * Create an instance of the php meta driver. + * + * @param array $config + * + * @return \Doctrine\Common\Persistence\Mapping\Driver\PHPDriver + */ + protected function createPhpDriver(array $config): array + { + return [ + 'driver' => new PHPDriver($config['paths']), + 'meta_factory' => ClassMetadataFactory::class, + ]; + } + + /** + * Create an instance of the fluent meta driver. + * + * @param array $config + * + * @return \LaravelDoctrine\Fluent\FluentDriver + */ + protected function createFluentDriver(array $config): array + { + $driver = new FluentDriver($config['mappings']); + + $driver->setFluentFactory(function (ClassMetadataInfo $meta) { + return new Builder(new ClassMetadataBuilder($meta)); + }); + + return [ + 'driver' => $driver, + 'meta_factory' => ExtensibleClassMetadataFactory::class, + ]; + } + + /** + * {@inheritdoc} + */ + protected static function getConfigName(): string + { + return 'metadata'; + } +} diff --git a/src/Viserio/Bridge/Doctrine/ORM/ConnectionFactory.php b/src/Viserio/Bridge/Doctrine/ORM/ConnectionFactory.php new file mode 100644 index 000000000..2f26a4bb8 --- /dev/null +++ b/src/Viserio/Bridge/Doctrine/ORM/ConnectionFactory.php @@ -0,0 +1,105 @@ +typesConfig = $typesConfig; + } + + /** + * Create a connection by name. + * + * @param array $params + * @param \Doctrine\DBAL\Configuration $config + * @param \Doctrine\Common\EventManager $eventManager + * @param array $mappingTypes + * + * @return \Doctrine\DBAL\Connection + */ + public function createConnection( + array $params, + Configuration $config = null, + EventManager $eventManager = null, + array $mappingTypes = [] + ): Connection { + if (! $this->initialized) { + $this->initializeTypes(); + } + + $connection = DriverManager::getConnection($params, $config, $eventManager); + + if (! empty($mappingTypes)) { + $platform = $connection->getDatabasePlatform(); + + foreach ($mappingTypes as $dbType => $doctrineType) { + $platform->registerDoctrineTypeMapping($dbType, $doctrineType); + } + } + + if (! empty($this->commentedTypes)) { + $platform = $connection->getDatabasePlatform(); + + foreach ($this->commentedTypes as $type) { + $platform->markDoctrineTypeCommented(Type::getType($type)); + } + } + + return $connection; + } + + /** + * Initialize the types. + * + * @return void + */ + private function initializeTypes(): void + { + foreach ($this->typesConfig as $type => $typeConfig) { + if (Type::hasType($type)) { + Type::overrideType($type, $typeConfig['class']); + } else { + Type::addType($type, $typeConfig['class']); + } + + if ($typeConfig['commented']) { + $this->commentedTypes[] = $type; + } + } + + $this->initialized = true; + } +} diff --git a/src/Viserio/Bridge/Doctrine/ORM/DataCollector/DoctrineDataCollector.php b/src/Viserio/Bridge/Doctrine/ORM/DataCollector/DoctrineDataCollector.php new file mode 100644 index 000000000..35174950c --- /dev/null +++ b/src/Viserio/Bridge/Doctrine/ORM/DataCollector/DoctrineDataCollector.php @@ -0,0 +1,365 @@ +registry = $registry; + $this->connections = $registry->getConnectionNames(); + $this->managers = $registry->getManagerNames(); + } + + public function getInvalidEntityCount() + { + if (null === $this->invalidEntityCount) { + $this->invalidEntityCount = \array_sum(\array_map('\count', $this->data['errors'])); + } + + return $this->invalidEntityCount; + } + + /** + * {@inheritdoc} + */ + public function collect(ServerRequestInterface $serverRequest, ResponseInterface $response): void + { + $queries = []; + $errors = []; + $entities = []; + $caches = [ + 'enabled' => false, + 'log_enabled' => false, + 'counts' => [ + 'puts' => 0, + 'hits' => 0, + 'misses' => 0, + ], + 'regions' => [ + 'puts' => [], + 'hits' => [], + 'misses' => [], + ], + ]; + + foreach ($this->loggers as $name => $logger) { + $queries[$name] = $this->sanitizeQueries($name, $logger->queries); + } + + foreach ($this->registry->getManagers() as $name => $em) { + $entities[$name] = []; + + /** @var $factory \Doctrine\ORM\Mapping\ClassMetadataFactory */ + $factory = $em->getMetadataFactory(); + $validator = new SchemaValidator($em); + + /** @var $class \Doctrine\ORM\Mapping\ClassMetadataInfo */ + foreach ($factory->getLoadedMetadata() as $class) { + if (! isset($entities[$name][$class->getName()])) { + $classErrors = $validator->validateClass($class); + $entities[$name][$class->getName()] = $class->getName(); + + if (! empty($classErrors)) { + $errors[$name][$class->getName()] = $classErrors; + } + } + } + + /** @var $emConfig \Doctrine\ORM\Configuration */ + $emConfig = $em->getConfiguration(); + $slcEnabled = $emConfig->isSecondLevelCacheEnabled(); + + if (! $slcEnabled) { + continue; + } + + $caches['enabled'] = true; + + /** @var $cacheConfiguration \Doctrine\ORM\Cache\CacheConfiguration */ + /** @var $cacheLoggerChain \Doctrine\ORM\Cache\Logging\CacheLoggerChain */ + $cacheConfiguration = $emConfig->getSecondLevelCacheConfiguration(); + $cacheLoggerChain = $cacheConfiguration->getCacheLogger(); + + if (! $cacheLoggerChain || ! $cacheLoggerChain->getLogger('statistics')) { + continue; + } + + /** @var $cacheLoggerStats \Doctrine\ORM\Cache\Logging\StatisticsCacheLogger */ + $cacheLoggerStats = $cacheLoggerChain->getLogger('statistics'); + $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 = [ + 'queries' => $queries, + 'connections' => $this->connections, + 'managers' => $this->managers, + 'entities' => $entities, + 'errors' => $errors, + 'caches' => $caches, + ]; + } + + /** + * Adds the stack logger for a connection. + * + * @param string $name + * @param \Doctrine\DBAL\Logging\DebugStack $logger + */ + public function addLogger(string $name, DebugStack $logger): void + { + $this->loggers[$name] = $logger; + } + + /** + * {@inheritdoc} + */ + public function getMenu(): array + { + return []; + } + + /** + * {@inheritdoc} + */ + public function getTooltip(): string + { + return ''; + } + + /** + * {@inheritdoc} + */ + public function getPanel(): string + { + return ''; + } + + private function getQueryCount() + { + return \array_sum(\array_map('count', $this->data['queries'])); + } + + private function getTime() + { + $time = 0; + + foreach ($this->data['queries'] as $queries) { + foreach ($queries as $query) { + $time += $query['executionMS']; + } + } + + return $time; + } + + private function getGroupedQueryCount() + { + $count = 0; + + foreach ($this->getGroupedQueries() as $connectionGroupedQueries) { + $count += \count($connectionGroupedQueries); + } + + return $count; + } + + private function getGroupedQueries() + { + static $groupedQueries = null; + + if ($groupedQueries !== null) { + return $groupedQueries; + } + + $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, function ($a, $b) { + if ($a['executionMS'] === $b['executionMS']) { + return 0; + } + + return ($a['executionMS'] < $b['executionMS']) ? 1 : -1; + }); + + $groupedQueries[$connection] = $connectionGroupedQueries; + } + + foreach ($groupedQueries as $connection => $queries) { + foreach ($queries as $i => $query) { + $groupedQueries[$connection][$i]['executionPercent'] = $this->executionTimePercentage($query['executionMS'], $totalExecutionMS); + } + } + + return $groupedQueries; + } + + private function sanitizeQueries(string $connectionName, array $queries): array + { + foreach ($queries as $i => $query) { + $queries[$i] = $this->sanitizeQuery($connectionName, $query); + } + + return $queries; + } + + private function sanitizeQuery(string $connectionName, array $query): array + { + $query['explainable'] = true; + + if (null === $query['params']) { + $query['params'] = []; + } + + if (! \is_array($query['params'])) { + $query['params'] = [$query['params']]; + } + + foreach ($query['params'] as $j => $param) { + if (isset($query['types'][$j])) { + // Transform the param according to the type + $type = $query['types'][$j]; + + if (\is_string($type)) { + $type = Type::getType($type); + } + + if ($type instanceof Type) { + $query['types'][$j] = $type->getBindingType(); + $param = $type->convertToDatabaseValue($param, $this->registry->getConnection($connectionName)->getDatabasePlatform()); + } + } + + [$query['params'][$j], $explainable] = $this->sanitizeParam($param); + + if (! $explainable) { + $query['explainable'] = false; + } + } + + return $query; + } + + /** + * Sanitizes a param. + * + * The return value is an array with the sanitized value and a boolean + * indicating if the original value was kept (allowing to use the sanitized + * value to explain the query). + * + * @param mixed $var + * + * @return array + */ + private function sanitizeParam($var): array + { + if (\is_object($var)) { + $className = \get_class($var); + + return \method_exists($var, '__toString') ? + [\sprintf('Object(%s): "%s"', $className, $var->__toString()), false] : + [\sprintf('Object(%s)', $className), false]; + } + + if (\is_array($var)) { + $a = []; + $original = true; + + foreach ($var as $k => $v) { + [$value, $orig] = $this->sanitizeParam($v); + $original = $original && $orig; + $a[$k] = $value; + } + + return [$a, $original]; + } + + if (\is_resource($var)) { + return [\sprintf('Resource(%s)', \get_resource_type($var)), false]; + } + + return [$var, true]; + } + + private function executionTimePercentage($executionTimeMS, $totalExecutionTimeMS) + { + if ($totalExecutionTimeMS === 0.0 || $totalExecutionTimeMS === 0) { + return 0; + } + + return $executionTimeMS / $totalExecutionTimeMS * 100; + } +} diff --git a/src/Viserio/Bridge/Doctrine/ORM/EntityManagerFactory.php b/src/Viserio/Bridge/Doctrine/ORM/EntityManagerFactory.php new file mode 100644 index 000000000..8031ebf24 --- /dev/null +++ b/src/Viserio/Bridge/Doctrine/ORM/EntityManagerFactory.php @@ -0,0 +1,406 @@ +container = $container; + $this->setup = $setup; + $this->meta = $meta; + $this->cache = $cache; + $this->resolver = $resolver; + } + + /** + * {@inheritdoc} + */ + public static function getDimensions(): array + { + return ['viserio', 'doctrine', 'orm']; + } + + /** + * {@inheritdoc} + */ + public static function getDefaultOptions(): array + { + return [ + 'logger' => false, + 'cache' => [ + 'default' => 'array', + ], + 'events' => [ + 'listners' => false, + 'subscribers' => false, + ], + 'proxies' => [ + // Auto generate mode possible values are: "NEVER", "ALWAYS", "FILE_NOT_EXISTS", "FILE_OUTDATED", "EVAL" + 'auto_generate' => false, + 'namespace' => false, + ], + 'second_level_cache' => false, + 'repository' => EntityRepository::class, + 'dql' => [ + 'datetime_functions' => [], + 'numeric_functions' => [], + 'string_functions' => [], + ], + 'filters' => false, + 'mapping_types' => [], + ]; + } + + /** + * {@inheritdoc} + */ + public static function getMandatoryOptions(): array + { + return [ + 'connections' => [ + 'default', + ], + 'proxies' => [ + 'path', + ], + ]; + } + + /** + * Create a new entity manager. + * + * @param string $id + * + * @return \Doctrine\ORM\EntityManagerInterface + */ + public function create(string $id): EntityManagerInterface + { + $this->configureOptions($this->container, $id); + + $configuration = $this->setup->createConfiguration( + $this->options['env'] === 'develop', + $this->options['proxies']['path'], + $this->cache->getDriver($this->options['cache']['default']) + ); + + $configuration = $this->setMetadataDriver($configuration); + $configuration = $this->configureCustomFunctions($configuration); + $configuration = $this->configureFirstLevelCacheSettings($configuration); + $configuration = $this->configureProxy($configuration); + + $configuration->setDefaultRepositoryClassName($this->options['repository']); + $configuration->setEntityListenerResolver($this->resolver); + $connection = new ConnectionFactory($this->options['']); + + $manager = EntityManager::create( + $connection->createConnection( + $this->mapConnectionKey($this->options['connections']['default']), + $configuration, + null, + $this->options['mapping_types'] + ), + $configuration + ); + + $this->registerLogger($manager, $configuration); + $this->registerListeners($manager); + $this->registerSubscribers($manager); + $this->registerFilters($configuration, $manager); + + return $manager; + } + + /** + * Register a logger. + * + * @param \Doctrine\ORM\EntityManagerInterface $em + * @param \Doctrine\ORM\Configuration $configuration + */ + protected function registerLogger(EntityManagerInterface $em, Configuration $configuration): void + { + if (($loggerClass = $this->options['logger']) !== false) { + $logger = $this->container->get($loggerClass); + $logger->register($em, $configuration); + } + } + + /** + * Configure custom functions. + * + * @param \Doctrine\ORM\Configuration $configuration + * + * @return \Doctrine\ORM\Configuration + */ + protected function configureCustomFunctions(Configuration $configuration): Configuration + { + $configuration->setCustomDatetimeFunctions($this->options['dql']['datetime_functions']); + $configuration->setCustomNumericFunctions($this->options['dql']['numeric_functions']); + $configuration->setCustomStringFunctions($this->options['dql']['string_functions']); + + return $configuration; + } + + /** + * Configure first level cache. + * + * @param \Doctrine\ORM\Configuration $configuration + * + * @return \Doctrine\ORM\Configuration + */ + protected function configureFirstLevelCacheSettings(Configuration $configuration): Configuration + { + $cache = $this->cache; + + $configuration->setQueryCacheImpl($cache->getDriver($this->options['query_cache_driver'])); + $configuration->setResultCacheImpl($cache->getDriver($this->options['result_cache_driver'])); + $configuration->setMetadataCacheImpl($cache->getDriver($this->options['metadata_cache_driver'])); + + return $this->setSecondLevelCaching($configuration); + } + + /** + * Configure second level cache. + * + * @param \Doctrine\ORM\Configuration $configuration + * + * @return \Doctrine\ORM\Configuration + */ + protected function setSecondLevelCaching(Configuration $configuration): Configuration + { + $secondCacheSetting = $this->options['second_level_cache']; + + if (\is_array($secondCacheSetting)) { + $configuration->setSecondLevelCacheEnabled(); + + $cacheConfig = $configuration->getSecondLevelCacheConfiguration(); + // $cacheConfig->setCacheLogger($logger); + $cacheConfig->setCacheFactory( + new DefaultCacheFactory( + $cacheConfig->getRegionsConfiguration(), + $this->cache->getDriver($secondCacheSetting['region_cache_driver'] ?? null) + ) + ); + } + + return $configuration; + } + + /** + * Configure proxies. + * + * @param \Doctrine\ORM\Configuration $configuration + * + * @return \Doctrine\ORM\Configuration + */ + protected function configureProxy(Configuration $configuration): Configuration + { + $configuration->setProxyDir( + $this->options['proxies']['path'] + ); + + $configuration->setAutoGenerateProxyClasses( + $this->options['proxies']['auto_generate'] + ); + + if ($namespace = $this->options['proxies']['namespace']) { + $configuration->setProxyNamespace($namespace); + } + } + + /** + * Decorate a entity manager. + * + * @param \Doctrine\ORM\EntityManagerInterface $manager + * + * @return \Doctrine\ORM\EntityManagerInterface + */ + protected function decorateManager(EntityManagerInterface $manager): EntityManagerInterface + { + if ($decorator = $this->options['decorator']) { + if (! class_exists($decorator)) { + throw new InvalidArgumentException(\sprintf('EntityManagerDecorator [%s] does not exist', $decorator)); + } + + $manager = new $decorator($manager); + } + + return $manager; + } + + /** + * Register event listeners. + * + * @param \Doctrine\ORM\EntityManagerInterface $manager + * + * @return void + */ + protected function registerListeners(EntityManagerInterface $manager): void + { + $eventManager = $manager->getEventManager(); + + if ($listeners = $this->options['events']['listeners'] !== false) { + foreach ($listeners as $event => $listener) { + if (\is_array($listener)) { + foreach ($listener as $individualListener) { + $resolvedListener = $this->container->get($listener); + + $eventManager->addEventListener($event, $resolvedListener); + } + } else { + $resolvedListener = $this->container->get($listener); + + $eventManager->addEventListener($event, $resolvedListener); + } + } + } + } + + /** + * Register event subscribers. + * + * @param \Doctrine\ORM\EntityManagerInterface $manager + * + * @return void + */ + protected function registerSubscribers(EntityManagerInterface $manager): void + { + if ($subscribers = $settings['events']['subscribers'] !== false) { + foreach ($subscribers as $subscriber) { + $resolvedSubscriber = $this->container->get($subscriber); + $manager->getEventManager()->addEventSubscriber($resolvedSubscriber); + } + } + } + + /** + * Set a metadata driver to doctrine. + * + * @param \Doctrine\ORM\Configuration $configuration + * + * @return \Doctrine\ORM\Configuration + */ + protected function setMetadataDriver(Configuration $configuration): Configuration + { + $metadata = $this->meta->getDriver($this->options['metadata']['default']); + + $configuration->setMetadataDriverImpl($metadata['driver']); + $configuration->setClassMetadataFactoryName($metadata['meta_factory']); + + return $configuration; + } + + /** + * Register filters. + * + * @param \Doctrine\ORM\Configuration $configuration + * @param \Doctrine\ORM\EntityManagerInterface $manager + */ + protected function registerFilters(Configuration $configuration, EntityManagerInterface $manager): void + { + if ($filters = $this->options['filters'] !== false) { + foreach ($filters as $name => $filter) { + $configuration->addFilter($name, $filter); + $manager->getFilters()->enable($name); + } + } + } + + /** + * {@inheritdoc} + */ + protected function getConfigClass(): RequiresConfigContract + { + return $this; + } + + /** + * Map our config style to the dortrine config style. + * + * @param array $configs + * + * @return array + */ + private static function mapConnectionKey(array $configs): array + { + $mapList = [ + 'dbname' => 'database', + 'user' => 'username', + ]; + + foreach ($mapList as $newKey => $oldKey) { + if ($configs[$oldKey]) { + $arr[$newKey] = $arr[$oldKey]; + unset($arr[$oldKey]); + } + } + + return $configs; + } +} diff --git a/src/Viserio/Bridge/Doctrine/ORM/Exceptions/ExtensionNotFoundException.php b/src/Viserio/Bridge/Doctrine/ORM/Exceptions/ExtensionNotFoundException.php new file mode 100644 index 000000000..efa353144 --- /dev/null +++ b/src/Viserio/Bridge/Doctrine/ORM/Exceptions/ExtensionNotFoundException.php @@ -0,0 +1,9 @@ +container = $container; + $this->factory = $factory; + } + + /** + * Set a default manager. + * + * @param string $defaultManager + * + * @return void + */ + public function setDefaultManager(string $defaultManager): void + { + $this->defaultManager = $defaultManager; + } + + /** + * Set a default connection. + * + * @param string $defaultConnection + * + * @return void + */ + public function setDefaultConnection(string $defaultConnection): void + { + $this->defaultConnection = $defaultConnection; + } + + /** + * {@inheritdoc} + */ + public function getManagers(): array + { + $managers = []; + + foreach ($this->getManagerNames() as $name) { + $managers[$name] = $this->getManager($name); + } + + return $managers; + } + + /** + * {@inheritdoc} + */ + public function getConnections(): array + { + $connections = []; + + foreach ($this->connections as $name) { + $connections[$name] = $this->getConnection($name); + } + + return $connections; + } + + /** + * Add a new manager instance. + * + * @param string $manager + * @param array $settings + * + * @return void + */ + public function addManager(string $manager, array $settings = []): void + { + $this->container->singleton($this->getManagerBindingName($manager), function () use ($settings) { + return $this->factory->create($settings); + }); + + $this->managers[$manager] = $manager; + + $this->addConnection($manager, $settings); + } + + /** + * {@inheritdoc} + */ + public function getDefaultManagerName(): string + { + if (isset($this->managers[$this->defaultManager])) { + return $this->defaultManager; + } + + return head($this->managers); + } + + /** + * {@inheritdoc} + */ + public function getManager($name = null): ObjectManager + { + $name = $name ?? $this->getDefaultManagerName(); + + if (! $this->hasManager($name)) { + throw new InvalidArgumentException(\sprintf('Doctrine Manager named [%s] does not exist.', $name)); + } + + if (isset($this->managersMap[$name])) { + return $this->managersMap[$name]; + } + + return $this->managersMap[$name] = $this->getService( + $this->getManagerBindingName($this->managers[$name]) + ); + } + + /** + * Check if a manager exists. + * + * @param string $name + * + * @return bool + */ + public function hasManager(string $name): bool + { + return isset($this->managers[$name]); + } + + /** + * {@inheritdoc} + */ + public function getManagerNames(): array + { + return $this->managers; + } + + /** + * {@inheritdoc} + */ + public function resetManager($name = null): ObjectManager + { + $name = $name ?? $this->getDefaultManagerName(); + + if (! $this->hasManager($name)) { + throw new InvalidArgumentException(\sprintf('Doctrine Manager named [%s] does not exist.', $name)); + } + + // force the creation of a new document manager + // if the current one is closed + $this->resetService( + $this->getManagerBindingName($this->managers[$name]) + ); + + $this->resetService( + $this->getConnectionBindingName($this->connections[$name]) + ); + + unset($this->managersMap[$name], $this->connectionsMap[$name]); + } + + /** + * {@inheritdoc} + */ + public function getAliasNamespace($alias): string + { + foreach ($this->getManagerNames() as $name) { + try { + return $this->getManager($name)->getConfiguration()->getEntityNamespace($alias); + } catch (ORMException $e) { + } + } + + throw ORMException::unknownEntityNamespace($alias); + } + + /** + * {@inheritdoc} + */ + public function getRepository($persistentObject, $persistentManagerName = null): ObjectRepository + { + return $this->getManager($persistentManagerName)->getRepository($persistentObject); + } + + /** + * {@inheritdoc} + */ + public function getManagerForClass($class): ?ObjectManager + { + // Check for namespace alias + if (\mb_strpos($class, ':') !== false) { + [$namespaceAlias, $simpleClassName] = \explode(':', $class, 2); + $class = $this->getAliasNamespace($namespaceAlias) . '\\' . $simpleClassName; + } + + $proxyClass = new ReflectionClass($class); + + if ($proxyClass->implementsInterface(Proxy::class)) { + $class = $proxyClass->getParentClass()->getName(); + } + + foreach ($this->getManagerNames() as $name) { + $manager = $this->getManager($name); + + if (! $manager->getMetadataFactory()->isTransient($class)) { + foreach ($manager->getMetadataFactory()->getAllMetadata() as $metadata) { + if ($metadata->getName() === $class) { + return $manager; + } + } + } + } + } + + /** + * Add a new connection. + * + * @param object|string $connection + * @param array $settings + * + * @return void + */ + public function addConnection($connection, array $settings = []): void + { + $this->container->singleton($this->getConnectionBindingName($connection), function () use ($connection) { + return $this->getManager($connection)->getConnection(); + }); + + $this->connections[$connection] = $connection; + } + + /** + * {@inheritdoc} + */ + public function getDefaultConnectionName(): string + { + if (isset($this->connections[$this->defaultConnection])) { + return $this->defaultConnection; + } + + return \reset($this->connections); + } + + /** + * {@inheritdoc} + */ + public function getConnection($name = null) + { + $name = $name ?? $this->getDefaultConnectionName(); + + if (! $this->hasConnection($name)) { + throw new InvalidArgumentException(\sprintf('Doctrine Connection named [%s] does not exist.', $name)); + } + + if (isset($this->connectionsMap[$name])) { + return $this->connectionsMap[$name]; + } + + return $this->connectionsMap[$name] = $this->getService( + $this->getConnectionBindingName($this->connections[$name]) + ); + } + + /** + * Check if a connection exists. + * + * @param string $name + * + * @return bool + */ + public function hasConnection(string $name): bool + { + return isset($this->connections[$name]); + } + + /** + * {@inheritdoc} + */ + public function getConnectionNames(): array + { + return $this->connections; + } + + /** + * Prefix a manager name. + * + * @param string $manager + * + * @return string + */ + protected function getManagerBindingName(string $manager): string + { + return self::MANAGER_BINDING_PREFIX . $manager; + } + + /** + * Prefix a connection name. + * + * @param $connection + * + * @return string + */ + protected function getConnectionBindingName(string $connection): string + { + return self::CONNECTION_BINDING_PREFIX . $connection; + } + + /** + * {@inheritdoc} + */ + protected function getService($name) + { + return $this->container->get($name); + } +} diff --git a/src/Viserio/Bridge/Doctrine/ORM/Middleware/SubstituteBindings.php b/src/Viserio/Bridge/Doctrine/ORM/Middleware/SubstituteBindings.php new file mode 100644 index 000000000..e69de29bb diff --git a/src/Viserio/Bridge/Doctrine/ORM/Pagination/PaginatorAdapter.php b/src/Viserio/Bridge/Doctrine/ORM/Pagination/PaginatorAdapter.php new file mode 100644 index 000000000..caa0fe624 --- /dev/null +++ b/src/Viserio/Bridge/Doctrine/ORM/Pagination/PaginatorAdapter.php @@ -0,0 +1,46 @@ +query = $query; + $this->perPage = $perPage; + $this->pageResolver = $pageResolver; + $this->fetchJoinCollection = $fetchJoinCollection; + } +} diff --git a/src/Viserio/Bridge/Doctrine/ORM/Provider/ConsoleCommandsServiceProvider.php b/src/Viserio/Bridge/Doctrine/ORM/Provider/ConsoleCommandsServiceProvider.php new file mode 100644 index 000000000..3726e205b --- /dev/null +++ b/src/Viserio/Bridge/Doctrine/ORM/Provider/ConsoleCommandsServiceProvider.php @@ -0,0 +1,86 @@ + [self::class, 'extendConsole'], + ]; + } + + /** + * Extend viserio console with commands. + * + * @param \Psr\Container\ContainerInterface $container + * @param null|\Viserio\Component\Console\Application $console + * + * @return null|\Viserio\Component\Console\Application + */ + public static function extendConsole(ContainerInterface $container, ?Application $console = null): ?Application + { + if ($console !== null) { + $manager = $container->get(EntityManagerInterface::class); + + $console->getHelperSet()->set(new ConnectionHelper($manager->getConnection()), 'db'); + $console->getHelperSet()->set(new EntityManagerHelper($manager), 'em'); + + $console->addCommands([ + new MetadataCommand(), + new ResultCommand(), + new QueryCommand(), + new CreateCommand(), + new UpdateCommand(), + new DropCommand(), + new EnsureProductionSettingsCommand(), + new ConvertDoctrine1SchemaCommand(), + new GenerateRepositoriesCommand(), + new GenerateEntitiesCommand(), + new ConvertMappingCommand(), + new RunDqlCommand(), + new ValidateSchemaCommand(), + new InfoCommand(), + new MappingDescribeCommand(), + ]); + + return $console; + } + + return null; + } +} diff --git a/src/Viserio/Bridge/Doctrine/ORM/Provider/DoctrineORMQueueJobServiceProvider.php b/src/Viserio/Bridge/Doctrine/ORM/Provider/DoctrineORMQueueJobServiceProvider.php new file mode 100644 index 000000000..2a2c412c4 --- /dev/null +++ b/src/Viserio/Bridge/Doctrine/ORM/Provider/DoctrineORMQueueJobServiceProvider.php @@ -0,0 +1,17 @@ + [self::class, 'createManagerRegistry'], + ManagerRegistry::class => function (ContainerInterface $container) { + return $container->get(DoctrineManagerRegistry::class); + }, + 'registry' => function (ContainerInterface $container) { + return $container->get(DoctrineManagerRegistry::class); + }, + EntityManagerInterface::class => [self::class, 'createEntityManager'], + EntityManager::class => function (ContainerInterface $container) { + return $container->get(EntityManagerInterface::class); + }, + 'em' => function (ContainerInterface $container) { + return $container->get(EntityManagerInterface::class); + }, + ]; + } + + public static function createManagerRegistry(ContainerInterface $container): DoctrineManagerRegistry + { + return new ManagerRegistry($container, $container->get(EntityManagerFactory::class)); + } + + public static function createEntityManager(ContainerInterface $container): EntityManagerInterface + { + return $container->get(DoctrineManagerRegistry::class)->getManager(); + } +} diff --git a/src/Viserio/Bridge/Doctrine/ORM/Proxy/Doctrine.php b/src/Viserio/Bridge/Doctrine/ORM/Proxy/Doctrine.php new file mode 100644 index 000000000..e0c9e5fce --- /dev/null +++ b/src/Viserio/Bridge/Doctrine/ORM/Proxy/Doctrine.php @@ -0,0 +1,19 @@ +createColumn('id', 'integer', true), + $this->createColumn('connection', 'string'), + $this->createColumn('queue', 'string'), + $this->createColumn('payload', 'text'), + $this->createColumn('failed_at', 'datetime'), + $this->createColumn('exception', 'text')->setNotnull(false), + ]; + } + + /** + * {@inheritdoc} + */ + protected function getIndices(): array + { + return [ + $this->index('pk', ['id'], true, true), + ]; + } +} diff --git a/src/Viserio/Bridge/Doctrine/ORM/README.md b/src/Viserio/Bridge/Doctrine/ORM/README.md new file mode 100644 index 000000000..38cc24f0d --- /dev/null +++ b/src/Viserio/Bridge/Doctrine/ORM/README.md @@ -0,0 +1,33 @@ +# Viserio Doctrine ORM package + +This package is part of the [Narrowspark framework](http://github.com/narrowspark/framework). + +[![Author](http://img.shields.io/badge/author-@anolilab-blue.svg?style=flat-square)](https://twitter.com/anolilab) +[![Quality Score](https://img.shields.io/scrutinizer/g/narrowspark/framework.svg?style=flat-square)](https://scrutinizer-ci.com/g/narrowspark/framework/code-structure/master) +[![Build Status](https://api.travis-ci.org/narrowspark/framework.svg?branch=master&style=flat-square)](https://travis-ci.org/narrowspark/framework) +[![Latest Version](https://img.shields.io/packagist/v/narrowspark/framework.svg?style=flat-square)](https://github.com/narrowspark/framework/releases) +[![Minimum PHP Version](https://img.shields.io/badge/php-%5E7.1.0-8892BF.svg?style=flat-square)](https://php.net/) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +> **Note:** If you want to build an application using Narrowspark, visit the main [![Source Code](http://img.shields.io/badge/source-narrowspark/narrowspark-blue.svg?style=flat-square)](https://github.com/narrowspark/narrowspark). + +## Contributing + +Issues for this package shall be posted on [Narrowspark framework issues](http://github.com/narrowspark/framework/issues). +Thank you for considering contributing to the Narrowspark framework! The contribution guide can be found in the [Narrowspark documentation](http://narrowspark.de/docs/contributions). + +## Installation + +Use [Composer](https://getcomposer.org/) to install this package: + +```sh +composer require viserio/bridge/doctrine +``` + +## Official Documentation + +Documentation for the framework can be found on the [Narrowspark website](http://narrowspark.de/docs). + +### License + +The Narrowspark framework is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT). diff --git a/src/Viserio/Bridge/Doctrine/ORM/Resolvers/EntityListenerResolver.php b/src/Viserio/Bridge/Doctrine/ORM/Resolvers/EntityListenerResolver.php new file mode 100644 index 000000000..8bedfa1d2 --- /dev/null +++ b/src/Viserio/Bridge/Doctrine/ORM/Resolvers/EntityListenerResolver.php @@ -0,0 +1,68 @@ +container = $container; + } + + /** + * {@inheritdoc} + */ + public function clear($className = null): void + { + if ($className) { + unset($this->instances[$className = \trim($className, '\\')]); + + return; + } + + $this->instances = []; + } + + /** + * {@inheritdoc} + */ + public function resolve($className) + { + if (isset($this->instances[$className = \trim($className, '\\')])) { + return $this->instances[$className]; + } + + return $this->instances[$className] = $this->container->get($className); + } + + /** + * {@inheritdoc} + */ + public function register($object): void + { + if (! \is_object($object)) { + throw new InvalidArgumentException(\sprintf('An object was expected, but got "%s".', \gettype($object))); + } + + $this->instances[\get_class($object)] = $object; + } +} diff --git a/src/Viserio/Bridge/Doctrine/ORM/Serializers/ArrayEncoder.php b/src/Viserio/Bridge/Doctrine/ORM/Serializers/ArrayEncoder.php new file mode 100644 index 000000000..9f0865f07 --- /dev/null +++ b/src/Viserio/Bridge/Doctrine/ORM/Serializers/ArrayEncoder.php @@ -0,0 +1,43 @@ + new ArrayEncoder(), + ] + ); + + return $serializer->serialize($this, 'array'); + } +} diff --git a/src/Viserio/Bridge/Doctrine/ORM/Tests/ClassAliasesTest.php b/src/Viserio/Bridge/Doctrine/ORM/Tests/ClassAliasesTest.php new file mode 100644 index 000000000..8682f0abf --- /dev/null +++ b/src/Viserio/Bridge/Doctrine/ORM/Tests/ClassAliasesTest.php @@ -0,0 +1,21 @@ +assertTrue(\interface_exists(ViserioFluent::class)); + $this->assertTrue(class_exists(ViserioEntityMapping::class)); + $this->assertTrue(class_exists(ViserioFluentDriver::class)); + } +} diff --git a/src/Viserio/Bridge/Doctrine/ORM/Tests/Configuration/CacheManagerTest.php b/src/Viserio/Bridge/Doctrine/ORM/Tests/Configuration/CacheManagerTest.php new file mode 100644 index 000000000..ed130babe --- /dev/null +++ b/src/Viserio/Bridge/Doctrine/ORM/Tests/Configuration/CacheManagerTest.php @@ -0,0 +1,35 @@ + [ + 'viserio' => [ + 'doctrine' => [ + 'cache' => [ + 'drivers' => [], + 'namespace' => false, + ], + ], + ], + ], + ]) + ); + + $this->assertInstanceOf(DoctrineCacheBridge::class, $manager->getDriver('array')); + $this->assertInstanceOf(DoctrineCacheBridge::class, $manager->getDriver()); + } +} diff --git a/src/Viserio/Bridge/Doctrine/ORM/Tests/Configuration/MetaDataManagerTest.php b/src/Viserio/Bridge/Doctrine/ORM/Tests/Configuration/MetaDataManagerTest.php new file mode 100644 index 000000000..3a68ff46e --- /dev/null +++ b/src/Viserio/Bridge/Doctrine/ORM/Tests/Configuration/MetaDataManagerTest.php @@ -0,0 +1,86 @@ + [ + 'doctrine' => [ + 'metadata' => [ + 'drivers' => [ + ], + ], + ], + ], + ] + ); + + $driver = $manager->getDriver(); + $this->assertInstanceOf(AnnotationDriver::class, $driver['driver']); + $this->assertSame(ClassMetadataFactory::class, $driver['meta_factory']); + + $driver = $manager->getDriver('annotations'); + + $this->assertInstanceOf(AnnotationDriver::class, $driver['driver']); + $this->assertSame(ClassMetadataFactory::class, $driver['meta_factory']); + } + + /** + * @dataProvider metaDriverProvider + * + * @param mixed $driverClass + * @param mixed $driverName + * @param mixed $driverInfos + * @param array $config + */ + public function testMetaDataDriver(array $config, $driverInfos, $driverName): void + { + $manager = new MetaDataManager( + [ + 'viserio' => [ + 'doctrine' => [ + 'metadata' => $config, + ], + ], + ] + ); + + $driver = $manager->getDriver($driverName); + + $this->assertInstanceOf($driverInfos['driver'], $driver['driver']); + $this->assertSame($driverInfos['meta_factory'], $driver['meta_factory']); + } + + public function metaDriverProvider() + { + return [ + [['default' => 'xml', 'drivers' => ['xml' => ['paths' => [__DIR__]]]], ['driver' => XmlDriver::class, 'meta_factory' => ClassMetadataFactory::class], 'xml'], + [['default' => 'yaml', 'drivers' => ['yaml' => ['paths' => [__DIR__]]]], ['driver' => YamlDriver::class, 'meta_factory' => ClassMetadataFactory::class], 'yaml'], + [['default' => 'simplified_yaml', 'drivers' => ['simplified_yaml' => ['paths' => [__DIR__]]]], ['driver' => SimplifiedYamlDriver::class, 'meta_factory' => ClassMetadataFactory::class], 'simplified_yaml'], + [['default' => 'simplified_xml', 'drivers' => ['simplified_xml' => ['paths' => [__DIR__]]]], ['driver' => SimplifiedXmlDriver::class, 'meta_factory' => ClassMetadataFactory::class], 'simplified_xml'], + [['default' => 'static_php', 'drivers' => ['static_php' => ['paths' => [__DIR__]]]], ['driver' => StaticPHPDriver::class, 'meta_factory' => ClassMetadataFactory::class], 'static_php'], + [['default' => 'php', 'drivers' => ['php' => ['paths' => [__DIR__]]]], ['driver' => PHPDriver::class, 'meta_factory' => ClassMetadataFactory::class], 'php'], + [['default' => 'fluent', 'drivers' => ['fluent' => ['paths' => [__DIR__]]]], ['driver' => FluentDriver::class, 'meta_factory' => ExtensibleClassMetadataFactory::class], 'fluent'], + ]; + } +} diff --git a/src/Viserio/Bridge/Doctrine/ORM/Tests/EntityManagerFactoryTest.php b/src/Viserio/Bridge/Doctrine/ORM/Tests/EntityManagerFactoryTest.php new file mode 100644 index 000000000..e69de29bb diff --git a/src/Viserio/Bridge/Doctrine/ORM/Tests/Fixtures/ArrayableEntityFixture.php b/src/Viserio/Bridge/Doctrine/ORM/Tests/Fixtures/ArrayableEntityFixture.php new file mode 100644 index 000000000..391572a79 --- /dev/null +++ b/src/Viserio/Bridge/Doctrine/ORM/Tests/Fixtures/ArrayableEntityFixture.php @@ -0,0 +1,24 @@ +id; + } + + public function getName() + { + return $this->name; + } +} diff --git a/src/Viserio/Bridge/Doctrine/ORM/Tests/ManagerRegistryTest.php b/src/Viserio/Bridge/Doctrine/ORM/Tests/ManagerRegistryTest.php new file mode 100644 index 000000000..4c18261ab --- /dev/null +++ b/src/Viserio/Bridge/Doctrine/ORM/Tests/ManagerRegistryTest.php @@ -0,0 +1,50 @@ +container = $this->mock(ContainerInterface::class); + $this->factory = $this->mock(EntityManagerFactory::class); + + $this->registry = new ManagerRegistry( + $this->container, + $this->factory + ); + } + + public function testCannotNonExistingConnection(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Doctrine Connection named [non-existing] does not exist.'); + + $this->registry->getConnection('non-existing'); + } +} diff --git a/src/Viserio/Bridge/Doctrine/ORM/Tests/Provider/ConsoleCommandsServiceProviderTest.php b/src/Viserio/Bridge/Doctrine/ORM/Tests/Provider/ConsoleCommandsServiceProviderTest.php new file mode 100644 index 000000000..599da46a1 --- /dev/null +++ b/src/Viserio/Bridge/Doctrine/ORM/Tests/Provider/ConsoleCommandsServiceProviderTest.php @@ -0,0 +1,69 @@ +mock(EntityManagerInterface::class); + $entityManager->shouldReceive('getConnection') + ->once() + ->andReturn($this->mock(Connection::class)); + $container = new Container(); + $container->instance(EntityManagerInterface::class, $entityManager); + $container->register(new ConsoleServiceProvider()); + $container->register(new ConsoleCommandsServiceProvider()); + + $container->instance('config', [ + 'viserio' => [ + 'console' => [ + 'version' => '1', + ], + ], + ]); + + $console = $container->get(Application::class); + $commands = $console->all(); + + $this->assertInstanceOf(MetadataCommand::class, $commands['orm:clear-cache:metadata']); + $this->assertInstanceOf(QueryCommand::class, $commands['orm:clear-cache:query']); + $this->assertInstanceOf(ResultCommand::class, $commands['orm:clear-cache:result']); + $this->assertInstanceOf(ConvertDoctrine1SchemaCommand::class, $commands['orm:convert-d1-schema']); + $this->assertInstanceOf(ConvertMappingCommand::class, $commands['orm:convert-mapping']); + $this->assertInstanceOf(EnsureProductionSettingsCommand::class, $commands['orm:ensure-production-settings']); + $this->assertInstanceOf(GenerateRepositoriesCommand::class, $commands['orm:generate-repositories']); + $this->assertInstanceOf(InfoCommand::class, $commands['orm:info']); + $this->assertInstanceOf(MappingDescribeCommand::class, $commands['orm:mapping:describe']); + $this->assertInstanceOf(RunDqlCommand::class, $commands['orm:run-dql']); + $this->assertInstanceOf(CreateCommand::class, $commands['orm:schema-tool:create']); + $this->assertInstanceOf(DropCommand::class, $commands['orm:schema-tool:drop']); + $this->assertInstanceOf(UpdateCommand::class, $commands['orm:schema-tool:update']); + $this->assertInstanceOf(ValidateSchemaCommand::class, $commands['orm:validate-schema']); + } +} diff --git a/src/Viserio/Bridge/Doctrine/ORM/Tests/Provider/DoctrineORMServiceProviderTest.php b/src/Viserio/Bridge/Doctrine/ORM/Tests/Provider/DoctrineORMServiceProviderTest.php new file mode 100644 index 000000000..05dad5c49 --- /dev/null +++ b/src/Viserio/Bridge/Doctrine/ORM/Tests/Provider/DoctrineORMServiceProviderTest.php @@ -0,0 +1,16 @@ +register(new OptionsResolverServiceProvider()); +// } +// } diff --git a/src/Viserio/Bridge/Doctrine/ORM/Tests/Resolvers/EntityListenerResolverTest.php b/src/Viserio/Bridge/Doctrine/ORM/Tests/Resolvers/EntityListenerResolverTest.php new file mode 100644 index 000000000..8cc88bbf8 --- /dev/null +++ b/src/Viserio/Bridge/Doctrine/ORM/Tests/Resolvers/EntityListenerResolverTest.php @@ -0,0 +1,130 @@ +assertInstanceOf( + ResolverContract::class, + new EntityListenerResolver($this->mock(ContainerInterface::class)) + ); + } + + public function testResolvesFromContainer(): void + { + $object = new stdClass(); + + $container = $this->mock(ContainerInterface::class); + $container->shouldReceive('get') + ->with('class') + ->andReturn($object); + $resolver = new EntityListenerResolver($container); + + $resolvedObject = $resolver->resolve('class'); + + $this->assertSame($object, $resolvedObject, 'Resolver should retrieve the object from the container.'); + } + + public function testHoldsReferenceAfterResolve(): void + { + $object = new stdClass(); + $anotherObject = new stdClass(); + + $container = $this->mock(ContainerInterface::class); + $container->shouldReceive('get') + ->with('class') + ->once() + ->andReturn($object, $anotherObject); + + $resolver = new EntityListenerResolver($container); + + $resolvedObject = $resolver->resolve('class'); + $resolvedObjectAgain = $resolver->resolve('class'); + + $this->assertSame($object, $resolvedObject, 'Resolver should retrieve the object from the container.'); + $this->assertSame($object, $resolvedObjectAgain, 'Resolver should retrieve the object from its own reference.'); + } + + public function testClearsHeldReference(): void + { + $object = new stdClass(); + $anotherObject = new stdClass(); + + $container = $this->mock(ContainerInterface::class); + $container->shouldReceive('get') + ->with('class') + ->twice() + ->andReturn($object, $anotherObject); + + $resolver = new EntityListenerResolver($container); + $resolver->resolve('class'); + $resolver->clear('class'); + + $resolvedObjectAgain = $resolver->resolve('class'); + + $this->assertSame($anotherObject, $resolvedObjectAgain, 'Resolver should got back to container after clear'); + } + + public function testClearsAllHeldReferences(): void + { + $object = new stdClass(); + $anotherObject = new stdClass(); + $oneMoreObject = new stdClass(); + $yetAnotherObject = new stdClass(); + + $container = $this->mock(ContainerInterface::class); + $container->shouldReceive('get') + ->with('class') + ->twice() + ->andReturn($object, $anotherObject); + $container->shouldReceive('get') + ->with('class2') + ->twice() + ->andReturn($oneMoreObject, $yetAnotherObject); + + $resolver = new EntityListenerResolver($container); + $resolver->resolve('class'); + $resolver->resolve('class2'); + $resolver->clear(); + + $resolvedAnotherObject = $resolver->resolve('class'); + $resolvedYetAnotherObject = $resolver->resolve('class2'); + + $this->assertSame($anotherObject, $resolvedAnotherObject, 'Resolver should retrieve the object from the container.'); + $this->assertSame($yetAnotherObject, $resolvedYetAnotherObject, 'Resolver should retrieve the object from the container.'); + } + + public function testAllowsDirectlyRegisteringListeners(): void + { + $object = new stdClass(); + + $container = $this->mock(ContainerInterface::class); + $resolver = new EntityListenerResolver($container); + $resolver->register($object); + + $resolvedObject = $resolver->resolve(\get_class($object)); + + $this->assertSame($object, $resolvedObject, 'Resolver should not use container when directly registering'); + } + + public function testDoesNotAllowRegisteringNonObjects(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('An object was expected, but got "string".'); + + $container = $this->mock(ContainerInterface::class); + $resolver = new EntityListenerResolver($container); + $resolver->register('foo'); + } +} diff --git a/src/Viserio/Bridge/Doctrine/ORM/Tests/Serializers/ArrayEncoderTest.php b/src/Viserio/Bridge/Doctrine/ORM/Tests/Serializers/ArrayEncoderTest.php new file mode 100644 index 000000000..6c2413b17 --- /dev/null +++ b/src/Viserio/Bridge/Doctrine/ORM/Tests/Serializers/ArrayEncoderTest.php @@ -0,0 +1,48 @@ +serialize(new ArrayableEntityFixture()); + + $this->assertEquals( + [ + 'id' => 'IDVALUE', + 'name' => 'NAMEVALUE', + ], + $array + ); + } + + public function testEntityCanSerializeToArrayWithArrayableTrait(): void + { + $this->assertEquals( + [ + 'id' => 'IDVALUE', + 'name' => 'NAMEVALUE', + ], + (new ArrayableEntityFixture())->toArray() + ); + } + + private function serialize($entity) + { + $serializer = new Serializer([new GetSetMethodNormalizer()], [ + 'array' => new ArrayEncoder(), + ]); + + return $serializer->serialize($entity, 'array'); + } +} diff --git a/src/Viserio/Bridge/Doctrine/ORM/Tests/Stub/database.sqlite b/src/Viserio/Bridge/Doctrine/ORM/Tests/Stub/database.sqlite new file mode 100644 index 000000000..ccbe3558e Binary files /dev/null and b/src/Viserio/Bridge/Doctrine/ORM/Tests/Stub/database.sqlite differ diff --git a/src/Viserio/Bridge/Doctrine/ORM/alias.php b/src/Viserio/Bridge/Doctrine/ORM/alias.php new file mode 100644 index 000000000..e667d2f40 --- /dev/null +++ b/src/Viserio/Bridge/Doctrine/ORM/alias.php @@ -0,0 +1,18 @@ + + + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./vendor + ./Tests + + + + + diff --git a/src/Viserio/Bridge/DoctrineExtensions/.github/PULL_REQUEST_TEMPLATE.md b/src/Viserio/Bridge/DoctrineExtensions/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..3a1c85818 --- /dev/null +++ b/src/Viserio/Bridge/DoctrineExtensions/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,5 @@ +This is a READ ONLY repository. + +Please make your pull request to https://github.com/narrowspark/framework + +Thank you for contributing. diff --git a/src/Viserio/Bridge/DoctrineExtensions/LICENSE b/src/Viserio/Bridge/DoctrineExtensions/LICENSE new file mode 100644 index 000000000..9941d8719 --- /dev/null +++ b/src/Viserio/Bridge/DoctrineExtensions/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 - 2017 Narrowspark + +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. diff --git a/src/Viserio/Bridge/DoctrineExtensions/Provider/ExtensionsServiceProvider.php b/src/Viserio/Bridge/DoctrineExtensions/Provider/ExtensionsServiceProvider.php new file mode 100644 index 000000000..d9cde53fe --- /dev/null +++ b/src/Viserio/Bridge/DoctrineExtensions/Provider/ExtensionsServiceProvider.php @@ -0,0 +1,139 @@ + false, + ]; + } + + /** + * Check if a annotations driver exists. + * + * @param \Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain $chain + * + * @return bool + */ + private static function hasAnnotationReader(MappingDriverChain $chain): bool + { + foreach ($chain->getDrivers() as $driver) { + if ($driver instanceof AnnotationDriver) { + return true; + } + } + + return false; + } + + /** + * Register gedmo for annotations. + * + * @param \Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain $chain + * + * @return void + */ + private static function registerGedmoForAnnotations(MappingDriverChain $chain): void + { + if (self::needsAllMappings()) { + DoctrineExtensions::registerMappingIntoDriverChainORM( + $chain, + $chain->getReader() + ); + } else { + DoctrineExtensions::registerAbstractMappingIntoDriverChainORM( + $chain, + $chain->getReader() + ); + } + } + + /** + * Check if a fluent driver exists. + * + * @param \Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain $chain + * + * @return bool + */ + private static function hasFluentDriver(MappingDriverChain $chain): bool + { + foreach ($chain->getDrivers() as $driver) { + if ($driver instanceof FluentDriver) { + return true; + } + } + + return false; + } + + /** + * Register gedmo for fluent. + * + * @param \Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain $chain + * + * @return void + */ + private static function registerGedmoForFluent(MappingDriverChain $chain): void + { + if (self::needsAllMappings()) { + GedmoExtensions::registerAll($chain); + } else { + GedmoExtensions::registerAbstract($chain); + } + } + + /** + * Check if all mappings are needed. + * + * @return bool + */ + private static function needsAllMappings(): bool + { + return self::$options['all_mappings'] === true; + } +} diff --git a/src/Viserio/Bridge/DoctrineExtensions/README.md b/src/Viserio/Bridge/DoctrineExtensions/README.md new file mode 100644 index 000000000..a880132af --- /dev/null +++ b/src/Viserio/Bridge/DoctrineExtensions/README.md @@ -0,0 +1,33 @@ +# Viserio Doctrine Extensions package + +This package is part of the [Narrowspark framework](http://github.com/narrowspark/framework). + +[![Author](http://img.shields.io/badge/author-@anolilab-blue.svg?style=flat-square)](https://twitter.com/anolilab) +[![Quality Score](https://img.shields.io/scrutinizer/g/narrowspark/framework.svg?style=flat-square)](https://scrutinizer-ci.com/g/narrowspark/framework/code-structure/master) +[![Build Status](https://api.travis-ci.org/narrowspark/framework.svg?branch=master&style=flat-square)](https://travis-ci.org/narrowspark/framework) +[![Latest Version](https://img.shields.io/packagist/v/narrowspark/framework.svg?style=flat-square)](https://github.com/narrowspark/framework/releases) +[![Minimum PHP Version](https://img.shields.io/badge/php-%5E7.1.0-8892BF.svg?style=flat-square)](https://php.net/) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +> **Note:** If you want to build an application using Narrowspark, visit the main [![Source Code](http://img.shields.io/badge/source-narrowspark/narrowspark-blue.svg?style=flat-square)](https://github.com/narrowspark/narrowspark). + +## Contributing + +Issues for this package shall be posted on [Narrowspark framework issues](http://github.com/narrowspark/framework/issues). +Thank you for considering contributing to the Narrowspark framework! The contribution guide can be found in the [Narrowspark documentation](http://narrowspark.de/docs/contributions). + +## Installation + +Use [Composer](https://getcomposer.org/) to install this package: + +```sh +composer require viserio/bridge-doctrine-extensions +``` + +## Official Documentation + +Documentation for the framework can be found on the [Narrowspark website](http://narrowspark.de/docs). + +### License + +The Narrowspark framework is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT). diff --git a/src/Viserio/Bridge/DoctrineExtensions/Types/ChronosDateTimeType.php b/src/Viserio/Bridge/DoctrineExtensions/Types/ChronosDateTimeType.php new file mode 100644 index 000000000..c43bcb27c --- /dev/null +++ b/src/Viserio/Bridge/DoctrineExtensions/Types/ChronosDateTimeType.php @@ -0,0 +1,3 @@ + + + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./vendor + ./Tests + + + + + diff --git a/src/Viserio/Bridge/DoctrineTesting/.github/PULL_REQUEST_TEMPLATE.md b/src/Viserio/Bridge/DoctrineTesting/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..3a1c85818 --- /dev/null +++ b/src/Viserio/Bridge/DoctrineTesting/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,5 @@ +This is a READ ONLY repository. + +Please make your pull request to https://github.com/narrowspark/framework + +Thank you for contributing. diff --git a/src/Viserio/Bridge/DoctrineTesting/Cache/StaticArrayCache.php b/src/Viserio/Bridge/DoctrineTesting/Cache/StaticArrayCache.php new file mode 100644 index 000000000..76a2a265c --- /dev/null +++ b/src/Viserio/Bridge/DoctrineTesting/Cache/StaticArrayCache.php @@ -0,0 +1,120 @@ +upTime = \time(); + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + if (! $this->doContains($id)) { + $this->missesCount++; + + return false; + } + + $this->hitsCount++; + + return self::$data[$id][0]; + } + + /** + * {@inheritdoc} + */ + protected function doContains($id): bool + { + if (! isset(self::$data[$id])) { + return false; + } + + $expiration = self::$data[$id][1]; + + if ($expiration && $expiration < \time()) { + $this->doDelete($id); + + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0): bool + { + self::$data[$id] = [$data, $lifeTime ? \time() + $lifeTime : false]; + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id): bool + { + unset(self::$data[$id]); + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doFlush(): bool + { + self::$data = []; + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doGetStats(): ?array + { + return [ + Cache::STATS_HITS => $this->hitsCount, + Cache::STATS_MISSES => $this->missesCount, + Cache::STATS_UPTIME => $this->upTime, + Cache::STATS_MEMORY_USAGE => null, + Cache::STATS_MEMORY_AVAILABLE => null, + ]; + } +} diff --git a/src/Viserio/Bridge/DoctrineTesting/Commands/LoadDataFixturesDoctrineCommand.php b/src/Viserio/Bridge/DoctrineTesting/Commands/LoadDataFixturesDoctrineCommand.php new file mode 100644 index 000000000..f2a93dc70 --- /dev/null +++ b/src/Viserio/Bridge/DoctrineTesting/Commands/LoadDataFixturesDoctrineCommand.php @@ -0,0 +1,13 @@ +find($class, $id); + + Assert::assertNotNull( + $entity, + \sprintf( + 'A [%s] entity was not found by id: %s', + $class, + \print_r($id, true) + ) + ); + + return $entity; + } + + /** + * Check if a entitie dont exists. + * + * @param string $class + * @param mixed $id + * + * @return void + */ + public static function entityDoesNotExist(string $class, $id): void + { + Assert::assertNull( + self::entityManager()->find($class, $id), + \sprintf( + 'A [%s] entity was found by id: %s', + $class, + \print_r($id, true) + ) + ); + } + + /** + * Check if entities match. + * + * @param string $class + * @param array $criteria + * @param null|int $count + * + * @return object[] + */ + public static function entitiesMatch(string $class, array $criteria, ?int $count = null): array + { + $entities = self::entityManager()->getRepository($class)->findBy($criteria); + + Assert::assertNotEmpty( + $entities, + \sprintf( + 'No [%s] entities were found with the given criteria: %s', + $class, + \print_r($criteria, true) + ) + ); + + if ($count !== null) { + Assert::assertCount( + $count, + $entities, + \sprintf( + 'Expected to find %s [%s] entities, but found %s with the given criteria: %s', + $count, + $class, + \count($entities), + \print_r($criteria, true) + ) + ); + } + + return $entities; + } + + /** + * Check if entities does not match. + * + * @param string $class + * @param array $criteria + * + * @return void + */ + public static function noEntitiesMatch(string $class, array $criteria): void + { + Assert::assertEmpty( + self::entityManager()->getRepository($class)->findBy($criteria), + "Some [${class}] entities were found with the given criteria: " . \print_r($criteria, true) + ); + } + + /** + * Create a new EntityManager class. + * + * @return \Doctrine\ORM\EntityManager + */ + abstract protected static function entityManager(): EntityManager; +} diff --git a/src/Viserio/Bridge/DoctrineTesting/DBAL/StaticConnection.php b/src/Viserio/Bridge/DoctrineTesting/DBAL/StaticConnection.php new file mode 100644 index 000000000..b50fc28dc --- /dev/null +++ b/src/Viserio/Bridge/DoctrineTesting/DBAL/StaticConnection.php @@ -0,0 +1,123 @@ +connection = $connection; + } + + /** + * {@inheritdoc} + */ + public function prepare($prepareString) + { + return $this->connection->prepare($prepareString); + } + + /** + * {@inheritdoc} + */ + public function query() + { + return \call_user_func_array([$this->connection, 'query'], \func_get_args()); + } + + /** + * {@inheritdoc} + */ + public function quote($input, $type = \PDO::PARAM_STR) + { + return $this->connection->quote($input, $type); + } + + /** + * {@inheritdoc} + */ + public function exec($statement) + { + return $this->connection->exec($statement); + } + + /** + * {@inheritdoc} + */ + public function lastInsertId($name = null) + { + return $this->connection->lastInsertId($name); + } + + /** + * {@inheritdoc} + */ + public function beginTransaction() + { + if ($this->transactionStarted) { + return $this->connection->beginTransaction(); + } + + return $this->transactionStarted = true; + } + + /** + * {@inheritdoc} + */ + public function commit() + { + return $this->connection->commit(); + } + + /** + * {@inheritdoc} + */ + public function rollBack() + { + return $this->connection->rollBack(); + } + + /** + * {@inheritdoc} + */ + public function errorCode() + { + return $this->connection->errorCode(); + } + + /** + * {@inheritdoc} + */ + public function errorInfo() + { + return $this->connection->errorInfo(); + } + + /** + * @return Connection + */ + public function getWrappedConnection(): Connection + { + return $this->connection; + } +} diff --git a/src/Viserio/Bridge/DoctrineTesting/DBAL/StaticConnectionManager.php b/src/Viserio/Bridge/DoctrineTesting/DBAL/StaticConnectionManager.php new file mode 100644 index 000000000..5db6a0b98 --- /dev/null +++ b/src/Viserio/Bridge/DoctrineTesting/DBAL/StaticConnectionManager.php @@ -0,0 +1,39 @@ +getParams(), + new StaticDriver($connectionOriginalDriver->getDriver(), $connectionOriginalDriver->getDatabasePlatform()), + $connectionOriginalDriver->getConfiguration(), + $connectionOriginalDriver->getEventManager() + ); + + if (StaticDriver::isKeepStaticConnections()) { + // The underlying connection already has a transaction started. + // Make sure we use savepoints to be able to easily roll-back nested transactions + if ($connection->getDriver()->getDatabasePlatform()->supportsSavepoints()) { + $connection->setNestTransactionsWithSavepoints(true); + } + + // We start a transaction on the connection as well + // so the internal state ($_transactionNestingLevel) is in sync with the underlying connection. + $connection->beginTransaction(); + } + + return $connection; + } +} diff --git a/src/Viserio/Bridge/DoctrineTesting/DBAL/StaticDriver.php b/src/Viserio/Bridge/DoctrineTesting/DBAL/StaticDriver.php new file mode 100644 index 000000000..d37f68e20 --- /dev/null +++ b/src/Viserio/Bridge/DoctrineTesting/DBAL/StaticDriver.php @@ -0,0 +1,187 @@ +underlyingDriver = $underlyingDriver; + $this->platform = $platform; + } + + /** + * Is the connection held. + * + * @return bool + */ + public static function isKeepStaticConnections(): bool + { + return self::$keepStaticConnections; + } + + /** + * Should the connection be kept? + * + * @param bool $keepStaticConnections + * + * @return void + */ + public static function setKeepStaticConnections(bool $keepStaticConnections): void + { + self::$keepStaticConnections = $keepStaticConnections; + } + + /** + * {@inheritdoc} + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = []) + { + if (self::$keepStaticConnections) { + $key = \sha1(\serialize($params) . $username . $password); + + if (! isset(self::$connections[$key])) { + self::$connections[$key] = $this->underlyingDriver->connect($params, $username, $password, $driverOptions); + self::$connections[$key]->beginTransaction(); + } + + return new StaticConnection(self::$connections[$key]); + } + + return $this->underlyingDriver->connect($params, $username, $password, $driverOptions); + } + + /** + * {@inheritdoc} + */ + public function getDatabasePlatform() + { + return $this->platform; + } + + /** + * {@inheritdoc} + */ + public function getSchemaManager(Connection $conn) + { + return $this->underlyingDriver->getSchemaManager($conn); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->underlyingDriver->getName(); + } + + /** + * {@inheritdoc} + */ + public function getDatabase(Connection $conn) + { + return $this->underlyingDriver->getDatabase($conn); + } + + /** + * {@inheritdoc} + */ + public function convertException($message, DriverException $exception) + { + if ($this->underlyingDriver instanceof ExceptionConverterDriver) { + return $this->underlyingDriver->convertException($message, $exception); + } + + return $exception; + } + + /** + * {@inheritdoc} + */ + public function createDatabasePlatformForVersion($version) + { + return $this->platform; + } + + /** + * Begins a transaction. + * + * @return void + */ + public static function beginTransaction(): void + { + foreach (self::$connections as $connection) { + try { + $connection->beginTransaction(); + } catch (PDOException $exception) { + // transaction could be started already + } + } + } + + /** + * Rolls back a transaction. + * + * @return void + */ + public static function rollBack(): void + { + foreach (self::$connections as $connection) { + $connection->rollBack(); + } + } + + /** + * Commits a transaction. + * + * @return void + */ + public static function commit(): void + { + foreach (self::$connections as $connection) { + $connection->commit(); + } + } +} diff --git a/src/Viserio/Bridge/DoctrineTesting/Factory.php b/src/Viserio/Bridge/DoctrineTesting/Factory.php new file mode 100644 index 000000000..c8bbb69a8 --- /dev/null +++ b/src/Viserio/Bridge/DoctrineTesting/Factory.php @@ -0,0 +1,227 @@ +faker = $faker; + $this->registry = $registry; + + if (! \is_dir($pathToFactories)) { + throw new RuntimeException(\sprintf('[%s] is not a directory.', $pathToFactories)); + } + + foreach (Finder::create()->files()->name('*.php')->in($pathToFactories) as $file) { + require $file->getRealPath(); + } + } + + /** + * Define a class with a given short-name. + * + * @param string $class + * @param string $name + * @param callable $attributes + * + * @return void + */ + public function defineAs(string $class, string $name, callable $attributes): void + { + $this->define($class, $attributes, $name); + } + + /** + * Define a class with a given set of attributes. + * + * @param string $class + * @param callable $attributes + * @param string $name + * + * @return void + */ + public function define(string $class, callable $attributes, string $name = 'default'): void + { + $this->definitions[$class][$name] = $attributes; + } + + /** + * Create an instance of the given model and persist it to the database. + * + * @param string $class + * @param array $attributes + * + * @return mixed + */ + public function create(string $class, array $attributes = []) + { + return $this->of($class)->create($attributes); + } + + /** + * Create an instance of the given model and type and persist it to the database. + * + * @param string $class + * @param string $name + * @param array $attributes + * + * @return mixed + */ + public function createAs(string $class, string $name, array $attributes = []) + { + return $this->of($class, $name)->create($attributes); + } + + /** + * Create an instance of the given model. + * + * @param string $class + * @param array $attributes + * + * @return mixed + */ + public function make(string $class, array $attributes = []) + { + return $this->of($class)->make($attributes); + } + + /** + * Create an instance of the given model and type. + * + * @param string $class + * @param string $name + * @param array $attributes + * + * @return mixed + */ + public function makeAs(string $class, string $name, array $attributes = []) + { + return $this->of($class, $name)->make($attributes); + } + + /** + * Get the raw attribute array for a given named model. + * + * @param string $class + * @param string $name + * @param array $attributes + * + * @return array + */ + public function rawOf(string $class, string $name, array $attributes = []): array + { + return $this->raw($class, $attributes, $name); + } + + /** + * Get the raw attribute array for a given model. + * + * @param string $class + * @param array $attributes + * @param string $name + * + * @return array + */ + public function raw(string $class, array $attributes = [], string $name = 'default'): array + { + $raw = \call_user_func($this->definitions[$class][$name], $this->faker); + + return \array_merge($raw, $attributes); + } + + /** + * Create a builder for the given model. + * + * @param string $class + * @param string $name + * + * @return \Viserio\Bridge\Doctrine\Testing\FactoryBuilder + */ + public function of(string $class, string $name = 'default'): FactoryBuilder + { + return new FactoryBuilder($this->registry, $class, $name, $this->definitions, $this->faker); + } + + /** + * Determine if the given offset exists. + * + * @param string $offset + * + * @return bool + */ + public function offsetExists($offset): bool + { + return isset($this->definitions[$offset]); + } + + /** + * Get the value of the given offset. + * + * @param string $offset + * + * @return mixed + */ + public function offsetGet($offset) + { + return $this->make($offset); + } + + /** + * Set the given offset to the given value. + * + * @param string $offset + * @param callable $value + * + * @return void + */ + public function offsetSet($offset, $value): void + { + $this->define($offset, $value); + } + + /** + * Unset the value at the given offset. + * + * @param string $offset + * + * @return void + */ + public function offsetUnset($offset): void + { + unset($this->definitions[$offset]); + } +} diff --git a/src/Viserio/Bridge/DoctrineTesting/FactoryBuilder.php b/src/Viserio/Bridge/DoctrineTesting/FactoryBuilder.php new file mode 100644 index 000000000..54037f41d --- /dev/null +++ b/src/Viserio/Bridge/DoctrineTesting/FactoryBuilder.php @@ -0,0 +1,159 @@ +name = $name; + $this->class = $class; + $this->faker = $faker; + $this->registry = $registry; + $this->definitions = $definitions; + } + + /** + * Set the amount of models you wish to create / make. + * + * @param int $amount + * + * @return $this + */ + public function times(int $amount): self + { + $this->amount = $amount; + + return $this; + } + + /** + * Create a collection of models and persist them to the database. + * + * @param array $attributes + * + * @return mixed + */ + public function create(array $attributes = []) + { + $results = $this->make($attributes); + $manager = $this->registry->getManagerForClass($this->class); + + if ($this->amount === 1) { + $manager->persist($results); + } else { + foreach ($results as $result) { + $manager->persist($result); + } + } + + $manager->flush(); + + return $results; + } + + /** + * Create a collection of models. + * + * @param array $attributes + * + * @return mixed + */ + public function make(array $attributes = []) + { + if ($this->amount === 1) { + return $this->makeInstance($attributes); + } + + $results = []; + + for ($i = 0; $i < $this->amount; $i++) { + $results[] = $this->makeInstance($attributes); + } + + return new Collection($results); + } + + /** + * Make an instance of the model with the given attributes. + * + * @param array $attributes + * + * @return mixed + */ + protected function makeInstance(array $attributes = []) + { + if (! isset($this->definitions[$this->class][$this->name])) { + throw new InvalidArgumentException("Unable to locate factory with name [{$this->name}]."); + } + + $definition = \call_user_func($this->definitions[$this->class][$this->name], $this->faker, $attributes); + + if ($definition instanceof $this->class) { + return $definition; + } + + return SimpleHydrator::hydrate($this->class, \array_merge($definition, $attributes)); + } +} diff --git a/src/Viserio/Bridge/DoctrineTesting/LICENSE b/src/Viserio/Bridge/DoctrineTesting/LICENSE new file mode 100644 index 000000000..9941d8719 --- /dev/null +++ b/src/Viserio/Bridge/DoctrineTesting/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 - 2017 Narrowspark + +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. diff --git a/src/Viserio/Bridge/DoctrineTesting/PHPUnit/PHPUnitListener.php b/src/Viserio/Bridge/DoctrineTesting/PHPUnit/PHPUnitListener.php new file mode 100644 index 000000000..0fc55ba74 --- /dev/null +++ b/src/Viserio/Bridge/DoctrineTesting/PHPUnit/PHPUnitListener.php @@ -0,0 +1,37 @@ + [self::class, 'extendConsole'], + ]; + } + + /** + * Extend viserio console with commands. + * + * @param \Psr\Container\ContainerInterface $container + * @param null|\Viserio\Component\Console\Application $console + * + * @return null|\Viserio\Component\Console\Application + */ + public static function extendConsole(ContainerInterface $container, ?Application $console = null): ?Application + { + if ($console !== null) { + $console->add(new LoadDataFixturesDoctrineCommand()); + } + + return $console; + } +} diff --git a/src/Viserio/Bridge/DoctrineTesting/Provider/DoctrineORMTestingServiceProvider.php b/src/Viserio/Bridge/DoctrineTesting/Provider/DoctrineORMTestingServiceProvider.php new file mode 100644 index 000000000..d089c9ffa --- /dev/null +++ b/src/Viserio/Bridge/DoctrineTesting/Provider/DoctrineORMTestingServiceProvider.php @@ -0,0 +1,80 @@ + [self::class, 'createEntityFactory'], + FakerGenerator::class => [self::class, 'createFakerGenerator'], + ]; + } + + /** + * {@inheritdoc} + */ + public function getExtensions(): array + { + return []; + } + + /** + * {@inheritdoc} + */ + public static function getDimensions(): array + { + return ['viserio', 'doctrine']; + } + + /** + * {@inheritdoc} + */ + public static function getDefaultOptions(): array + { + return [ + 'locale' => FakerFactory::DEFAULT_LOCALE, + ]; + } + + /** + * @param \Psr\Container\ContainerInterface $container + * + * @return EntityFactory + */ + public static function createEntityFactory(ContainerInterface $container): EntityFactory + { + return new EntityFactory( + $container->get(FakerGenerator::class), + $container->get(ManagerRegistry::class) + ); + } + + /** + * Create a new instance of faker generator. + * + * @param \Psr\Container\ContainerInterface $container + * + * @return \Faker\Generator + */ + public static function createFakerGenerator(ContainerInterface $container): FakerGenerator + { + return FakerFactory::create(); + } +} diff --git a/src/Viserio/Bridge/DoctrineTesting/Provider/DoctrineTestingServiceProvier.php b/src/Viserio/Bridge/DoctrineTesting/Provider/DoctrineTestingServiceProvier.php new file mode 100644 index 000000000..63f902803 --- /dev/null +++ b/src/Viserio/Bridge/DoctrineTesting/Provider/DoctrineTestingServiceProvier.php @@ -0,0 +1,51 @@ + [self::class, 'createStaticConnectionManager'], + ]; + } + + /** + * Create a new instance of faker generator. + * + * @param \Psr\Container\ContainerInterface $container + * @param null|\Viserio\Bridge\Doctrine\DBAL\ConnectionManager $connectionManager + * + * @return \Viserio\Bridge\Doctrine\Testing\DBAL\StaticConnectionManager + */ + public static function createStaticConnectionManager( + ContainerInterface $container, + ?ConnectionManager $connectionManager + ): StaticConnectionManager { + $manager = new StaticConnectionManager($container); + + if ($connectionManager !== null) { + $manager->setDoctrineConfiguration($manager->getDoctrineConfiguration()); + $manager->setDoctrineEventManager($manager->getDoctrineEventManager()); + } + + return $manager; + } +} diff --git a/src/Viserio/Bridge/DoctrineTesting/README.md b/src/Viserio/Bridge/DoctrineTesting/README.md new file mode 100644 index 000000000..959090bb3 --- /dev/null +++ b/src/Viserio/Bridge/DoctrineTesting/README.md @@ -0,0 +1,33 @@ +# Viserio Doctrine Testing package + +This package is part of the [Narrowspark framework](http://github.com/narrowspark/framework). + +[![Author](http://img.shields.io/badge/author-@anolilab-blue.svg?style=flat-square)](https://twitter.com/anolilab) +[![Quality Score](https://img.shields.io/scrutinizer/g/narrowspark/framework.svg?style=flat-square)](https://scrutinizer-ci.com/g/narrowspark/framework/code-structure/master) +[![Build Status](https://api.travis-ci.org/narrowspark/framework.svg?branch=master&style=flat-square)](https://travis-ci.org/narrowspark/framework) +[![Latest Version](https://img.shields.io/packagist/v/narrowspark/framework.svg?style=flat-square)](https://github.com/narrowspark/framework/releases) +[![Minimum PHP Version](https://img.shields.io/badge/php-%5E7.1.0-8892BF.svg?style=flat-square)](https://php.net/) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +> **Note:** If you want to build an application using Narrowspark, visit the main [![Source Code](http://img.shields.io/badge/source-narrowspark/narrowspark-blue.svg?style=flat-square)](https://github.com/narrowspark/narrowspark). + +## Contributing + +Issues for this package shall be posted on [Narrowspark framework issues](http://github.com/narrowspark/framework/issues). +Thank you for considering contributing to the Narrowspark framework! The contribution guide can be found in the [Narrowspark documentation](http://narrowspark.de/docs/contributions). + +## Installation + +Use [Composer](https://getcomposer.org/) to install this package: + +```sh +composer require viserio/bridge/doctrine +``` + +## Official Documentation + +Documentation for the framework can be found on the [Narrowspark website](http://narrowspark.de/docs). + +### License + +The Narrowspark framework is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT). diff --git a/src/Viserio/Bridge/DoctrineTesting/SimpleHydrator.php b/src/Viserio/Bridge/DoctrineTesting/SimpleHydrator.php new file mode 100644 index 000000000..7db593ddd --- /dev/null +++ b/src/Viserio/Bridge/DoctrineTesting/SimpleHydrator.php @@ -0,0 +1,32 @@ +newInstanceWithoutConstructor(); + + foreach ($attributes as $field => $value) { + if ($reflection->hasProperty($field)) { + $property = $reflection->getProperty($field); + $property->setAccessible(true); + $property->setValue($instance, $value); + } + } + + return $instance; + } +} diff --git a/src/Viserio/Bridge/DoctrineTesting/Tests/Cache/StaticArrayCacheTest.php b/src/Viserio/Bridge/DoctrineTesting/Tests/Cache/StaticArrayCacheTest.php new file mode 100644 index 000000000..e849248d9 --- /dev/null +++ b/src/Viserio/Bridge/DoctrineTesting/Tests/Cache/StaticArrayCacheTest.php @@ -0,0 +1,443 @@ +getCacheDriver(); + // Test saving a value, checking if it exists, and fetching it back + $this->assertTrue($cache->save('key', $value)); + $this->assertTrue($cache->contains('key')); + + if (\is_object($value)) { + $this->assertEquals($value, $cache->fetch('key'), 'Objects retrieved from the cache must be equal but not necessarily the same reference'); + } else { + $this->assertSame($value, $cache->fetch('key'), 'Scalar and array data retrieved from the cache must be the same as the original, e.g. same type'); + } + + // Test deleting a value + $this->assertTrue($cache->delete('key')); + $this->assertFalse($cache->contains('key')); + $this->assertFalse($cache->fetch('key')); + } + + /** + * @dataProvider provideDataToCache + * + * @param mixed $value + */ + public function testUpdateExistingEntry($value): void + { + $cache = $this->getCacheDriver(); + + $this->assertTrue($cache->save('key', 'old-value')); + $this->assertTrue($cache->contains('key')); + $this->assertTrue($cache->save('key', $value)); + $this->assertTrue($cache->contains('key')); + + if (\is_object($value)) { + $this->assertEquals($value, $cache->fetch('key'), 'Objects retrieved from the cache must be equal but not necessarily the same reference'); + } else { + $this->assertSame($value, $cache->fetch('key'), 'Scalar and array data retrieved from the cache must be the same as the original, e.g. same type'); + } + } + + public function testCacheKeyIsCaseSensitive(): void + { + $cache = $this->getCacheDriver(); + + $this->assertTrue($cache->save('key', 'value')); + $this->assertTrue($cache->contains('key')); + $this->assertSame('value', $cache->fetch('key')); + $this->assertFalse($cache->contains('KEY')); + $this->assertFalse($cache->fetch('KEY')); + + $cache->delete('KEY'); + + $this->assertTrue($cache->contains('key'), 'Deleting cache item with different case must not affect other cache item'); + } + + public function testFetchMultiple(): void + { + $cache = $this->getCacheDriver(); + $values = $this->provideDataToCache(); + $saved = []; + + foreach ($values as $key => $value) { + $cache->save($key, $value[0]); + $saved[$key] = $value[0]; + } + + $keys = \array_keys($saved); + + $this->assertEquals( + $saved, + $cache->fetchMultiple($keys), + 'Testing fetchMultiple with different data types' + ); + $this->assertEquals( + \array_slice($saved, 0, 1), + $cache->fetchMultiple(\array_slice($keys, 0, 1)), + 'Testing fetchMultiple with a single key' + ); + + $keysWithNonExisting = []; + $keysWithNonExisting[] = 'non_existing1'; + $keysWithNonExisting[] = $keys[0]; + $keysWithNonExisting[] = 'non_existing2'; + $keysWithNonExisting[] = $keys[1]; + $keysWithNonExisting[] = 'non_existing3'; + + $this->assertEquals( + \array_slice($saved, 0, 2), + $cache->fetchMultiple($keysWithNonExisting), + 'Testing fetchMultiple with a subset of keys and mixed with non-existing ones' + ); + } + + public function testFetchMultipleWithNoKeys(): void + { + $cache = $this->getCacheDriver(); + + $this->assertSame([], $cache->fetchMultiple([])); + } + + public function testSaveMultiple(): void + { + $cache = $this->getCacheDriver(); + $cache->deleteAll(); + $data = \array_map(function ($value) { + return $value[0]; + }, $this->provideDataToCache()); + + $this->assertTrue($cache->saveMultiple($data)); + + $keys = \array_keys($data); + + $this->assertEquals($data, $cache->fetchMultiple($keys)); + } + + public function provideDataToCache(): array + { + $obj = new \stdClass(); + $obj->foo = 'bar'; + $obj2 = new \stdClass(); + $obj2->bar = 'foo'; + $obj2->obj = $obj; + $obj->obj2 = $obj2; + + return [ + 'array' => [['one', 2, 3.01]], + 'string' => ['value'], + 'string_invalid_utf8' => ["\xc3\x28"], + 'string_null_byte' => ['with' . "\0" . 'null char'], + 'integer' => [1], + 'float' => [1.5], + 'object' => [new ArrayObject(['one', 2, 3.01])], + 'object_recursive' => [$obj], + 'true' => [true], + // the following are considered FALSE in boolean context, but caches should still recognize their existence + 'null' => [null], + 'false' => [false], + 'array_empty' => [[]], + 'string_zero' => ['0'], + 'integer_zero' => [0], + 'float_zero' => [0.0], + 'string_empty' => [''], + ]; + } + + public function testDeleteIsSuccessfulWhenKeyDoesNotExist(): void + { + $cache = $this->getCacheDriver(); + $cache->delete('key'); + + $this->assertFalse($cache->contains('key')); + $this->assertTrue($cache->delete('key')); + } + + public function testDeleteAll(): void + { + $cache = $this->getCacheDriver(); + + $this->assertTrue($cache->save('key1', 1)); + $this->assertTrue($cache->save('key2', 2)); + $this->assertTrue($cache->deleteAll()); + $this->assertFalse($cache->contains('key1')); + $this->assertFalse($cache->contains('key2')); + } + + public function testDeleteMulti(): void + { + $cache = $this->getCacheDriver(); + + $this->assertTrue($cache->save('key1', 1)); + $this->assertTrue($cache->save('key2', 1)); + $this->assertTrue($cache->deleteMultiple(['key1', 'key2', 'key3'])); + $this->assertFalse($cache->contains('key1')); + $this->assertFalse($cache->contains('key2')); + $this->assertFalse($cache->contains('key3')); + } + + /** + * @dataProvider provideCacheIds + * + * @param mixed $id + */ + public function testCanHandleSpecialCacheIds($id): void + { + $cache = $this->getCacheDriver(); + + $this->assertTrue($cache->save($id, 'value')); + $this->assertTrue($cache->contains($id)); + $this->assertEquals('value', $cache->fetch($id)); + $this->assertTrue($cache->delete($id)); + $this->assertFalse($cache->contains($id)); + $this->assertFalse($cache->fetch($id)); + } + + public function testNoCacheIdCollisions(): void + { + $cache = $this->getCacheDriver(); + $ids = $this->provideCacheIds(); + // fill cache with each id having a different value + foreach ($ids as $index => $id) { + $cache->save($id[0], $index); + } + + // then check value of each cache id + foreach ($ids as $index => $id) { + $value = $cache->fetch($id[0]); + $this->assertNotFalse($value, \sprintf('Failed to retrieve data for cache id "%s".', $id[0])); + + if ($index !== $value) { + $this->fail(\sprintf('Cache id "%s" collides with id "%s".', $id[0], $ids[$value][0])); + } + } + } + + /** + * Returns cache ids with special characters that should still work. + * + * For example, the characters :\/<>"*?| are not valid in Windows filenames. So they must be encoded properly. + * Each cache id should be considered different from the others. + */ + public function provideCacheIds(): array + { + return [ + [':'], + ['\\'], + ['/'], + ['<'], + ['>'], + ['"'], + ['*'], + ['?'], + ['|'], + ['['], + [']'], + ['ä'], + ['a'], + ['é'], + ['e'], + ['.'], // directory traversal + ['..'], // directory traversal + ['-'], + ['_'], + ['$'], + ['%'], + [' '], + ["\0"], + [''], + [\str_repeat('a', 300)], // long key + [\str_repeat('a', 113)], + ]; + } + + public function testLifetime(): void + { + $cache = $this->getCacheDriver(); + $cache->save('expire', 'value', 1); + + $this->assertTrue($cache->contains('expire'), 'Data should not be expired yet'); + + // @TODO should more TTL-based tests pop up, so then we should mock the `time` API instead + \sleep(2); + + $this->assertFalse($cache->contains('expire'), 'Data should be expired'); + } + + public function testNoExpire(): void + { + $cache = $this->getCacheDriver(); + $cache->save('noexpire', 'value', 0); + + // @TODO should more TTL-based tests pop up, so then we should mock the `time` API instead + \sleep(1); + + $this->assertTrue($cache->contains('noexpire'), 'Data with lifetime of zero should not expire'); + } + + public function testLongLifetime(): void + { + $cache = $this->getCacheDriver(); + $cache->save('longlifetime', 'value', 30 * 24 * 3600 + 1); + + $this->assertTrue($cache->contains('longlifetime'), 'Data with lifetime > 30 days should be accepted'); + } + + public function testFlushAll(): void + { + $cache = $this->getCacheDriver(); + + $this->assertTrue($cache->save('key1', 1)); + $this->assertTrue($cache->save('key2', 2)); + $this->assertTrue($cache->flushAll()); + $this->assertFalse($cache->contains('key1')); + $this->assertFalse($cache->contains('key2')); + } + + public function testNamespace(): void + { + $cache = $this->getCacheDriver(); + $cache->setNamespace('ns1_'); + + $this->assertTrue($cache->save('key1', 1)); + $this->assertTrue($cache->contains('key1')); + + $cache->setNamespace('ns2_'); + + $this->assertFalse($cache->contains('key1')); + } + + public function testDeleteAllNamespace(): void + { + $cache = $this->getCacheDriver(); + $cache->setNamespace('ns1'); + + $this->assertFalse($cache->contains('key1')); + + $cache->save('key1', 'test'); + + $this->assertTrue($cache->contains('key1')); + + $cache->setNamespace('ns2'); + + $this->assertFalse($cache->contains('key1')); + + $cache->save('key1', 'test'); + + $this->assertTrue($cache->contains('key1')); + + $cache->setNamespace('ns1'); + + $this->assertTrue($cache->contains('key1')); + + $cache->deleteAll(); + + $this->assertFalse($cache->contains('key1')); + + $cache->setNamespace('ns2'); + + $this->assertTrue($cache->contains('key1')); + + $cache->deleteAll(); + + $this->assertFalse($cache->contains('key1')); + } + + public function testSaveReturnsTrueWithAndWithoutTTlSet(): void + { + $cache = $this->getCacheDriver(); + $cache->deleteAll(); + + $this->assertTrue($cache->save('without_ttl', 'without_ttl')); + $this->assertTrue($cache->save('with_ttl', 'with_ttl', 3600)); + } + + public function testValueThatIsFalseBooleanIsProperlyRetrieved(): void + { + $cache = $this->getCacheDriver(); + $cache->deleteAll(); + + $this->assertTrue($cache->save('key1', false)); + $this->assertTrue($cache->contains('key1')); + $this->assertFalse($cache->fetch('key1')); + } + + /** + * @group 147 + * @group 152 + */ + public function testFetchingANonExistingKeyShouldNeverCauseANoticeOrWarning(): void + { + $cache = $this->getCacheDriver(); + $errorHandler = function (): void { + \restore_error_handler(); + $this->fail('include failure captured'); + }; + \set_error_handler($errorHandler); + $cache->fetch('key'); + + $this->assertSame( + $errorHandler, + \set_error_handler(function (): void { + }), + 'The error handler is the one set by this test, and wasn\'t replaced' + ); + + \restore_error_handler(); + \restore_error_handler(); + } + + public function testGetStats(): void + { + $cache = $this->getCacheDriver(); + $cache->fetch('test1'); + $cache->fetch('test2'); + $cache->fetch('test3'); + $cache->save('test1', 123); + $cache->save('test2', 123); + $cache->fetch('test1'); + $cache->fetch('test2'); + $cache->fetch('test3'); + $stats = $cache->getStats(); + + $this->assertEquals(2, $stats[Cache::STATS_HITS]); + $this->assertEquals(5, $stats[Cache::STATS_MISSES]); // +1 for internal call to DoctrineNamespaceCacheKey + $this->assertNotNull($stats[Cache::STATS_UPTIME]); + $this->assertNull($stats[Cache::STATS_MEMORY_USAGE]); + $this->assertNull($stats[Cache::STATS_MEMORY_AVAILABLE]); + + $cache->delete('test1'); + $cache->delete('test2'); + $cache->fetch('test1'); + $cache->fetch('test2'); + $cache->fetch('test3'); + $stats = $cache->getStats(); + + $this->assertEquals(2, $stats[Cache::STATS_HITS]); + $this->assertEquals(8, $stats[Cache::STATS_MISSES]); // +1 for internal call to DoctrineNamespaceCacheKey + } + + private function getCacheDriver(): CacheProvider + { + return new StaticArrayCache(); + } +} diff --git a/src/Viserio/Bridge/DoctrineTesting/Tests/Concerns/DoctrineEntitiesTestCaseTest.php b/src/Viserio/Bridge/DoctrineTesting/Tests/Concerns/DoctrineEntitiesTestCaseTest.php new file mode 100644 index 000000000..bc05404dc --- /dev/null +++ b/src/Viserio/Bridge/DoctrineTesting/Tests/Concerns/DoctrineEntitiesTestCaseTest.php @@ -0,0 +1,3 @@ +platform = $this->createMock(AbstractPlatform::class); + } + + public function testReturnCorrectPlatform(): void + { + $driver = new StaticDriver(new MockDriver(), $this->platform); + + $this->assertSame($this->platform, $driver->getDatabasePlatform()); + $this->assertSame($this->platform, $driver->createDatabasePlatformForVersion(1)); + } + + public function testConnect(): void + { + $driver = new StaticDriver(new MockDriver(), $this->platform); + $driver::setKeepStaticConnections(true); + + $connection1 = $driver->connect(['database_name' => 1], 'user1', 'pw1'); + $connection2 = $driver->connect(['database_name' => 2], 'user1', 'pw2'); + + $this->assertInstanceOf(StaticConnection::class, $connection1); + $this->assertNotSame($connection1->getWrappedConnection(), $connection2->getWrappedConnection()); + + $driver = new StaticDriver(new MockDriver(), $this->platform); + $connectionNew1 = $driver->connect(['database_name' => 1], 'user1', 'pw1'); + $connectionNew2 = $driver->connect(['database_name' => 2], 'user1', 'pw2'); + + $this->assertSame($connection1->getWrappedConnection(), $connectionNew1->getWrappedConnection()); + $this->assertSame($connection2->getWrappedConnection(), $connectionNew2->getWrappedConnection()); + } +} diff --git a/src/Viserio/Bridge/DoctrineTesting/Tests/FactoryBuilderTest.php b/src/Viserio/Bridge/DoctrineTesting/Tests/FactoryBuilderTest.php new file mode 100644 index 000000000..e2dd7e0e0 --- /dev/null +++ b/src/Viserio/Bridge/DoctrineTesting/Tests/FactoryBuilderTest.php @@ -0,0 +1,9 @@ +name; + } +} diff --git a/src/Viserio/Bridge/DoctrineTesting/Tests/Fixtures/ChildHydrateableClass.php b/src/Viserio/Bridge/DoctrineTesting/Tests/Fixtures/ChildHydrateableClass.php new file mode 100644 index 000000000..dab139861 --- /dev/null +++ b/src/Viserio/Bridge/DoctrineTesting/Tests/Fixtures/ChildHydrateableClass.php @@ -0,0 +1,13 @@ +description; + } +} diff --git a/src/Viserio/Bridge/DoctrineTesting/Tests/Fixtures/MockDriver.php b/src/Viserio/Bridge/DoctrineTesting/Tests/Fixtures/MockDriver.php new file mode 100644 index 000000000..9a9d51f93 --- /dev/null +++ b/src/Viserio/Bridge/DoctrineTesting/Tests/Fixtures/MockDriver.php @@ -0,0 +1,68 @@ +getMock(DriverConnection::class); + } + + /** + * {@inheritdoc} + */ + public function getDatabasePlatform() + { + return $this->getMock(AbstractPlatform::class); + } + + /** + * {@inheritdoc} + */ + public function getSchemaManager(Connection $conn) + { + return $this->getMock(AbstractSchemaManager::class); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'mock'; + } + + /** + * {@inheritdoc} + */ + public function getDatabase(Connection $conn) + { + return 'mock'; + } + + /** + * Create a new phpunit mock. + * + * @param string $className + * + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function getMock(string $className): MockObject + { + $generator = new Generator(); + + return $generator->getMock($className, [], [], '', false); + } +} diff --git a/src/Viserio/Bridge/DoctrineTesting/Tests/Provider/ConsoleCommandsServiceProviderTest.php b/src/Viserio/Bridge/DoctrineTesting/Tests/Provider/ConsoleCommandsServiceProviderTest.php new file mode 100644 index 000000000..aaab8788a --- /dev/null +++ b/src/Viserio/Bridge/DoctrineTesting/Tests/Provider/ConsoleCommandsServiceProviderTest.php @@ -0,0 +1,50 @@ +register(new ConsoleServiceProvider()); + $container->register(new DoctrineDBALServiceProvider()); + $container->register(new ConsoleCommandsServiceProvider()); + + $container->instance('config', [ + 'viserio' => [ + 'console' => [ + 'version' => '1', + ], + 'doctrine' => [ + 'default' => 'mysql', + 'connections' => [ + 'mysql' => [ + 'driver' => 'pdo_mysql', + 'host' => 'DB_HOST', + 'port' => 'DB_PORT', + 'database' => 'DB_DATABASE_NAME', + 'username' => 'DB_DATABASE_USER', + 'password' => 'DB_DATABASE_PASSWORD', + 'charset' => 'DB_CHARSET', 'UTF8', + 'driverOptions' => [1002 => 'SET NAMES utf8'], + ], + ], + ], + ], + ]); + + $console = $container->get(Application::class); + $commands = $console->all(); + } +} diff --git a/src/Viserio/Bridge/DoctrineTesting/Tests/Provider/DoctrineORMTestingServiceProviderTest.php b/src/Viserio/Bridge/DoctrineTesting/Tests/Provider/DoctrineORMTestingServiceProviderTest.php new file mode 100644 index 000000000..e69de29bb diff --git a/src/Viserio/Bridge/DoctrineTesting/Tests/SimpleHydratorTest.php b/src/Viserio/Bridge/DoctrineTesting/Tests/SimpleHydratorTest.php new file mode 100644 index 000000000..2c2009700 --- /dev/null +++ b/src/Viserio/Bridge/DoctrineTesting/Tests/SimpleHydratorTest.php @@ -0,0 +1,33 @@ + 'Ghost', + ]); + + $this->assertInstanceOf(BaseHydrateableClass::class, $entity); + $this->assertEquals('Ghost', $entity->getName()); + } + + public function testCanHydrateWithExtensionOfPrivateProperties(): void + { + $entity = SimpleHydrator::hydrate(ChildHydrateableClass::class, [ + 'name' => 'Ghost', + 'description' => 'Hello World', + ]); + + $this->assertInstanceOf(ChildHydrateableClass::class, $entity); + $this->assertEquals('Ghost', $entity->getName()); + $this->assertEquals('Hello World', $entity->getDescription()); + } +} diff --git a/src/Viserio/Bridge/DoctrineTesting/Tests/Stub/database.sqlite b/src/Viserio/Bridge/DoctrineTesting/Tests/Stub/database.sqlite new file mode 100644 index 000000000..ccbe3558e Binary files /dev/null and b/src/Viserio/Bridge/DoctrineTesting/Tests/Stub/database.sqlite differ diff --git a/src/Viserio/Bridge/DoctrineTesting/composer.json b/src/Viserio/Bridge/DoctrineTesting/composer.json new file mode 100644 index 000000000..fea4496e7 --- /dev/null +++ b/src/Viserio/Bridge/DoctrineTesting/composer.json @@ -0,0 +1,64 @@ +{ + "name": "viserio/doctrine-testing-bridge", + "type": "viserio-bridge", + "description": "The Viserio Doctrine Testing bridge.", + "keywords": [ + "narrowspark", + "viserio", + "database", + "testing", + "doctrine", + "bridge" + ], + "homepage": "http://narrowspark.com", + "license": "MIT", + "authors": [ + { + "name": "Daniel Bannert", + "email": "d.bannert@anolilab.de", + "homepage": "http://www.anolilab.de", + "role": "Developer" + } + ], + "require": { + "php": "^7.1", + "fzaninotto/faker": "^1.6", + "narrowspark/collection": "^0.1", + "phpunit/phpunit": "^7.2.0" + }, + "require-dev": { + "container-interop/service-provider": "^0.3", + "doctrine/data-fixtures": "^1.2", + "viserio/container": "self.version", + "viserio/doctrine-orm-bridge": "self.version" + }, + "suggest": { + "container-interop/service-provider": "Required to use service-provider (^0.3).", + "doctrine/orm": "Required to use the concerns (self.version).", + "viserio/console": "Required to use the data-fixture command (self.version)." + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Viserio\\Bridge\\Doctrine\\Testing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "autoload-dev": { + "psr-4": { + "Viserio\\Bridge\\Doctrine\\Testing\\Tests\\": "Tests/" + } + }, + "minimum-stability": "dev", + "prefer-stable": true, + "support": { + "issues": "https://github.com/narrowspark/framework/issues", + "source": "https://github.com/narrowspark/framework" + } +} diff --git a/src/Viserio/Bridge/DoctrineTesting/phpunit.xml.dist b/src/Viserio/Bridge/DoctrineTesting/phpunit.xml.dist new file mode 100644 index 000000000..95fa99259 --- /dev/null +++ b/src/Viserio/Bridge/DoctrineTesting/phpunit.xml.dist @@ -0,0 +1,40 @@ + + + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./vendor + ./Tests + + + + + diff --git a/src/Viserio/Provider/Doctrine/.github/PULL_REQUEST_TEMPLATE.md b/src/Viserio/Provider/Doctrine/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..3a1c85818 --- /dev/null +++ b/src/Viserio/Provider/Doctrine/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,5 @@ +This is a READ ONLY repository. + +Please make your pull request to https://github.com/narrowspark/framework + +Thank you for contributing. diff --git a/src/Viserio/Provider/Doctrine/Connection.php b/src/Viserio/Provider/Doctrine/Connection.php new file mode 100644 index 000000000..7827e9372 --- /dev/null +++ b/src/Viserio/Provider/Doctrine/Connection.php @@ -0,0 +1,39 @@ +doctrineEventManager; + } + + /** + * Set a doctrine event manager instance. + * + * @param \Doctrine\Common\EventManager $doctrineEventManager + * + * @return void + */ + public function setDoctrineEventManager(EventManager $doctrineEventManager): void + { + $this->doctrineEventManager = $doctrineEventManager; + } + + /** + * Get a doctrine event manager instance or null. + * + * @return null|\Doctrine\DBAL\Configuration + */ + public function getDoctrineConfiguration(): ?Configuration + { + return $this->doctrineConfiguration; + } + + /** + * Set a doctrine event manager instance. + * + * @param \Doctrine\DBAL\Configuration $doctrineConfiguration + * + * @return void + */ + public function setDoctrineConfiguration(Configuration $doctrineConfiguration): void + { + $this->doctrineConfiguration = $doctrineConfiguration; + } + + /** + * {@inheritdoc} + */ + public static function getDimensions(): array + { + return ['viserio', 'doctrine', static::getConfigName()]; + } + + /** + * {@inheritdoc} + */ + public static function getMandatoryOptions(): array + { + return ['default']; + } + + /** + * {@inheritdoc} + */ + public static function getDefaultOptions(): array + { + return [ + 'connections' => [ + 'mysql' => [ + 'driver' => 'pdo_mysql', + 'host' => 'DB_HOST', + 'port' => 'DB_PORT', + 'database' => 'DB_DATABASE_NAME', + 'username' => 'DB_DATABASE_USER', + 'password' => 'DB_DATABASE_PASSWORD', + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'strict' => true, + 'driverOptions' => [PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'utf8mb4\''], + ], + 'sqlite' => [ + 'driver' => 'pdo_sqlite', + 'database' => 'DB_PATH', + ], + 'pgsql' => [ + 'driver' => 'pdo_pgsql', + 'host' => 'DB_HOST', + 'port' => 'DB_PORT', + 'database' => 'DB_DATABASE_NAME', + 'username' => 'DB_DATABASE_USER', + 'password' => 'DB_DATABASE_PASSWORD', + 'charset' => 'utf8mb4', + 'sslmode' => 'prefer', + ], + 'sqlsrv' => [ + 'driver' => 'pdo_sqlsrv', + 'host' => 'DB_HOST', + 'port' => 'DB_PORT', + 'database' => 'DB_DATABASE_NAME', + 'username' => 'DB_DATABASE_USER', + 'password' => 'DB_DATABASE_PASSWORD', + 'charset' => 'utf8mb4', + ], + ], + ]; + } + + /** + * {@inheritdoc} + */ + public function getConnectionConfig(string $name): array + { + return self::parseConfig(parent::getConnectionConfig($name)); + } + + /** + * Create a new mysql doctrine connection. + * + * @param array $config + * + * @throws \Doctrine\DBAL\DBALException + * + * @return \Doctrine\DBAL\Connection|\Viserio\Provider\Doctrine\Connection + */ + protected function createMysqlConnection(array $config): Connection + { + return DriverManager::getConnection($config, $this->getDoctrineConfiguration(), $this->getDoctrineEventManager()); + } + + /** + * Create a new sqlite doctrine connection. + * + * @param array $config + * + * @throws \Doctrine\DBAL\DBALException + * + * @return \Doctrine\DBAL\Connection|\Viserio\Provider\Doctrine\Connection + */ + protected function createSqliteConnection(array $config): Connection + { + return DriverManager::getConnection($config, $this->getDoctrineConfiguration(), $this->getDoctrineEventManager()); + } + + /** + * Create a new pgsql doctrine connection. + * + * @param array $config + * + * @throws \Doctrine\DBAL\DBALException + * + * @return \Doctrine\DBAL\Connection|\Viserio\Provider\Doctrine\Connection + */ + protected function createPgsqlConnection(array $config): Connection + { + return DriverManager::getConnection($config, $this->getDoctrineConfiguration(), $this->getDoctrineEventManager()); + } + + /** + * Create a new sqlsrv doctrine connection. + * + * @param array $config + * + * @throws \Doctrine\DBAL\DBALException + * + * @return \Doctrine\DBAL\Connection|\Viserio\Provider\Doctrine\Connection + */ + protected function createSqlsrvConnection(array $config): Connection + { + return DriverManager::getConnection($config, $this->getDoctrineConfiguration(), $this->getDoctrineEventManager()); + } + + /** + * {@inheritdoc} + */ + protected static function getConfigName(): string + { + return 'dbal'; + } + + /** + * Map our config style to doctrine config. + * + * @param array $config + * + * @return array + */ + private static function parseConfig(array $config): array + { + $doctrineConfig = []; + + $doctrineConfig['wrapperClass'] = $config['wrapper_class'] ?? Connection::class; + + if (\strpos($config['name'], 'sqlite') === false) { + $doctrineConfig['user'] = $config['username']; + $doctrineConfig['dbname'] = $config['database']; + } else { + if (isset($config['username'])) { + $doctrineConfig['user'] = $config['username']; + } + + $doctrineConfig['path'] = $config['database']; + } + + unset($config['username'], $config['database']); + + return \array_merge($config, $doctrineConfig); + } +} diff --git a/src/Viserio/Provider/Doctrine/LICENSE b/src/Viserio/Provider/Doctrine/LICENSE new file mode 100644 index 000000000..9941d8719 --- /dev/null +++ b/src/Viserio/Provider/Doctrine/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 - 2017 Narrowspark + +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. diff --git a/src/Viserio/Provider/Doctrine/Provider/ConsoleCommandsServiceProvider.php b/src/Viserio/Provider/Doctrine/Provider/ConsoleCommandsServiceProvider.php new file mode 100644 index 000000000..0206c1985 --- /dev/null +++ b/src/Viserio/Provider/Doctrine/Provider/ConsoleCommandsServiceProvider.php @@ -0,0 +1,56 @@ + [self::class, 'extendConsole'], + ]; + } + + /** + * Extend viserio console with commands. + * + * @param \Psr\Container\ContainerInterface $container + * @param null|\Viserio\Component\Console\Application $console + * + * @return null|\Viserio\Component\Console\Application + */ + public static function extendConsole(ContainerInterface $container, ?Application $console = null): ?Application + { + if ($console !== null) { + $console->getHelperSet()->set(new ConnectionHelper($container->get(Connection::class)), 'db'); + + $console->addCommands([ + new RunSqlCommand(), + new ImportCommand(), + new ReservedWordsCommand(), + ]); + } + + return $console; + } +} diff --git a/src/Viserio/Provider/Doctrine/Provider/DoctrineDBALServiceProvider.php b/src/Viserio/Provider/Doctrine/Provider/DoctrineDBALServiceProvider.php new file mode 100644 index 000000000..0d04b7d83 --- /dev/null +++ b/src/Viserio/Provider/Doctrine/Provider/DoctrineDBALServiceProvider.php @@ -0,0 +1,93 @@ + [self::class, 'createConnectionManager'], + Connection::class => [self::class, 'createConnection'], + DoctrineConnection::class => function (ContainerInterface $container) { + return $container->get(Connection::class); + }, + 'db' => function (ContainerInterface $container) { + return $container->get(Connection::class); + }, + 'database' => function (ContainerInterface $container) { + return $container->get(Connection::class); + }, + Configuration::class => [self::class, 'createConfiguration'], + EventManager::class => [self::class, 'createEventManager'], + ]; + } + + /** + * {@inheritdoc} + */ + public function getExtensions(): array + { + return []; + } + + /** + * Create a new doctrine configuration. + * + * @return \Doctrine\DBAL\Configuration + */ + public static function createConfiguration(): Configuration + { + return new Configuration(); + } + + /** + * Create a new doctrine event manager. + * + * @return \Doctrine\Common\EventManager + */ + public static function createEventManager(): EventManager + { + return new EventManager(); + } + + /** + * Create a new doctrine connection. + * + * @param \Psr\Container\ContainerInterface $container + * + * @return \Viserio\Provider\Doctrine\ConnectionManager + */ + public static function createConnectionManager(ContainerInterface $container): ConnectionManager + { + $manager = new ConnectionManager($container->get('config')); + + $manager->setDoctrineConfiguration($container->get(Configuration::class)); + $manager->setDoctrineEventManager($container->get(EventManager::class)); + + return $manager; + } + + /** + * Create a new doctrine connection. + * + * @param \Psr\Container\ContainerInterface $container + * + * @return \Doctrine\DBAL\Connection + */ + public static function createConnection(ContainerInterface $container): DoctrineConnection + { + return $container->get(ConnectionManager::class)->getConnection(); + } +} diff --git a/src/Viserio/Provider/Doctrine/Proxy/DBAL.php b/src/Viserio/Provider/Doctrine/Proxy/DBAL.php new file mode 100644 index 000000000..feee98f03 --- /dev/null +++ b/src/Viserio/Provider/Doctrine/Proxy/DBAL.php @@ -0,0 +1,19 @@ + **Note:** If you want to build an application using Narrowspark, visit the main [![Source Code](http://img.shields.io/badge/source-narrowspark/narrowspark-blue.svg?style=flat-square)](https://github.com/narrowspark/narrowspark). + +## Contributing + +Issues for this package shall be posted on [Narrowspark framework issues](http://github.com/narrowspark/framework/issues). +Thank you for considering contributing to the Narrowspark framework! The contribution guide can be found in the [Narrowspark documentation](http://narrowspark.de/docs/contributions). + +## Installation + +Use [Composer](https://getcomposer.org/) to install this package: + +```sh +composer require viserio/bridge/doctrine +``` + +## Official Documentation + +Documentation for the framework can be found on the [Narrowspark website](http://narrowspark.de/docs). + +### License + +The Narrowspark framework is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT). diff --git a/src/Viserio/Provider/Doctrine/Statement.php b/src/Viserio/Provider/Doctrine/Statement.php new file mode 100644 index 000000000..a89a687ec --- /dev/null +++ b/src/Viserio/Provider/Doctrine/Statement.php @@ -0,0 +1,75 @@ +statement = $statement; + } + + /** + * Invoke doctrine statement functions. + * + * @param string $name + * @param array $args + * + * @return mixed + */ + public function __call($name, $args) + { + return \call_user_func_array([$this->statement, $name], $args); + } + + /** + * Returns the next row of a result set. + * + * @param null|int $fetchMode Controls how the next row will be returned to the caller. + * The value must be one of the PDO::FETCH_* constants, + * defaulting to PDO::FETCH_BOTH. + * + * @return null|bool|float|int|object|string The return value of this method on + * success depends on the fetch mode. + * In all cases, FALSE is returned on failure. + */ + public function fetch(int $fetchMode = null) + { + $stmt = $this->statement->fetch($fetchMode); + + if (\is_array($stmt)) { + return new Collection($stmt); + } + + return $stmt; + } + + /** + * Returns an array containing all of the result set rows. + * + * @param null|int $fetchMode Controls how the next row will be returned to the caller. + * The value must be one of the PDO::FETCH_* constants, + * defaulting to PDO::FETCH_BOTH. + * + * @return \Narrowspark\Collection\Collection + */ + public function fetchAll(int $fetchMode = null): Collection + { + return new Collection($this->statement->fetch($fetchMode)); + } +} diff --git a/src/Viserio/Provider/Doctrine/Tests/ConnectionManagerTest.php b/src/Viserio/Provider/Doctrine/Tests/ConnectionManagerTest.php new file mode 100644 index 000000000..5c823707e --- /dev/null +++ b/src/Viserio/Provider/Doctrine/Tests/ConnectionManagerTest.php @@ -0,0 +1,78 @@ +manager = new ConnectionManager([ + 'viserio' => [ + 'doctrine' => [ + 'dbal' => [ + 'default' => 'mysql', + ], + ], + ], + ]); + } + + public function testDefaultConnection(): void + { + $this->assertInstanceOf(Connection::class, $this->manager->getConnection()); + } + + public function testMysqlConnection(): void + { + $this->assertInstanceOf(Connection::class, $this->manager->getConnection('mysql')); + } + + public function testSqliteConnection(): void + { + $this->assertInstanceOf(Connection::class, $this->manager->getConnection('sqlite')); + } + + public function testPgsqlConnection(): void + { + $this->assertInstanceOf(Connection::class, $this->manager->getConnection('pgsql')); + } + + public function testSqlsrvConnection(): void + { + $this->assertInstanceOf(Connection::class, $this->manager->getConnection('sqlsrv')); + } + + public function testSetAndGetDoctrineEventManager(): void + { + $this->assertNull($this->manager->getDoctrineEventManager()); + + $this->manager->setDoctrineEventManager($this->mock(EventManager::class)); + + $this->assertInstanceOf(EventManager::class, $this->manager->getDoctrineEventManager()); + } + + public function testSetAndGetDoctrineConfiguration(): void + { + $this->assertNull($this->manager->getDoctrineConfiguration()); + + $this->manager->setDoctrineConfiguration($this->mock(Configuration::class)); + + $this->assertInstanceOf(Configuration::class, $this->manager->getDoctrineConfiguration()); + } +} diff --git a/src/Viserio/Provider/Doctrine/Tests/Provider/ConsoleCommandsServiceProviderTest.php b/src/Viserio/Provider/Doctrine/Tests/Provider/ConsoleCommandsServiceProviderTest.php new file mode 100644 index 000000000..df8bbb370 --- /dev/null +++ b/src/Viserio/Provider/Doctrine/Tests/Provider/ConsoleCommandsServiceProviderTest.php @@ -0,0 +1,44 @@ +register(new ConsoleServiceProvider()); + $container->register(new DoctrineDBALServiceProvider()); + $container->register(new ConsoleCommandsServiceProvider()); + + $container->instance('config', [ + 'viserio' => [ + 'doctrine' => [ + 'dbal' => [ + 'default' => 'mysql', + ], + ], + ], + ]); + + $console = $container->get(Application::class); + $commands = $console->all(); + + $this->assertInstanceOf(ImportCommand::class, $commands['dbal:import']); + $this->assertInstanceOf(ReservedWordsCommand::class, $commands['dbal:reserved-words']); + $this->assertInstanceOf(RunSqlCommand::class, $commands['dbal:run-sql']); + } +} diff --git a/src/Viserio/Provider/Doctrine/Tests/Provider/DoctrineDBALServiceProviderDatabaseConnectionTest.php b/src/Viserio/Provider/Doctrine/Tests/Provider/DoctrineDBALServiceProviderDatabaseConnectionTest.php new file mode 100644 index 000000000..a4120c468 --- /dev/null +++ b/src/Viserio/Provider/Doctrine/Tests/Provider/DoctrineDBALServiceProviderDatabaseConnectionTest.php @@ -0,0 +1,74 @@ +register(new DoctrineDBALServiceProvider()); + + $container->instance('config', [ + 'viserio' => [ + 'doctrine' => [ + 'dbal' => [ + 'default' => 'sqlite', + 'connections' => [ + 'sqlite' => [ + 'driver' => 'pdo_sqlite', + 'database' => \dirname(__DIR__) . '/Stub/database.sqlite', + 'memory' => true, + ], + ], + ], + ], + ], + ]); + + $conn = $container->get(Connection::class); + $sql = 'SELECT name FROM text WHERE id = 1'; + + $collection = $conn->fetchArray($sql); + + $this->assertInstanceOf(Collection::class, $collection); + $this->assertSame([0 => 'narrowspark'], $collection->all()); + + $collection = $conn->fetchAll($sql); + + $this->assertInstanceOf(Collection::class, $collection); + $this->assertSame(['name' => 'narrowspark'], $collection->all()); + + $collection = $conn->fetchAssoc($sql); + + $this->assertInstanceOf(Collection::class, $collection); + $this->assertSame(['name' => 'narrowspark'], $collection->all()); + + $stmt = $conn->prepare($sql); + $stmt->execute(); + + $collection = $stmt->fetchAll(); + + $this->assertInstanceOf(Collection::class, $collection); + $this->assertSame(['name' => 'narrowspark'], $collection->all()); + + $stmt = $conn->query($sql); + $collection = $stmt->fetch(); + + $this->assertInstanceOf(Collection::class, $collection); + $this->assertSame(['name' => 'narrowspark'], $collection->all()); + + $stmt = $conn->query('SELECT name FROM text WHERE id = 2'); + + $this->assertFalse($stmt->fetch()); + } +} diff --git a/src/Viserio/Provider/Doctrine/Tests/Provider/DoctrineDBALServiceProviderTest.php b/src/Viserio/Provider/Doctrine/Tests/Provider/DoctrineDBALServiceProviderTest.php new file mode 100644 index 000000000..33b8ad743 --- /dev/null +++ b/src/Viserio/Provider/Doctrine/Tests/Provider/DoctrineDBALServiceProviderTest.php @@ -0,0 +1,40 @@ +register(new DoctrineDBALServiceProvider()); + + $container->instance('config', [ + 'viserio' => [ + 'doctrine' => [ + 'dbal' => [ + 'default' => 'mysql', + ], + ], + ], + ]); + + $this->assertInstanceOf(Configuration::class, $container->get(Configuration::class)); + $this->assertInstanceOf(EventManager::class, $container->get(EventManager::class)); + $this->assertInstanceOf(ConnectionManager::class, $container->get(ConnectionManager::class)); + $this->assertInstanceOf(Connection::class, $container->get(Connection::class)); + $this->assertInstanceOf(Connection::class, $container->get('db')); + $this->assertInstanceOf(Connection::class, $container->get('database')); + } +} diff --git a/src/Viserio/Provider/Doctrine/Tests/Stub/database.sqlite b/src/Viserio/Provider/Doctrine/Tests/Stub/database.sqlite new file mode 100644 index 000000000..ccbe3558e Binary files /dev/null and b/src/Viserio/Provider/Doctrine/Tests/Stub/database.sqlite differ diff --git a/src/Viserio/Provider/Doctrine/composer.json b/src/Viserio/Provider/Doctrine/composer.json new file mode 100644 index 000000000..f618be402 --- /dev/null +++ b/src/Viserio/Provider/Doctrine/composer.json @@ -0,0 +1,66 @@ +{ + "name": "viserio/doctrine-provider", + "type": "viserio-bridge", + "description": "The Viserio Doctrine DBAL & ORM provider.", + "keywords": [ + "narrowspark", + "viserio", + "database", + "doctrine", + "dbal", + "orm", + "provider" + ], + "homepage": "http://narrowspark.com", + "license": "MIT", + "authors": [ + { + "name": "Daniel Bannert", + "email": "d.bannert@anolilab.de", + "homepage": "http://www.anolilab.de", + "role": "Developer" + } + ], + "require": { + "php": "^7.1", + "ext-pdo": "*", + "doctrine/dbal": "^2.5.13", + "jdorn/sql-formatter": "^1.2.17", + "narrowspark/collection": "^0.1", + "viserio/support": "self.version" + }, + "require-dev": { + "container-interop/service-provider": "^0.3", + "narrowspark/testing-helper": "^4.0", + "phpunit/phpunit": "^7.2.0", + "viserio/container": "self.version" + }, + "suggest": { + "container-interop/service-provider": "Required to use service-provider (^0.3).", + "viserio/statical-proxy": "Required to use the proxy instances (self.version)." + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Viserio\\Provider\\Doctrine\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "autoload-dev": { + "psr-4": { + "Viserio\\Provider\\Doctrine\\Tests\\": "Tests/" + } + }, + "minimum-stability": "dev", + "prefer-stable": true, + "support": { + "issues": "https://github.com/narrowspark/framework/issues", + "source": "https://github.com/narrowspark/framework" + } +} diff --git a/src/Viserio/Provider/Doctrine/phpunit.xml.dist b/src/Viserio/Provider/Doctrine/phpunit.xml.dist new file mode 100644 index 000000000..d9fb85e2a --- /dev/null +++ b/src/Viserio/Provider/Doctrine/phpunit.xml.dist @@ -0,0 +1,40 @@ + + + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./vendor + ./Tests + + + + + diff --git a/src/Viserio/Provider/DoctrineMigration/.github/PULL_REQUEST_TEMPLATE.md b/src/Viserio/Provider/DoctrineMigration/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..3a1c85818 --- /dev/null +++ b/src/Viserio/Provider/DoctrineMigration/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,5 @@ +This is a READ ONLY repository. + +Please make your pull request to https://github.com/narrowspark/framework + +Thank you for contributing. diff --git a/src/Viserio/Provider/DoctrineMigration/Commands/AbstractCommand.php b/src/Viserio/Provider/DoctrineMigration/Commands/AbstractCommand.php new file mode 100644 index 000000000..3597c929a --- /dev/null +++ b/src/Viserio/Provider/DoctrineMigration/Commands/AbstractCommand.php @@ -0,0 +1,108 @@ +migrationConfiguration) { + if ($this->getHelperSet()->has('migration_configuration')) { + $configHelper = $this->getHelperSet()->get('migration_configuration'); + + if (! $configHelper instanceof ConfigurationHelper) { + throw new RuntimeException('.'); + } + } else { + $configHelper = new ConfigurationHelper($this->configuration, $this->getConnection($input)); + } + + $configHelper->setContainer($this->container); + + $this->migrationConfiguration = $configHelper->getMigrationConfig($input, $this->getOutputWriter($output)); + } + + return $this->migrationConfiguration; + } + + /** + * @param \Symfony\Component\Console\Input\InputInterface $input + * + * @throws \Doctrine\DBAL\DBALException + * + * @return \Doctrine\DBAL\Connection + */ + private function getConnection(InputInterface $input): Connection + { + if ($this->connection) { + return $this->connection; + } + + $chainLoader = new ConnectionConfigurationChainLoader( + [ + new ArrayConnectionConfigurationLoader($input->getOption('db-configuration')), + new ArrayConnectionConfigurationLoader('migrations-db.php'), + new ConnectionHelperLoader($this->getHelperSet(), 'connection'), + new ConnectionConfigurationLoader($this->configuration), + ] + ); + + if ($connection = $chainLoader->chosen()) { + return $this->connection = $connection; + } + + throw new \InvalidArgumentException('You have to specify a --db-configuration file or pass a Database Connection as a dependency to the Migrations.'); + } + + /** + * @param \Symfony\Component\Console\Output\OutputInterface $output + * + * @return \Doctrine\DBAL\Migrations\OutputWriter + */ + private function getOutputWriter(OutputInterface $output): \Doctrine\DBAL\Migrations\OutputWriter + { + if (! $this->outputWriter) { + $this->outputWriter = new OutputWriter(function ($message) use ($output) { + return $output->writeln($message); + }); + } + + return $this->outputWriter; + } +} diff --git a/src/Viserio/Provider/DoctrineMigration/Commands/DiffCommand.php b/src/Viserio/Provider/DoctrineMigration/Commands/DiffCommand.php new file mode 100644 index 000000000..310f046cd --- /dev/null +++ b/src/Viserio/Provider/DoctrineMigration/Commands/DiffCommand.php @@ -0,0 +1,3 @@ +getMigrationsDirectory(); + $dir = $dir ?? \getcwd(); + $dir = \rtrim($dir, '/'); + + if (! \file_exists($dir)) { + throw new InvalidArgumentException(\sprintf('Migrations directory [%s] does not exist.', $dir)); + } + + if ($configuration->areMigrationsOrganizedByYear()) { + $dir .= \DIRECTORY_SEPARATOR . \date('Y'); + } + + if ($configuration->areMigrationsOrganizedByYearAndMonth()) { + $dir .= \DIRECTORY_SEPARATOR . \date('m'); + } + + if (! \file_exists($dir)) { + \mkdir($dir, 0755, true); + } + + return $dir; + } +} diff --git a/src/Viserio/Provider/DoctrineMigration/Commands/Helper/ConfigurationHelper.php b/src/Viserio/Provider/DoctrineMigration/Commands/Helper/ConfigurationHelper.php new file mode 100644 index 000000000..43bdb1066 --- /dev/null +++ b/src/Viserio/Provider/DoctrineMigration/Commands/Helper/ConfigurationHelper.php @@ -0,0 +1,186 @@ +options = $options; + $this->connection = $connection; + } + + /** + * {@inheritdoc} + */ + public static function getDimensions(): array + { + return ['viserio', 'doctrine', 'migration']; + } + + /** + * {@inheritdoc} + */ + public static function getDefaultOptions(): array + { + return [ + 'default' => 'mysql', + 'connections' => [ + 'mysql' => [ + 'name' => 'Doctrine Migrations', + 'namespace' => 'Database\\Migration', + 'table' => 'migrations', + 'schema_filter' => '/^(?).*$/', + 'naming_strategy' => null, + 'custom_template' => null, + 'organize_migrations' => null, + ], + ], + ]; + } + + /** + * {@inheritdoc} + */ + public static function getMandatoryOptions(): array + { + return ['directory']; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'migration_configuration'; + } + + /** + * Get a viserio migration configuration object. + * + * @param \Symfony\Component\Console\Input\InputInterface $input + * @param \Doctrine\DBAL\Migrations\OutputWriter $outputWriter + * + * @return \Viserio\Provider\Doctrine\Migration\Configuration\Configuration + */ + public function getMigrationConfig(InputInterface $input, OutputWriter $outputWriter): Configuration + { + $options = self::resolveOptions($this->options); + + try { + $name = $input->getOption('connection'); + } catch (SymfonyInvalidArgumentException $exception) { + $name = $options['default']; + } + + $configuration = new Configuration($this->connection, $outputWriter); + $config = $options['connections'][$name]; + + $configuration->setName($config['name']); + $configuration->setMigrationsNamespace($config['namespace']); + $configuration->setMigrationsTableName($config['table']); + $configuration->getConnection()->getConfiguration()->setFilterSchemaAssetsExpression($config['schema_filter']); + + $strategy = $this->getStrategy($config); + + $configuration->setNamingStrategy($strategy); + $configuration->setMigrationsFinder($strategy->getFinder()); + + $configuration->setMigrationsDirectory($config['directory']); + $configuration->registerMigrationsFromDirectory($config['directory']); + $configuration->setCustomTemplate($config['custom_template']); + + return $this->configureOrganizeMigrations($config, $configuration); + } + + /** + * Get a naming strategy. + * + * @param array $config + * + * @return \Viserio\Provider\Doctrine\Contract\Migration\NamingStrategy + */ + private function getStrategy(array $config): NamingStrategyContract + { + if (\is_object($config['naming_strategy'])) { + return $config['naming_strategy']; + } + + if ($this->container !== null && $this->container->has($config['naming_strategy'])) { + return $this->container->get($config['naming_strategy']); + } + + return new DefaultNamingStrategy(); + } + + /** + * Configures the migration organize. + * + * @param iterable $config + * @param \Viserio\Provider\Doctrine\Migration\Configuration\Configuration $configuration + * + * @throws \Viserio\Provider\Doctrine\Contract\Migration\Exception\InvalidArgumentException + * + * @return \Viserio\Provider\Doctrine\Migration\Configuration\Configuration + */ + private function configureOrganizeMigrations(iterable $config, Configuration $configuration): Configuration + { + switch ($config['organize_migrations']) { + case Configuration::VERSIONS_ORGANIZATION_BY_YEAR: + $configuration->setMigrationsAreOrganizedByYear(true); + + break; + case Configuration::VERSIONS_ORGANIZATION_BY_YEAR_AND_MONTH: + $configuration->setMigrationsAreOrganizedByYearAndMonth(true); + + break; + case null: + break; + default: + throw new InvalidArgumentException('Invalid value for [organize_migrations].'); + } + + return $configuration; + } +} diff --git a/src/Viserio/Provider/DoctrineMigration/Commands/LatestCommand.php b/src/Viserio/Provider/DoctrineMigration/Commands/LatestCommand.php new file mode 100644 index 000000000..310f046cd --- /dev/null +++ b/src/Viserio/Provider/DoctrineMigration/Commands/LatestCommand.php @@ -0,0 +1,3 @@ +getName(); + $name = $name ? $name : 'Doctrine Database Migrations'; + $name = \str_repeat(' ', 20) . $name . \str_repeat(' ', 20); + + $this->line(\str_repeat(' ', \mb_strlen($name)), 'question'); + $this->line($name, 'question'); + $this->line(\str_repeat(' ', \mb_strlen($name)), 'question'); + $this->line(''); + } +} diff --git a/src/Viserio/Provider/DoctrineMigration/Commands/RefreshCommand.php b/src/Viserio/Provider/DoctrineMigration/Commands/RefreshCommand.php new file mode 100644 index 000000000..310f046cd --- /dev/null +++ b/src/Viserio/Provider/DoctrineMigration/Commands/RefreshCommand.php @@ -0,0 +1,3 @@ +namingStrategy; + } + + /** + * Set a naming strategy object. + * + * @param \Viserio\Provider\Doctrine\Contract\Migration\NamingStrategy $namingStrategy + * + * @return void + */ + public function setNamingStrategy(NamingStrategyContract $namingStrategy): void + { + $this->namingStrategy = $namingStrategy; + } +} diff --git a/src/Viserio/Provider/DoctrineMigration/LICENSE b/src/Viserio/Provider/DoctrineMigration/LICENSE new file mode 100644 index 000000000..9941d8719 --- /dev/null +++ b/src/Viserio/Provider/DoctrineMigration/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 - 2017 Narrowspark + +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. diff --git a/src/Viserio/Provider/DoctrineMigration/Naming/DefaultNamingStrategy.php b/src/Viserio/Provider/DoctrineMigration/Naming/DefaultNamingStrategy.php new file mode 100644 index 000000000..8b70e2c22 --- /dev/null +++ b/src/Viserio/Provider/DoctrineMigration/Naming/DefaultNamingStrategy.php @@ -0,0 +1,38 @@ + [self::class, 'extendConsole'], + ]; + } + + /** + * Extend viserio console with commands. + * + * @param \Psr\Container\ContainerInterface $container + * @param null|\Viserio\Component\Console\Application $console + * + * @return null|\Viserio\Component\Console\Application + */ + public static function extendConsole(ContainerInterface $container, ?Application $console = null): ?Application + { + if ($console !== null) { + $console->getHelperSet() + ->set(new ConfigurationHelper($container), 'connection'); + + $console->addCommands([ + new DiffCommand(), + new ExecuteCommand(), + new GenerateCommand(), + new MigrateCommand(), + new StatusCommand(), + new VersionCommand(), + ]); + } + + return $console; + } +} diff --git a/src/Viserio/Provider/DoctrineMigration/README.md b/src/Viserio/Provider/DoctrineMigration/README.md new file mode 100644 index 000000000..502a77dfb --- /dev/null +++ b/src/Viserio/Provider/DoctrineMigration/README.md @@ -0,0 +1,33 @@ +# Viserio Doctrine Migration package + +This package is part of the [Narrowspark framework](http://github.com/narrowspark/framework). + +[![Author](http://img.shields.io/badge/author-@anolilab-blue.svg?style=flat-square)](https://twitter.com/anolilab) +[![Quality Score](https://img.shields.io/scrutinizer/g/narrowspark/framework.svg?style=flat-square)](https://scrutinizer-ci.com/g/narrowspark/framework/code-structure/master) +[![Build Status](https://api.travis-ci.org/narrowspark/framework.svg?branch=master&style=flat-square)](https://travis-ci.org/narrowspark/framework) +[![Latest Version](https://img.shields.io/packagist/v/narrowspark/framework.svg?style=flat-square)](https://github.com/narrowspark/framework/releases) +[![Minimum PHP Version](https://img.shields.io/badge/php-%5E7.1.0-8892BF.svg?style=flat-square)](https://php.net/) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +> **Note:** If you want to build an application using Narrowspark, visit the main [![Source Code](http://img.shields.io/badge/source-narrowspark/narrowspark-blue.svg?style=flat-square)](https://github.com/narrowspark/narrowspark). + +## Contributing + +Issues for this package shall be posted on [Narrowspark framework issues](http://github.com/narrowspark/framework/issues). +Thank you for considering contributing to the Narrowspark framework! The contribution guide can be found in the [Narrowspark documentation](http://narrowspark.de/docs/contributions). + +## Installation + +Use [Composer](https://getcomposer.org/) to install this package: + +```sh +composer require viserio/bridge/doctrine +``` + +## Official Documentation + +Documentation for the framework can be found on the [Narrowspark website](http://narrowspark.de/docs). + +### License + +The Narrowspark framework is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT). diff --git a/src/Viserio/Provider/DoctrineMigration/Tests/Provider/ConsoleCommandsServiceProviderTest.php b/src/Viserio/Provider/DoctrineMigration/Tests/Provider/ConsoleCommandsServiceProviderTest.php new file mode 100644 index 000000000..028a9fd11 --- /dev/null +++ b/src/Viserio/Provider/DoctrineMigration/Tests/Provider/ConsoleCommandsServiceProviderTest.php @@ -0,0 +1,71 @@ +register(new ConsoleServiceProvider()); + $container->register(new DoctrineDBALServiceProvider()); + $container->register(new ConsoleCommandsServiceProvider()); + + $container->instance('config', [ + 'viserio' => [ + 'console' => [ + 'version' => '1', + ], + 'doctrine' => [ + 'dbal' => [ + 'default' => 'mysql', + 'connections' => [ + 'mysql' => [ + 'driver' => 'pdo_mysql', + 'host' => 'DB_HOST', + 'port' => 'DB_PORT', + 'database' => 'DB_DATABASE_NAME', + 'username' => 'DB_DATABASE_USER', + 'password' => 'DB_DATABASE_PASSWORD', + 'charset' => 'DB_CHARSET', 'UTF8', + 'driverOptions' => [1002 => 'SET NAMES utf8'], + ], + ], + ], + 'migrations' => [ + 'path' => __DIR__ . '/../Stub/', + 'namespace' => 'Database\\Migrations', + 'name' => 'migration', + 'table_name' => 'migration', + ], + ], + ], + ]); + + $console = $container->get(Application::class); + $commands = $console->all(); + + $this->assertInstanceOf(DiffCommand::class, $commands['migrations:diff']); + $this->assertInstanceOf(ExecuteCommand::class, $commands['migrations:execute']); + $this->assertInstanceOf(GenerateCommand::class, $commands['migrations:generate']); + $this->assertInstanceOf(MigrateCommand::class, $commands['migrations:migrate']); + $this->assertInstanceOf(StatusCommand::class, $commands['migrations:status']); + $this->assertInstanceOf(VersionCommand::class, $commands['migrations:version']); + } +} diff --git a/src/Viserio/Provider/DoctrineMigration/Tests/Stub/database.sqlite b/src/Viserio/Provider/DoctrineMigration/Tests/Stub/database.sqlite new file mode 100644 index 000000000..ccbe3558e Binary files /dev/null and b/src/Viserio/Provider/DoctrineMigration/Tests/Stub/database.sqlite differ diff --git a/src/Viserio/Provider/DoctrineMigration/composer.json b/src/Viserio/Provider/DoctrineMigration/composer.json new file mode 100644 index 000000000..201706622 --- /dev/null +++ b/src/Viserio/Provider/DoctrineMigration/composer.json @@ -0,0 +1,61 @@ +{ + "name": "viserio/doctrine-migration-provider", + "type": "viserio-bridge", + "description": "The Viserio Doctrine provider.", + "keywords": [ + "narrowspark", + "viserio", + "database", + "doctrine", + "migrations", + "provider" + ], + "homepage": "http://narrowspark.com", + "license": "MIT", + "authors": [ + { + "name": "Daniel Bannert", + "email": "d.bannert@anolilab.de", + "homepage": "http://www.anolilab.de", + "role": "Developer" + } + ], + "require": { + "php": "^7.2", + "doctrine/migrations": "^2.0@beta", + "viserio/console": "self.version", + "viserio/options-resolver": "self.version" + }, + "require-dev": { + "container-interop/service-provider": "^0.4", + "phpunit/phpunit": "^7.2.0", + "viserio/container": "self.version" + }, + "suggest": { + "container-interop/service-provider": "Required to use service-provider (^0.4)." + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Viserio\\Provider\\Doctrine\\Migration\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "autoload-dev": { + "psr-4": { + "Viserio\\Provider\\Doctrine\\Migration\\Tests\\": "Tests/" + } + }, + "minimum-stability": "dev", + "prefer-stable": true, + "support": { + "issues": "https://github.com/narrowspark/framework/issues", + "source": "https://github.com/narrowspark/framework" + } +} diff --git a/src/Viserio/Provider/DoctrineMigration/phpunit.xml.dist b/src/Viserio/Provider/DoctrineMigration/phpunit.xml.dist new file mode 100644 index 000000000..a817fae30 --- /dev/null +++ b/src/Viserio/Provider/DoctrineMigration/phpunit.xml.dist @@ -0,0 +1,40 @@ + + + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./vendor + ./Tests + + + + + diff --git a/src/Viserio/Provider/Twig/Command/LintCommand.php b/src/Viserio/Provider/Twig/Command/LintCommand.php index 5fab717e7..9c32c142e 100644 --- a/src/Viserio/Provider/Twig/Command/LintCommand.php +++ b/src/Viserio/Provider/Twig/Command/LintCommand.php @@ -4,11 +4,11 @@ use Symfony\Component\Finder\Finder; use Twig\Environment; -use Viserio\Bridge\Twig\Command\LintCommand as BaseLintCommand; use Viserio\Component\Contract\OptionsResolver\ProvidesDefaultOptions as ProvidesDefaultOptionsContract; use Viserio\Component\Contract\OptionsResolver\RequiresComponentConfig as RequiresComponentConfigContract; use Viserio\Component\Contract\View\Finder as FinderContract; use Viserio\Component\OptionsResolver\Traits\OptionsResolverTrait; +use Viserio\Provider\Twig\Command\LintCommand as BaseLintCommand; class LintCommand extends BaseLintCommand implements RequiresComponentConfigContract, ProvidesDefaultOptionsContract { diff --git a/src/Viserio/Provider/Twig/Provider/ConsoleCommandsServiceProvider.php b/src/Viserio/Provider/Twig/Provider/ConsoleCommandsServiceProvider.php index 7818f18ef..fb9733e7e 100644 --- a/src/Viserio/Provider/Twig/Provider/ConsoleCommandsServiceProvider.php +++ b/src/Viserio/Provider/Twig/Provider/ConsoleCommandsServiceProvider.php @@ -5,12 +5,12 @@ use Interop\Container\ServiceProviderInterface; use Psr\Container\ContainerInterface; use Twig\Environment; -use Viserio\Bridge\Twig\Command\DebugCommand; use Viserio\Component\Console\Application; use Viserio\Component\Contract\OptionsResolver\ProvidesDefaultOptions as ProvidesDefaultOptionsContract; use Viserio\Component\Contract\OptionsResolver\RequiresComponentConfig as RequiresComponentConfigContract; use Viserio\Component\Contract\View\Finder as FinderContract; use Viserio\Provider\Twig\Command\CleanCommand; +use Viserio\Provider\Twig\Command\DebugCommand; use Viserio\Provider\Twig\Command\LintCommand; class ConsoleCommandsServiceProvider implements diff --git a/src/Viserio/Provider/Twig/Tests/Engine/TwigEngineTest.php b/src/Viserio/Provider/Twig/Tests/Engine/TwigEngineTest.php index 8beb61342..fe1377d73 100644 --- a/src/Viserio/Provider/Twig/Tests/Engine/TwigEngineTest.php +++ b/src/Viserio/Provider/Twig/Tests/Engine/TwigEngineTest.php @@ -7,11 +7,11 @@ use Symfony\Component\Filesystem\Filesystem; use Twig\Environment; use Twig\Loader\FilesystemLoader; -use Viserio\Bridge\Twig\Extension\ConfigExtension; -use Viserio\Bridge\Twig\Extension\StrExtension; use Viserio\Component\Contract\Config\Repository as RepositoryContract; use Viserio\Component\Contract\View\Exception\RuntimeException; use Viserio\Provider\Twig\Engine\TwigEngine; +use Viserio\Provider\Twig\Extension\ConfigExtension; +use Viserio\Provider\Twig\Extension\StrExtension; /** * @internal