diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index 859797c..dd8a110 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -4,8 +4,7 @@ ->in(__DIR__ . '/src') ->in(__DIR__ . '/tests') ->append([__DIR__ . '/castor.php']) - ->exclude(['cache', 'Bundle/Resources/config']) - ->notName('FooTransformerStaticCallable.php') + ->exclude(['cache', 'Resources/stubs']) ; return (new PhpCsFixer\Config()) diff --git a/README.md b/README.md index e2cc133..04e34b9 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,140 @@ Install using Composer: ```bash composer require hecht-a/graphql-orm ``` + +## Configuration +Configure the GraphQL endpoint and entity mapping. + +Example : +```yaml +graphql_orm: + endpoint: 'http://localhost:4000' + max_depth: 3 + + mapping: + entity: + dir: '%kernel.project_dir%/src/GraphQL/Entity' + namespace: App\GraphQL\Entity + repository: + dir: '%kernel.project_dir%/src/GraphQL/Repository' + namespace: App\GraphQL\Repository +``` +Options: + +- `endpoint` : GraphQL API endpoint. +- `max_depth` : Maximum nested relation loading depth. +- `mapping.entity` : Entity generation directory and namespace. +- `mapping.repository` : Repository generation directory and namespace. + +--- + +## Entity Generator +GraphqlOrm provides a console wizard to generate entities and repositories. + +Create a new entity : + +```bash +php bin/console graphqlorm:make:entity Task +```` + +The command generates : +- Entity class +- Repository class +- Identifier property +- Scalar properties +- Relations + +The generator asks interactively : +- GraphQL root name +- Identifier field +- Scalar properties +- Relations + +Example wizard : +``` +Add a property ? yes + +Property name: +id + +Field name in GraphQL schema : +id + +Identifier ? yes + +Type: +int + +Nullable ? no + +Add a property ? yes + +Property name: +title + +Field name in GraphQL schema : +title + +Type: +string + +Nullable ? no + +Add a property ? yes + +Property name: +user + +Field name in GraphQL schema : +user + +Type: +relation +``` + +Relation wizard : +``` +Target entity : +> Us +User + +Relation type : +object +``` +Existing entities are automatically suggested using autocomplete. + +Generated example : +```php +#[GraphqlEntity('tasks', TaskRepository::class)] +class Task +{ + #[GraphqlField(mappedFrom: 'id', identifier: true)] + private int $id; + + #[GraphqlField(mappedFrom: 'title')] + private string $title; + + #[GraphqlField(mappedFrom: 'user')] + private ?User $user = null; +} +``` +Repository : +```php +/** + * @extends GraphqlEntityRepository + * @method Task[] findAll() + * @method Task[] findBy(array $criteria) + */ +class TaskRepository extends GraphqlEntityRepository +{ + public function __construct(GraphqlManager $graphQLManager) + { + parent::__construct($graphQLManager, Task::class); + } +} +``` +--- + ## Quick Start ### Define an Entity ```php diff --git a/composer.json b/composer.json index 5b2b9cb..e217122 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,10 @@ "symfony/stopwatch": "^7.4", "symfony/http-kernel": "^7.4", "symfony/config": "^7.4", - "symfony/http-client": "^7.4" + "symfony/http-client": "^7.4", + "symfony/console": "^7.4", + "symfony/filesystem": "^7.4", + "symfony/finder": "^7.4" }, "require-dev": { "symfony/phpunit-bridge": "^8.0", diff --git a/composer.lock b/composer.lock index ee40568..07e3c8a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c532655814056dc4fa87baa3b2e667ad", + "content-hash": "14f594861189ded6c79ed0c92cd665ff", "packages": [ { "name": "psr/container", @@ -238,6 +238,104 @@ ], "time": "2026-01-13T11:36:38+00:00" }, + { + "name": "symfony/console", + "version": "v7.4.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/41e38717ac1dd7a46b6bda7d6a82af2d98a78894", + "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2|^8.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.4.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-13T11:36:38+00:00" + }, { "name": "symfony/dependency-injection", "version": "v7.4.5", @@ -702,6 +800,74 @@ ], "time": "2025-11-27T13:27:24+00:00" }, + { + "name": "symfony/finder", + "version": "v7.4.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", + "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v7.4.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-26T15:07:59+00:00" + }, { "name": "symfony/http-client", "version": "v7.4.5", @@ -1165,6 +1331,173 @@ ], "time": "2024-09-09T11:45:10+00:00" }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-27T09:58:17+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, { "name": "symfony/polyfill-mbstring", "version": "v1.33.0", @@ -1563,6 +1896,97 @@ ], "time": "2025-08-04T07:05:15+00:00" }, + { + "name": "symfony/string", + "version": "v7.4.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/1c4b10461bf2ec27537b5f36105337262f5f5d6f", + "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.33", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.1|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v7.4.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-12T10:54:30+00:00" + }, { "name": "symfony/var-dumper", "version": "v7.4.4", diff --git a/phpstan.neon b/phpstan.neon index 4ed6365..384d715 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2,5 +2,7 @@ parameters: level: max paths: - src/ + excludePaths: + - src/Resources/stubs/* tmpDir: cache \ No newline at end of file diff --git a/src/Codegen/PropertyDefinition.php b/src/Codegen/PropertyDefinition.php new file mode 100644 index 0000000..4d4e6c8 --- /dev/null +++ b/src/Codegen/PropertyDefinition.php @@ -0,0 +1,20 @@ + $vars + */ + public function render(string $stubFile, array $vars): string + { + $path = rtrim($this->stubsDir, '/') . '/' . $stubFile; + + if (!is_file($path)) { + throw new FileNotFoundException(\sprintf('Stub not found: %s', $path)); + } + + $content = file_get_contents($path); + if ($content === false) { + throw new \RuntimeException(\sprintf('Unable to read stub: %s', $path)); + } + + $replacements = []; + foreach ($vars as $key => $value) { + $replacements['{{ ' . $key . ' }}'] = $value; + } + + return strtr($content, $replacements); + } +} diff --git a/src/Command/MakeGraphqlEntityCommand.php b/src/Command/MakeGraphqlEntityCommand.php new file mode 100644 index 0000000..5708525 --- /dev/null +++ b/src/Command/MakeGraphqlEntityCommand.php @@ -0,0 +1,359 @@ +normalizeClassName($name); + + $properties = $this->askProperties($io); + $propertiesCode = $this->renderProperties($properties); + $relationImports = $this->collectRelationImports($properties); + + $root = \is_string($root) && $root !== '' ? $root : $this->defaultRoot($className); + + $entityFqcn = $this->entityNamespace . '\\' . $className; + $entityPath = rtrim($this->entityDir, '/') . '/' . $className . '.php'; + + $repoClassName = $className . 'Repository'; + $repoFqcn = $this->repositoryNamespace . '\\' . $repoClassName; + $repoPath = rtrim($this->repositoryDir, '/') . '/' . $repoClassName . '.php'; + + $entityCode = $this->stubRenderer->render('entity.stub.php', [ + 'namespace' => $this->entityNamespace, + 'class_name' => $className, + 'root' => $root, + 'repo_namespace' => $this->repositoryNamespace, + 'repo_short' => $repoClassName, + 'properties' => $propertiesCode, + 'relation_imports' => $relationImports, + ]); + + $repoCode = $this->stubRenderer->render('repository.stub.php', [ + 'namespace' => $this->repositoryNamespace, + 'repo_class_name' => $repoClassName, + 'entity_namespace' => $this->entityNamespace, + 'entity_short' => $className, + ]); + + if (!$force) { + if ($this->fs->exists($entityPath)) { + $io->error(\sprintf('The file already exists: %s (use --force to overwrite)', $entityPath)); + + return Command::FAILURE; + } + if ($this->fs->exists($repoPath)) { + $io->error(\sprintf('The file already exists: %s (use --force to overwrite)', $repoPath)); + + return Command::FAILURE; + } + } + + $this->fs->mkdir([$this->entityDir, $this->repositoryDir]); + + $this->fs->dumpFile($entityPath, $entityCode); + $this->fs->dumpFile($repoPath, $repoCode); + + $io->success([ + \sprintf('Entity : %s', $entityFqcn), + \sprintf('Repository : %s', $repoFqcn), + \sprintf('GraphQL root : %s', $root), + ]); + + return Command::SUCCESS; + } + + /** + * @return PropertyDefinition[] + */ + private function askProperties(SymfonyStyle $io): array + { + $identifierAlreadyExists = false; + + $properties = []; + + while (true) { + if (!$io->confirm('Add a property ?', empty($properties))) { + break; + } + + /** @var ?string $name */ + $name = $io->ask('Property name'); + + if (!$name) { + $io->warning('Invalid property name'); + continue; + } + + /** @var ?string $graphqlField */ + $graphqlField = $io->ask('Field name in GraphQL schema'); + + if (!$graphqlField) { + $io->warning('Invalid property GraphQL field'); + continue; + } + + $identifier = false; + if (!$identifierAlreadyExists) { + $identifier = $io->confirm('Identifier ?', false); + } + + /** @var string $type */ + $type = $io->choice( + 'Type', + [ + 'string', + 'int', + 'float', + 'bool', + 'relation', + ], + 'string' + ); + + if ($type === 'relation') { + $relation = $this->askRelation( + $io, + $name, + $graphqlField + ); + + if ($relation !== null) { + $properties[] = $relation; + } + + continue; + } + + $nullable = $io->confirm('Nullable ?', false); + + $properties[] = new PropertyDefinition( + lcfirst($name), + $type, + $graphqlField, + $nullable, + $identifier + ); + + if ($identifier) { + $identifierAlreadyExists = true; + } + } + + return $properties; + } + + private function askRelation(SymfonyStyle $io, string $propertyName, string $graphqlField): ?PropertyDefinition + { + $entities = $this->findExistingEntities(); + + if ($entities !== []) { + /* @var ?string $target */ + $target = $io->choice('Target entity', $entities); + } else { + /* @var ?string $target */ + $target = $io->ask('Target entity'); + } + + if (!\is_string($target) || !$target) { + $io->warning('Invalid target entity'); + + return null; + } + + $type = $io->choice( + 'Relation type', + [ + 'object', + 'collection', + ], + 'object' + ); + + $collection = $type === 'collection'; + + return new PropertyDefinition( + name: lcfirst($propertyName), + phpType: $collection ? 'array' : $target, + mappedFrom: $graphqlField, + nullable: !$collection, + identifier: false, + relation: true, + collection: $collection, + targetEntity: $target, + ); + } + + /** + * @param PropertyDefinition[] $properties + */ + private function renderProperties(array $properties): string + { + if ($properties === []) { + return ''; + } + + $out = []; + + foreach ($properties as $property) { + $attribute = $property->identifier + ? "#[GraphqlField(mappedFrom: '{$property->mappedFrom}', identifier: true)]" + : "#[GraphqlField(mappedFrom: '{$property->mappedFrom}')]"; + + if ($property->relation) { + if ($property->collection) { + $out[] = <<targetEntity}[] */ + {$attribute} + private array \${$property->name} = []; + +PHP; + + continue; + } + + $out[] = <<targetEntity} \${$property->name} = null; + +PHP; + + continue; + } + + $type = $property->nullable + ? '?' . $property->phpType + : $property->phpType; + + $default = $property->nullable + ? ' = null' + : ''; + + $out[] = <<name}{$default}; + +PHP; + } + + return implode("\n", $out); + } + + /** + * @param PropertyDefinition[] $properties + */ + private function collectRelationImports(array $properties): string + { + $imports = []; + + foreach ($properties as $property) { + if (!$property->relation) { + continue; + } + + $imports[] = 'use ' . $this->entityNamespace . '\\' . $property->targetEntity . ';'; + } + + return implode("\n", array_unique($imports)); + } + + /** + * @return string[] + */ + private function findExistingEntities(): array + { + if (!is_dir($this->entityDir)) { + return []; + } + + $finder = new Finder(); + $finder + ->files() + ->in($this->entityDir) + ->name('*.php'); + + $entities = []; + + foreach ($finder as $file) { + $entities[] = pathinfo($file->getFilename(), PATHINFO_FILENAME); + } + + sort($entities); + + return $entities; + } + + private function normalizeClassName(string $name): string + { + $name = trim($name); + $name = preg_replace('/[^a-zA-Z0-9_\\\\]/', '', $name) ?: ''; + $name = str_replace('\\', '', $name); + + if ($name === '') { + return 'Entity'; + } + + $name = preg_replace('/_+/', '_', $name) ?: $name; + $parts = array_filter(explode('_', $name), fn ($p) => $p !== ''); + + $out = ''; + foreach ($parts as $p) { + $out .= ucfirst(strtolower($p)); + } + + return $out === '' ? 'Entity' : $out; + } + + private function defaultRoot(string $className): string + { + $camel = lcfirst($className); + + if (str_ends_with($camel, 's')) { + return $camel; + } + + return $camel . 's'; + } +} diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index e188e95..a3e2d05 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -15,7 +15,6 @@ public function getConfigTreeBuilder(): TreeBuilder $treeBuilder->getRootNode() ->children() - ->scalarNode('endpoint') ->isRequired() ->cannotBeEmpty() @@ -31,7 +30,31 @@ public function getConfigTreeBuilder(): TreeBuilder ->min(1) ->end() - ->end(); + ->arrayNode('mapping') + ->addDefaultsIfNotSet() + ->children() + + ->arrayNode('entity') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('dir')->defaultValue('%kernel.project_dir%/src/GraphQL/Entity')->end() + ->scalarNode('namespace')->defaultValue('App\\GraphQL\\Entity')->end() + ->end() + ->end() + + ->arrayNode('repository') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('dir')->defaultValue('%kernel.project_dir%/src/GraphQL/Repository')->end() + ->scalarNode('namespace')->defaultValue('App\\GraphQL\\Repository')->end() + ->end() + ->end() + + ->end() + ->end() + + ->end() + ; return $treeBuilder; } diff --git a/src/DependencyInjection/GraphqlOrmExtension.php b/src/DependencyInjection/GraphqlOrmExtension.php index c881cb9..8a29b3a 100644 --- a/src/DependencyInjection/GraphqlOrmExtension.php +++ b/src/DependencyInjection/GraphqlOrmExtension.php @@ -41,5 +41,10 @@ public function load(array $configs, ContainerBuilder $container): void 'graphql_orm.max_depth', $config['max_depth'] ); + + $container->setParameter('graphql_orm.mapping.entity.dir', $config['mapping']['entity']['dir']); + $container->setParameter('graphql_orm.mapping.entity.namespace', $config['mapping']['entity']['namespace']); + $container->setParameter('graphql_orm.mapping.repository.dir', $config['mapping']['repository']['dir']); + $container->setParameter('graphql_orm.mapping.repository.namespace', $config['mapping']['repository']['namespace']); } } diff --git a/src/Resources/config/services.php b/src/Resources/config/services.php index a2023a2..cfdf36b 100644 --- a/src/Resources/config/services.php +++ b/src/Resources/config/services.php @@ -4,6 +4,8 @@ use GraphqlOrm\Client\GraphqlClient; use GraphqlOrm\Client\GraphqlClientInterface; +use GraphqlOrm\Codegen\StubRenderer; +use GraphqlOrm\Command\MakeGraphqlEntityCommand; use GraphqlOrm\DataCollector\GraphqlOrmDataCollector; use GraphqlOrm\GraphqlManager; use GraphqlOrm\Hydrator\EntityHydrator; @@ -34,4 +36,10 @@ 'template' => '@GraphqlOrm/collector/graphql_orm.html.twig', ]) ->public(); + + $services->set(StubRenderer::class) + ->arg('$stubsDir', __DIR__ . '/../stubs'); + + $services->set(MakeGraphqlEntityCommand::class) + ->tag('console.command'); }; diff --git a/src/Resources/stubs/entity.stub.php b/src/Resources/stubs/entity.stub.php new file mode 100644 index 0000000..6043c69 --- /dev/null +++ b/src/Resources/stubs/entity.stub.php @@ -0,0 +1,14 @@ + + * @method {{ entity_short }}[] findAll() + * @method {{ entity_short }}[] findBy(array $criteria) + */ +class {{ repo_class_name }} extends GraphqlEntityRepository +{ + public function __construct(GraphqlManager $graphQLManager) +{ + parent::__construct($graphQLManager, {{ entity_short }}::class); + } +} \ No newline at end of file