├── .gitignore
├── img
└── maker_bundle.png
├── phpstan.neon
├── src
├── Utils
│ ├── NameGenerators
│ │ ├── UniqueNameGenerator.php
│ │ └── ResourceKeyExtractor.php
│ └── ConsoleHelperTrait.php
├── Maker
│ ├── TashHandlerMaker
│ │ ├── TashHandlerGeneratorSettings.php
│ │ ├── trash_handler_template.tpl.php
│ │ └── MakeTrashHandlerCommand.php
│ ├── PreviewMaker
│ │ ├── preview_template.tpl.php
│ │ ├── preview_provider_template.tpl.php
│ │ └── MakePreviewCommand.php
│ ├── SuluPageMaker
│ │ ├── page_template.tpl.php
│ │ ├── page_config.tpl.php
│ │ └── MakePageTypeCommand.php
│ ├── ListConfigurationMaker
│ │ ├── ListJoinInfo.php
│ │ ├── ListPropertyInfo.php
│ │ ├── ConditionType.php
│ │ ├── JoinType.php
│ │ ├── list_template.tpl.php
│ │ ├── MakeListConfigurationCommand.php
│ │ └── ListPropertyInfoProvider.php
│ ├── DocumentFixtureMaker
│ │ ├── fixture.tpl.php
│ │ └── MakeDocumentFixtureCommand.php
│ ├── ControllerMaker
│ │ ├── ControllerGeneratorSettings.php
│ │ ├── controllerTemplate.tpl.php
│ │ └── MakeControllerCommand.php
│ ├── AdminConfigurationMaker
│ │ ├── AdminGeneratorSettings.php
│ │ ├── configurationTemplate.tpl.php
│ │ └── MakeAdminConfigurationCommand.php
│ ├── MigrationMaker
│ │ ├── MigrationFilters.php
│ │ ├── migration_template.tpl.php
│ │ └── MakeMigrationCommand.php
│ └── WebspaceConfigMaker
│ │ ├── webspace_template.tpl.php
│ │ └── MakeWebspaceConfigCommand.php
├── Property
│ ├── PropertyToSuluTypeGuesserInterface.php
│ └── PropertyToSuluTypeGuesser.php
├── SuluMakerBundle.php
├── DependencyInjection
│ └── SuluMakerExtension.php
├── Enums
│ └── Visibility.php
└── Resources
│ └── config
│ └── services.php
├── LICENSE.md
├── composer.json
├── .github
└── workflows
│ └── php.yml
├── .php-cs-fixer.dist.php
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 | /.gitignore
3 | /composer.lock
4 | /.php-cs-fixer.cache
5 |
--------------------------------------------------------------------------------
/img/maker_bundle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FriendsOfSulu/maker-bundle/HEAD/img/maker_bundle.png
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | includes:
2 | - vendor/phpstan/phpstan-webmozart-assert/extension.neon
3 |
4 | parameters:
5 | level: max
6 | paths:
7 | - src
8 | ignoreErrors:
9 | - "#ClassMetadata#"
10 |
11 |
--------------------------------------------------------------------------------
/src/Utils/NameGenerators/UniqueNameGenerator.php:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 | Preview of = $resource_key; ?>
10 |
11 |
12 | {% block content %}
13 |
14 | {{ dump() }}
15 | {% endblock %}
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/Property/PropertyToSuluTypeGuesserInterface.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | public function getPossibleTypes(string $doctrineType): array;
15 | }
16 |
--------------------------------------------------------------------------------
/src/Maker/SuluPageMaker/page_template.tpl.php:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 | {{ content.title }}
10 |
11 |
12 | {{ content.title }}
13 |
14 | The configuration for this page is under:
= $configPath; ?>
.
15 |
16 | Here are some more properties of this page:
17 | {{ dump(content) }}
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/Maker/ListConfigurationMaker/ListJoinInfo.php:
--------------------------------------------------------------------------------
1 | hasExtension('maker')) {
13 | throw new \LogicException('The Symfony MakerBundle is not installed or not enabled in the bundles.php file.');
14 | }
15 | parent::build($container);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Maker/ListConfigurationMaker/ConditionType.php:
--------------------------------------------------------------------------------
1 | */
14 | public static function descriptions(): array
15 | {
16 | return [
17 | self::ON->value => 'Use ON condition for the join',
18 | self::WITH->value => 'Use WITH condition for the join',
19 | ];
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/DependencyInjection/SuluMakerExtension.php:
--------------------------------------------------------------------------------
1 | $configs */
13 | public function load(array $configs, ContainerBuilder $container): void
14 | {
15 | $loader = new PhpFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
16 | $loader->load('services.php');
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Maker/ListConfigurationMaker/JoinType.php:
--------------------------------------------------------------------------------
1 | */
15 | public static function descriptions(): array
16 | {
17 | return [
18 | self::LEFT->value => 'Left join (all entries from left table with optional entries from the right table)',
19 | self::RIGHT->value => 'Left join (all entries from right table with optional entries from the left table)',
20 | self::INNER->value => 'Inner join (only entries that are in both tables',
21 | ];
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Maker/DocumentFixtureMaker/fixture.tpl.php:
--------------------------------------------------------------------------------
1 |
12 |
13 | declare(strict_types=1);
14 |
15 | namespace = $namespace; ?>;
16 |
17 | = $use_statements; ?>
18 |
19 | class = $class_name; ?> implements DocumentFixtureInterface
20 | {
21 | final public function load(DocumentManagerInterface $documentManager): void
22 | {
23 | // Create your objects here...
24 |
25 | // Don't forget to flush at the end
26 | $documentManager->flush();
27 | }
28 |
29 | public function getOrder(): int
30 | {
31 | return 10;
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/Enums/Visibility.php:
--------------------------------------------------------------------------------
1 | */
18 | public static function descriptions(): array
19 | {
20 | return [
21 | self::YES->value => 'Show the property',
22 | self::NO->value => 'Hide the property',
23 | self::ALWAYS->value => "Same as yes but the user can't choose to hide the property",
24 | self::NEVER_->value => "Same as no but the user can't choose to show the property",
25 | ];
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Maker/ControllerMaker/ControllerGeneratorSettings.php:
--------------------------------------------------------------------------------
1 | shouldHaveGetAction
23 | || $this->shouldHavePostAction
24 | || $this->shouldHavePutAction
25 | || $this->shouldHaveDeleteAction;
26 | }
27 |
28 | public function hasUpdateActions(): bool
29 | {
30 | return $this->shouldHavePutAction || $this->shouldHavePostAction;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Property/PropertyToSuluTypeGuesser.php:
--------------------------------------------------------------------------------
1 | 'Renders a checkbox'];
11 | }
12 |
13 | if (\in_array($doctrineType, ['text', 'string'], true)) {
14 | return [null => 'Renders a text field'];
15 | }
16 |
17 | if (\in_array($doctrineType, ['float', 'int'], true)) {
18 | return ['number' => 'Renders a number'];
19 | }
20 |
21 | if (\in_array($doctrineType, ['datetime', 'datetime_immutable'], true)) {
22 | return [
23 | 'datetime' => 'Renders a datetime selector',
24 | 'date' => 'Renders a date selector',
25 | 'time' => 'Renders a time selector',
26 | ];
27 | }
28 |
29 | return [];
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Maker/AdminConfigurationMaker/AdminGeneratorSettings.php:
--------------------------------------------------------------------------------
1 | slug = '/' . $resourceKey;
26 | $this->formKey = $resourceKey;
27 | $this->listKey = $resourceKey;
28 | }
29 |
30 | /** @var array */
31 | public array $listToolbarActions = [
32 | 'add', 'delete', 'export',
33 | ];
34 |
35 | /** @var array */
36 | public array $formToolbarActions = [
37 | 'save', 'delete',
38 | ];
39 |
40 | /** @var array */
41 | public array $permissionTypes = [];
42 | }
43 |
--------------------------------------------------------------------------------
/src/Maker/PreviewMaker/preview_provider_template.tpl.php:
--------------------------------------------------------------------------------
1 |
10 |
11 | declare(strict_types=1);
12 |
13 | namespace = $namespace; ?>;
14 |
15 | = $use_statements; ?>
16 |
17 | class = $class_name; ?> implements PreviewObjectProviderInterface
18 | {
19 | public function getObject($id, $locale): = $resource_class; ?>
20 | {
21 | }
22 |
23 | public function getId($object): string
24 | {
25 | }
26 |
27 | public function setValues($object, $locale, array $data): void
28 | {
29 | }
30 |
31 | public function setContext($object, $locale, array $context)
32 | {
33 | }
34 |
35 | public function serialize($object): string
36 | {
37 | }
38 |
39 | public function deserialize($serializedObject, $objectClass): = $resource_class; ?>
40 | {
41 | }
42 |
43 | public function getSecurityContext($id, $locale): ?string
44 | {
45 | }
46 | }
47 |
48 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 mamazu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "friendsofsulu/maker-bundle",
3 | "description": "Package to generate configuration and boilerplate code in Sulu",
4 | "type": "library",
5 | "keywords": [
6 | "maker-bundle",
7 | "sulu"
8 | ],
9 | "require": {
10 | "php": ">=8.2",
11 | "symfony/maker-bundle": "^v1.60.0",
12 | "webmozart/assert": "^1.11.0"
13 | },
14 | "require-dev": {
15 | "phpunit/phpunit": "^9.6.20",
16 | "phpstan/phpstan": "^1.11.7",
17 | "phpstan/phpstan-webmozart-assert": "^1.2.7",
18 | "php-cs-fixer/shim": "^3.14"
19 | },
20 | "license": "MIT",
21 | "autoload": {
22 | "psr-4": {
23 | "FriendsOfSulu\\MakerBundle\\": "src/"
24 | }
25 | },
26 | "config": {
27 | "sort-packages": true
28 | },
29 | "scripts": {
30 | "analyse": [
31 | "vendor/bin/phpstan"
32 | ],
33 | "fix": [
34 | "vendor/bin/php-cs-fixer fix src"
35 | ]
36 | },
37 | "authors": [
38 | {
39 | "name": "mamazu",
40 | "email": "14860264+mamazu@users.noreply.github.com"
41 | }
42 | ]
43 | }
44 |
--------------------------------------------------------------------------------
/.github/workflows/php.yml:
--------------------------------------------------------------------------------
1 | name: PHP Composer
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 | pull_request:
7 | branches: [ "master" ]
8 |
9 | permissions:
10 | contents: read
11 |
12 | jobs:
13 | build:
14 | name: "PHP ${{ matrix.php }}"
15 | runs-on: ubuntu-22.04
16 | timeout-minutes: 30
17 | strategy:
18 | matrix:
19 | php: [ '8.2', '8.3' ]
20 |
21 | steps:
22 | - uses: actions/checkout@v3
23 | - name: "Set up php"
24 | uses: "shivammathur/setup-php@v2"
25 | with:
26 | php-version: "${{ matrix.php }}"
27 | coverage: "none"
28 |
29 | - name: Validate composer.json and composer.lock
30 | run: composer validate --strict
31 |
32 | - name: Cache Composer packages
33 | id: composer-cache
34 | uses: actions/cache@v3
35 | with:
36 | path: vendor
37 | key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
38 | restore-keys: |
39 | ${{ runner.os }}-php-
40 |
41 | - name: Install dependencies
42 | run: composer install --prefer-dist --no-progress
43 |
44 | - name: Run test suite
45 | run: vendor/bin/phpstan
46 |
47 | - name: Run codestyle
48 | run: vendor/bin/php-cs-fixer check src
49 |
--------------------------------------------------------------------------------
/src/Utils/NameGenerators/ResourceKeyExtractor.php:
--------------------------------------------------------------------------------
1 | hasConstant(self::RESOURCE_KEY_CONSTANT)) {
18 | $resourceKey = $reflection->getConstant(self::RESOURCE_KEY_CONSTANT);
19 | }
20 |
21 | if ($reflection->hasProperty(self::RESOURCE_KEY_CONSTANT)) {
22 | $resourceKey = $reflection->getProperty(self::RESOURCE_KEY_CONSTANT)->getValue();
23 | }
24 |
25 | Assert::notNull(
26 | $resourceKey,
27 | 'Could not find resource key. It has to be a constant or a property on the class: ' . $className,
28 | );
29 | Assert::string(
30 | $resourceKey,
31 | 'Resource key must be a "string" but got "' . \get_debug_type($resourceKey) . '" given',
32 | );
33 |
34 | return $resourceKey;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Maker/SuluPageMaker/page_config.tpl.php:
--------------------------------------------------------------------------------
1 | ';
9 | ?>
10 |
14 |
15 | = $pageKey; ?>
16 |
17 | = $viewPath; ?>
18 | = $controller; ?>
19 | 86400
20 |
21 |
22 | = $pageName; ?>
23 |
24 |
25 |
26 |
27 |
28 | sulu_admin.title
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | sulu_admin.url
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/Maker/MigrationMaker/MigrationFilters.php:
--------------------------------------------------------------------------------
1 | $locales */
11 | public array $locales,
12 | /** @var string|null $webspace */
13 | public ?string $webspace,
14 | /** @var array $templateKeys */
15 | public array $templateKeys,
16 | /** @var array $stages */
17 | public array $stages,
18 | ) {
19 | }
20 |
21 | public function getWhereCondition(): string
22 | {
23 | $whereCondition = '(version = 0)';
24 | if (null !== $this->webspace) {
25 | $whereCondition .= ' (AND webspace = :webspace)';
26 | }
27 | if ([] !== $this->locales) {
28 | $whereCondition .= ' (AND locale IN (:locales))';
29 | }
30 | if ([] !== $this->templateKeys) {
31 | $whereCondition .= ' (AND templateKey IN (:templateKeys))';
32 | }
33 | if ([] !== $this->stages) {
34 | $whereCondition .= ' (AND stage IN (:stages))';
35 | }
36 |
37 | return $whereCondition;
38 | }
39 |
40 | /**
41 | * @return array
42 | */
43 | public function getParams(): array
44 | {
45 | $params = [];
46 | if (null !== $this->webspace) {
47 | $params['webspace'] = $this->webspace;
48 | }
49 | if ([] !== $this->locales) {
50 | $params['locales'] = $this->locales;
51 | }
52 | if ([] !== $this->templateKeys) {
53 | $params['templateKeys'] = $this->templateKeys;
54 | }
55 | if ([] !== $this->stages) {
56 | $params['stages'] = $this->stages;
57 | }
58 |
59 | return $params;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/.php-cs-fixer.dist.php:
--------------------------------------------------------------------------------
1 | exclude(['var/cache', 'tests/Resources/cache', 'node_modules'])
5 | ->in(__DIR__);
6 |
7 | $config = new PhpCsFixer\Config();
8 | $config->setRiskyAllowed(true)
9 | ->setUnsupportedPhpVersionAllowed(true)
10 | ->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect())
11 | ->setRules([
12 | '@Symfony' => true,
13 | 'array_syntax' => ['syntax' => 'short'],
14 | 'class_definition' => false,
15 | 'concat_space' => ['spacing' => 'one'],
16 | 'function_declaration' => ['closure_function_spacing' => 'none'],
17 | 'native_constant_invocation' => true,
18 | 'native_function_casing' => true,
19 | 'native_function_invocation' => ['include' => ['@internal']],
20 | 'global_namespace_import' => ['import_classes' => false, 'import_constants' => false, 'import_functions' => false],
21 | 'no_superfluous_phpdoc_tags' => ['allow_mixed' => true, 'remove_inheritdoc' => true],
22 | 'ordered_imports' => true,
23 | 'phpdoc_align' => ['align' => 'left'],
24 | 'phpdoc_types_order' => false,
25 | 'single_line_throw' => false,
26 | 'single_line_comment_spacing' => false,
27 | 'phpdoc_to_comment' => [
28 | 'ignored_tags' => ['todo', 'var'],
29 | ],
30 | 'phpdoc_separation' => [
31 | 'groups' => [
32 | ['Serializer\\*', 'VirtualProperty', 'Accessor', 'Type', 'Groups', 'Expose', 'Exclude', 'SerializedName', 'Inline', 'ExclusionPolicy'],
33 | ],
34 | ],
35 | 'echo_tag_syntax' => false,
36 | 'get_class_to_class_keyword' => false, // should be enabled as soon as support for php < 8 is dropped
37 | 'nullable_type_declaration_for_default_null_value' => true,
38 | 'no_null_property_initialization' => false,
39 | 'fully_qualified_strict_types' => false,
40 | 'new_with_parentheses' => true,
41 | 'trailing_comma_in_multiline' => ['after_heredoc' => true, 'elements' => ['array_destructuring', 'arrays', 'match']],
42 | ])
43 | ->setFinder($finder);
44 |
45 | return $config;
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/Maker/ListConfigurationMaker/list_template.tpl.php:
--------------------------------------------------------------------------------
1 | $properties
7 | * @var array $joins
8 | */
9 |
10 | use FriendsOfSulu\MakerBundle\Maker\ListConfigurationMaker\ListJoinInfo;
11 | use FriendsOfSulu\MakerBundle\Maker\ListConfigurationMaker\ListPropertyInfo;
12 |
13 | /** @param array $attributes */
14 | function renderAttributes(array $attributes): string
15 | {
16 | return \implode(' ', \array_map(
17 | fn (string $key, $value) => $key . '="' . $value . '"',
18 | \array_keys($attributes),
19 | \array_values($attributes),
20 | ));
21 | }
22 |
23 | echo '' . \PHP_EOL;
24 | ?>
25 |
26 | = $listKey; ?>
27 |
28 | $property->name,
31 | 'visibility' => $property->visibility->value,
32 | 'translation' => $property->translations,
33 | ];
34 | if ($property->visibility->isVisible()) {
35 | $attributes['searchability'] = $property->searchability ? 'yes' : 'no';
36 | }
37 | if ($property->type) {
38 | $attributes['type'] = $property->type;
39 | }
40 | ?> >
41 | = $property->name; ?>
42 | = $entityClass; ?>
43 |
44 |
45 |
46 |
47 |
48 |
49 | = $join->name; ?>
50 | = $join->targetEntity; ?>
51 | = $join->joinType->value; ?>
52 | condition) {
54 | echo ' ' . $join->condition . '' . \PHP_EOL;
55 | }
56 | if (null !== $join->conditionType) {
57 | echo ' ' . $join->conditionType->value . '' . \PHP_EOL;
58 | }
59 | ?>
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/src/Maker/MigrationMaker/migration_template.tpl.php:
--------------------------------------------------------------------------------
1 |
13 |
14 | declare(strict_types=1);
15 |
16 | namespace = $namespace; ?>;
17 |
18 | = $use_statements; ?>
19 |
20 | final class = $class_name; ?> extends AbstractMigration
21 | {
22 | /**
23 | * Process the template data for a given template key and locale.
24 | * If the locale is null, this is the base data for all locales and represents the non-translated properties.
25 | *
26 | * @param array $templateData
27 | * @return array
28 | */
29 | private function process(?string $templateKey, string $stage, ?string $locale, array $templateData): array
30 | {
31 | // TODO: Implement your own logic to process the template data.
32 | return $templateData;
33 | }
34 |
35 | public function up(Schema $schema): void
36 | {
37 | // By default only migrate the current version of the page (version 0).
38 | $whereCondition = getWhereCondition()); ?>;
39 | $params = getParams()); ?>;
40 |
41 | $sql = 'SELECT id, templateKey, locale, stage, templateData FROM pa_page_dimension_contents WHERE '.$whereCondition;
42 |
43 | // Foreach result run the process method to get the new template data.
44 | foreach ($this->connection->executeQuery($sql, $params) as $page) {
45 | $newTemplateData = $this->process(
46 | $page['templateKey'],
47 | $page['stage'],
48 | $page['locale'],
49 | json_decode($page['templateData'], associative: true, flags: JSON_THROW_ON_ERROR)
50 | );
51 |
52 | // Update the template data in the database.
53 | $this->connection->update('pa_page_dimension_contents', [
54 | 'templateData' => json_encode($newTemplateData, flags: JSON_THROW_ON_ERROR),
55 | ], [
56 | 'id' => $page['id'],
57 | ]);
58 | }
59 | }
60 |
61 | public function down(Schema $schema): void
62 | {
63 | throw new \RuntimeException('Down migrations are not supported.');
64 | }
65 | }
66 |
67 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sulu Maker Bundle
2 |
3 | This package adds code generators for Sulu configurations and other features of Sulu to get you started quicker. This bundle is based on the [Symfony maker bundle](https://symfony.com/bundles/SymfonyMakerBundle/current/index.html)
4 | ## How to install
5 | Installing it with composer is very easy:
6 | ```bash
7 | composer require --dev friendsofsulu/maker-bundle
8 | ```
9 |
10 | ### Example Usage
11 | Create an entity (either manually or with `make:entity`).
12 | ```php
13 | You can generate an XML file in the Sulu pages directory and an example template in the Twig directory.
39 |
40 | :white_check_mark: **Generating the List XML Configruation `make:sulu:list`**
41 | > The basics for this are working. This can't generate a configuration for entities with join columns.
42 |
43 | :exclamation: **Generating form XML configuration `make:sulu:form`**
44 | > TODO
45 |
46 | :white_check_mark: **Generating an admin class for an entity `sulu:make:admin`**
47 | > Basic generation is working. You can also disable parts of the view generation (generating a view without the edit form).
48 |
49 | :white_check_mark: **Generating a controller `sulu:make:controller`**
50 | > You can generate a controller with get, delete and update actions or any combination of those. And it even has some helpful tips on avoiding pitfalls with `_` in the resource key.
51 |
52 | :exclamation: **Generate all of the above `sulu:make:resource`**
53 | > TODO
54 |
55 | :white_check_mark: **Generate a Trash handler `sulu:make:trash_handler`**
56 | > Generates a Trash handler with the option to also implement restoring functionality for the resource.
57 |
58 | :white_check_mark: **Generate a Sulu fixture `sulu:make:fixture`**
59 | > Generates an example fixture to create a Sulu document
60 |
61 |
--------------------------------------------------------------------------------
/src/Maker/DocumentFixtureMaker/MakeDocumentFixtureCommand.php:
--------------------------------------------------------------------------------
1 | addArgument(
37 | self::ARG_FIXTURE_CLASS,
38 | InputArgument::OPTIONAL,
39 | 'The class name of the fixture to create',
40 | );
41 | }
42 |
43 | public function configureDependencies(DependencyBuilder $dependencies): void
44 | {
45 | }
46 |
47 | public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
48 | {
49 | $io->info('This command is for generating sulu document fixtures. If you just want to generate doctrine fixtures use bin/console make:fixtures instead.');
50 |
51 | $fixtureClass = $generator->createClassNameDetails(
52 | $this->getStringArgument($input, self::ARG_FIXTURE_CLASS),
53 | 'DataFixtures\\SuluDocument\\'
54 | );
55 |
56 | $useStatements = new UseStatementGenerator([
57 | 'Sulu\Bundle\DocumentManagerBundle\DataFixtures\DocumentFixtureInterface',
58 | 'Sulu\Component\DocumentManager\DocumentManagerInterface',
59 | ]);
60 |
61 | $generator->generateClass(
62 | $fixtureClass->getFullName(),
63 | __DIR__ . '/fixture.tpl.php',
64 | [
65 | 'use_statements' => $useStatements,
66 | ]
67 | );
68 |
69 | $generator->writeChanges();
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/Maker/WebspaceConfigMaker/webspace_template.tpl.php:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 | = $webspaceKey; ?>
14 | = $webspaceName; ?>
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | default
23 | homepage
24 |
25 |
26 |
31 |
32 |
33 | search/search
34 |
35 | error/error
36 |
37 |
38 |
39 |
40 |
41 |
42 | Main Navigation
43 | Hauptnavigation
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | Website
52 | website
53 |
54 |
55 |
56 |
57 | {host}
58 |
59 |
60 |
61 |
62 | {host}
63 |
64 |
65 |
66 |
67 | {host}
68 |
69 |
70 |
71 |
72 | {host}
73 |
74 |
75 |
76 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/src/Maker/TashHandlerMaker/trash_handler_template.tpl.php:
--------------------------------------------------------------------------------
1 |
14 |
15 | declare(strict_types=1);
16 |
17 | namespace = $namespace; ?>;
18 |
19 | = $useStatements; ?>
20 |
21 | class = $class_name; ?> implements StoreTrashItemHandlerInterface
22 | shouldHaveRestore) { ?>, RestoreTrashItemHandlerInterface
23 | {
24 | public function __construct(
25 | private readonly TrashItemRepositoryInterface $trashItemRepository,
26 | shouldHaveRestore) { ?>
27 | private readonly EntityManagerInterface $entityManager,
28 |
29 | ) {
30 | }
31 |
32 | public function store(object $resourceToTrash, array $options = []): TrashItemInterface
33 | {
34 | $restoreData = [];
35 | $id = (string) $resourceToTrash->getId();
36 | $title = 'Deleted = $settings->resourceClassToTrash; ?> with id '. $id;
37 |
38 | dd('Implement trashing logic here.');
39 |
40 | return $this->trashItemRepository->create(
41 | resourceKey: $this->getResourceKey(),
42 | resourceId: $id,
43 | resourceTitle: $title,
44 | restoreData: $restoreData,
45 | restoreType: null,
46 | restoreOptions: $options,
47 | resourceSecurityContext: null, // This should be something like = $settings->resourceClassToTrash; ?>Admin::SECURITY_CONTEXT,
48 | resourceSecurityObjectType: null,
49 | resourceSecurityObjectId: null,
50 | );
51 | }
52 |
53 | shouldHaveRestore) { ?>
54 | public function restore(TrashItemInterface $trashItem, array $restoreFormData = []): object
55 | {
56 | // Disable id generation for this entity, because we want to set the Id manually.
57 | $metadata = $this->entityManager->getClassMetaData(= $settings->resourceClassToTrash; ?>::class);
58 | $metadata->setIdGenerator(new AssignedGenerator());
59 | $metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_NONE);
60 |
61 | /** @var array $data */
62 | $data = $trashItem->getRestoreData();
63 |
64 | dd('Implement restore logic here.');
65 | /**
66 | Example:
67 |
68 | $resourceToRestore = new = $settings->resourceClassToTrash; ?>();
69 | $resourceToRestore->id = $trashItem->getResourceId();
70 | $this->entityManager->persist($resourceToRestore);
71 |
72 | return $resourceToRestore;
73 | */
74 | }
75 |
76 |
77 | public static function getResourceKey(): string
78 | {
79 | return = $settings->resourceClassToTrash; ?>::RESOURCE_KEY;
80 | }
81 | }
82 |
83 |
--------------------------------------------------------------------------------
/src/Utils/ConsoleHelperTrait.php:
--------------------------------------------------------------------------------
1 | ask($prompt, $default, null);
21 |
22 | return $result;
23 | }
24 |
25 | /**
26 | * @template T of BackedEnum $enum
27 | *
28 | * @param class-string $enum
29 | * @param ?T $default
30 | *
31 | * @return ($default is null ? T|null : T)
32 | */
33 | private function askEnum(ConsoleStyle $io, string $prompt, string $enum, ?\BackedEnum $default): ?\BackedEnum
34 | {
35 | Assert::implementsInterface($enum, \BackedEnum::class);
36 | $options = [];
37 | if (\method_exists($enum, 'descriptions')) {
38 | $options = [$enum, 'descriptions']();
39 | } else {
40 | foreach ([$enum, 'cases']() as $option) {
41 | $options[$option->value] = $option->name;
42 | }
43 | }
44 |
45 | $question = new ChoiceQuestion($prompt, $options, $default?->value);
46 | /** @var null|string $valueString */
47 | $valueString = $io->askQuestion($question);
48 | if (null === $valueString) {
49 | return $default;
50 | }
51 |
52 | return [$enum, 'from']($valueString);
53 | }
54 |
55 | private static function getStringArgument(InputInterface $input, string $key): string
56 | {
57 | $result = $input->getArgument($key);
58 | Assert::string($result, 'Input option: "' . $key . '" should be a string');
59 |
60 | return $result;
61 | }
62 |
63 | private static function interactiveEntityArgument(InputInterface $input, string $argumentName, DoctrineHelper $doctrineHelper): void
64 | {
65 | if ($input->getArgument($argumentName)) {
66 | return;
67 | }
68 |
69 | $entityQuestion = new Question('What entity do you want to generate the admin view for');
70 | $entityQuestion->setValidator(static function(mixed $value) {
71 | if (!$value) {
72 | throw new \InvalidArgumentException('This value cannot be blank.');
73 | }
74 |
75 | return $value;
76 | });
77 | $entityQuestion->setAutocompleterValues($doctrineHelper->getEntitiesForAutocomplete());
78 | $io = new SymfonyStyle($input, new ConsoleOutput());
79 |
80 | $className = $doctrineHelper->getEntityNamespace() . '\\' . $io->askQuestion($entityQuestion);
81 | $input->setArgument($argumentName, $className);
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Maker/SuluPageMaker/MakePageTypeCommand.php:
--------------------------------------------------------------------------------
1 | addArgument(self::ARG_PAGE_KEY, InputArgument::OPTIONAL, 'Key of the page (needs to be unique)');
43 | $command->addOption(
44 | self::OPT_CONTROLLER,
45 | null,
46 | InputOption::VALUE_OPTIONAL,
47 | 'Service name of the controller that should be called (eg. App\\SomeController::__invoke)',
48 | 'Sulu\Bundle\WebsiteBundle\Controller\DefaultController::indexAction',
49 | );
50 | $command->addOption(self::OPT_VIEW, null, InputOption::VALUE_OPTIONAL, 'Path where the template should be located');
51 | }
52 |
53 | public function configureDependencies(DependencyBuilder $dependencies): void
54 | {
55 | }
56 |
57 | public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
58 | {
59 | /** @var string $pageKey */
60 | $pageKey = $input->getArgument(self::ARG_PAGE_KEY);
61 | $viewPath = $input->getOption(self::OPT_VIEW) ?? 'page/' . $pageKey;
62 | $configPath = $this->projectDirectory . '/config/templates/pages/' . $pageKey . '.xml';
63 |
64 | if (\file_exists($configPath) && !$io->confirm("Config path '$configPath' already exists. Overwrite it?")) {
65 | return;
66 | }
67 |
68 | // Generate the config
69 | $generator->generateFile(
70 | $configPath,
71 | __DIR__ . '/page_config.tpl.php',
72 | [
73 | 'pageKey' => $input->getArgument(self::ARG_PAGE_KEY),
74 | 'viewPath' => $viewPath,
75 | 'controller' => $input->getOption(self::OPT_CONTROLLER),
76 | 'pageName' => Str::asHumanWords($pageKey),
77 | ]
78 | );
79 |
80 | // Generate an example template
81 | $generator->generateTemplate(
82 | $viewPath . '.html.twig',
83 | __DIR__ . '/page_template.tpl.php',
84 | ['configPath' => $configPath],
85 | );
86 |
87 | $generator->writeChanges();
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/Resources/config/services.php:
--------------------------------------------------------------------------------
1 | services();
21 |
22 | // Maker commands
23 | $services
24 | ->set(MakeListConfigurationCommand::class)
25 | ->args([
26 | '%kernel.project_dir%',
27 | service('maker.doctrine_helper'),
28 | service(ListPropertyInfoProvider::class),
29 | service(ResourceKeyExtractor::class),
30 | ])
31 | ->tag('maker.command')
32 | ;
33 |
34 | $services
35 | ->set(MakePageTypeCommand::class)
36 | ->args([
37 | '%kernel.project_dir%',
38 | ])
39 | ->tag('maker.command')
40 | ;
41 |
42 | $services
43 | ->set(MakeAdminConfigurationCommand::class)
44 | ->args([
45 | service(ResourceKeyExtractor::class),
46 | service('maker.doctrine_helper'),
47 | ])
48 | ->tag('maker.command')
49 | ;
50 |
51 | $services
52 | ->set(MakeControllerCommand::class)
53 | ->args([
54 | '%kernel.project_dir%',
55 | service(ResourceKeyExtractor::class),
56 | service('maker.doctrine_helper'),
57 | ])
58 | ->tag('maker.command')
59 | ;
60 |
61 | $services
62 | ->set(MakeDocumentFixtureCommand::class)
63 | ->tag('maker.command')
64 | ;
65 |
66 | $services->set(MakeTrashHandlerCommand::class)
67 | ->args([service('maker.doctrine_helper')])
68 | ->tag('maker.command')
69 | ;
70 |
71 | $services->set(MakePreviewCommand::class)
72 | ->args([
73 | service(ResourceKeyExtractor::class),
74 | service('maker.doctrine_helper'),
75 | ])
76 | ->tag('maker.command')
77 | ;
78 |
79 | $services
80 | ->set(MakeWebspaceConfigCommand::class)
81 | ->args([
82 | '%kernel.project_dir%',
83 | ])
84 | ->tag('maker.command')
85 | ;
86 |
87 | $services
88 | ->set(MakeMigrationCommand::class)
89 | ->args([
90 | '%kernel.project_dir%',
91 | ])
92 | ->tag('maker.command')
93 | ;
94 |
95 | // Other services
96 | $services->set(ListPropertyInfoProvider::class)
97 | ->args([
98 | service(PropertyToSuluTypeGuesser::class),
99 | ]);
100 |
101 | $services->set(PropertyToSuluTypeGuesser::class);
102 | $services->set(ResourceKeyExtractor::class);
103 | };
104 |
--------------------------------------------------------------------------------
/src/Maker/TashHandlerMaker/MakeTrashHandlerCommand.php:
--------------------------------------------------------------------------------
1 | addArgument(self::ARG_RESOURCE_CLASS, InputArgument::REQUIRED, 'The class name of the resource to trash');
46 | $command->addOption(self::OPT_NO_RESTORE, null, InputOption::VALUE_NONE, 'Do not add restore functionality');
47 | }
48 |
49 | public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void
50 | {
51 | $this->interactiveEntityArgument($input, self::ARG_RESOURCE_CLASS, $this->doctrineHelper);
52 | }
53 |
54 | public function configureDependencies(DependencyBuilder $dependencies): void
55 | {
56 | }
57 |
58 | public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
59 | {
60 | /** @var string $resourceClass */
61 | $resourceClass = $input->getArgument(self::ARG_RESOURCE_CLASS);
62 |
63 | $className = $generator->createClassNameDetails(
64 | Str::getShortClassName($resourceClass),
65 | namespacePrefix: 'Trash\\',
66 | suffix: 'TrashItemHandler'
67 | );
68 |
69 | $settings = new TashHandlerGeneratorSettings(
70 | Str::getShortClassName($resourceClass),
71 | !$input->getOption(self::OPT_NO_RESTORE),
72 | );
73 |
74 | $useStatements = new UseStatementGenerator([
75 | 'Sulu\Bundle\TrashBundle\Application\TrashItemHandler\StoreTrashItemHandlerInterface',
76 | 'Sulu\Bundle\TrashBundle\Domain\Model\TrashItemInterface',
77 | 'Sulu\Bundle\TrashBundle\Domain\Repository\TrashItemRepositoryInterface',
78 | $resourceClass,
79 | ]);
80 |
81 | if ($settings->shouldHaveRestore) {
82 | $useStatements->addUseStatement([
83 | 'Doctrine\ORM\EntityManagerInterface',
84 | 'Doctrine\ORM\Id\AssignedGenerator',
85 | 'Doctrine\ORM\Mapping\ClassMetadata',
86 | 'Sulu\Bundle\TrashBundle\Application\TrashItemHandler\RestoreTrashItemHandlerInterface',
87 | ]);
88 | }
89 |
90 | $generator->generateClass(
91 | $className->getFullName(),
92 | __DIR__ . '/trash_handler_template.tpl.php',
93 | [
94 | 'useStatements' => $useStatements,
95 | 'settings' => $settings,
96 | ],
97 | );
98 | $generator->writeChanges();
99 |
100 | $io->success(\sprintf('The "%s" trash handler class was created successfully.', $className->getShortName()));
101 | $io->text(<<addArgument(self::ARG_WEBSPACE_KEY, InputArgument::REQUIRED, 'Key of the webspace configuration');
46 | $command->addArgument(
47 | self::ARG_WEBSPACE_DIRECTORY,
48 | InputArgument::OPTIONAL,
49 | 'Directory for list configurations',
50 | $this->projectDirectory . '/config/webspaces',
51 | );
52 | $command->addOption(
53 | self::OPT_WEBSPACE_NAME,
54 | null,
55 | InputOption::VALUE_REQUIRED,
56 | 'Name of the webspace configuration',
57 | );
58 | $command->addOption(
59 | self::OPT_ASSUME_DEFAULTS,
60 | '-d',
61 | InputOption::VALUE_NONE,
62 | 'Assume default values (names will be generated from keys)',
63 | );
64 | }
65 |
66 | public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void
67 | {
68 | /** @var string $webspaceKey */
69 | $webspaceKey = $input->getArgument(self::ARG_WEBSPACE_KEY);
70 |
71 | if ($input->getOption(self::OPT_WEBSPACE_NAME)) {
72 | return;
73 | }
74 |
75 | if ($input->getOption(self::OPT_ASSUME_DEFAULTS)) {
76 | $webspaceName = Str::asHumanWords($webspaceKey);
77 | } else {
78 | $webspaceName = $io->askQuestion(new Question('What should the webspace name be'));
79 | }
80 | $input->setOption(self::OPT_WEBSPACE_NAME, $webspaceName);
81 | }
82 |
83 | public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
84 | {
85 | $webspaceKey = $input->getArgument(self::ARG_WEBSPACE_KEY);
86 | $webspaceName = $input->getOption(self::OPT_WEBSPACE_NAME);
87 |
88 | /** @var string $configDirectory */
89 | $configDirectory = $input->getArgument(self::ARG_WEBSPACE_DIRECTORY);
90 | if (!\file_exists($configDirectory)) {
91 | throw new FileNotFoundException('Could not find config directory: ' . $configDirectory);
92 | }
93 |
94 | $io->info('Using config directory: ' . $configDirectory);
95 |
96 | $filePath = $configDirectory . '/' . $webspaceKey . '.xml';
97 | if (\file_exists($filePath)) {
98 | if (!$io->confirm("The list configuration under '$filePath' already exists. Do you want to overwrite it?")) {
99 | return;
100 | }
101 | \unlink($filePath);
102 | }
103 |
104 | $generator->generateFile($filePath, __DIR__ . '/webspace_template.tpl.php', [
105 | 'webspaceKey' => $webspaceKey,
106 | 'webspaceName' => $webspaceName,
107 | ]);
108 | $generator->writeChanges();
109 | }
110 |
111 | public function configureDependencies(DependencyBuilder $dependencies): void
112 | {
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/Maker/PreviewMaker/MakePreviewCommand.php:
--------------------------------------------------------------------------------
1 | addArgument(self::ARG_RESOURCE_CLASS, InputOption::VALUE_REQUIRED, 'The resource class to be previewed');
48 | }
49 |
50 | public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void
51 | {
52 | $this->interactiveEntityArgument($input, self::ARG_RESOURCE_CLASS, $this->doctrineHelper);
53 | }
54 |
55 | public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
56 | {
57 | /** @var string $resourceClass */
58 | $resourceClass = $input->getArgument(self::ARG_RESOURCE_CLASS);
59 | Assert::classExists($resourceClass);
60 | $resourceClassName = Str::getShortClassName($resourceClass);
61 |
62 | $classNameDetails = $generator->createClassNameDetails(
63 | name: $resourceClassName,
64 | namespacePrefix: 'PreviewProvider\\',
65 | suffix: 'PreviewProvider'
66 | );
67 | $resourceKey = $this->resourceKeyExtractor->getUniqueName($resourceClass);
68 |
69 | if (\is_a($resourceClass, '\Sulu\Bundle\ContentBundle\Content\Domain\Model\ContentRichEntityInterface', true)) {
70 | $io->info([<<generateClass(
91 | $classNameDetails->getFullName(),
92 | __DIR__ . '/preview_provider_template.tpl.php',
93 | [
94 | 'use_statements' => $useStatements,
95 | 'resource_class' => $resourceClassName,
96 | ]
97 | );
98 |
99 | $templateName = 'admin_preview/' . Str::asTwigVariable($resourceClassName);
100 | $generator->generateTemplate(
101 | $templateName . '.html.twig',
102 | __DIR__ . '/preview_template.tpl.php',
103 | [
104 | 'resource_key' => $resourceKey,
105 | ]
106 | );
107 | $generator->writeChanges();
108 |
109 | $io->info(<<
114 | ...
115 | $templateName
116 | ...
117 |
118 |
119 | * Fill the template with your preview content
120 | * In the {$resourceKey}Admin class use the `createPreviewFormBuilder` method instead of `createFormViewBuilder`
121 | TEXT);
122 | }
123 |
124 | public function configureDependencies(DependencyBuilder $dependencies): void
125 | {
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/Maker/MigrationMaker/MakeMigrationCommand.php:
--------------------------------------------------------------------------------
1 | addOption(
45 | self::ARG_LOCALES,
46 | null,
47 | InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
48 | 'Array of locales that should be migrated (e.g. en fr de). All by default.',
49 | )
50 | ->addOption(
51 | self::OPT_WEBSPACE,
52 | null,
53 | InputOption::VALUE_OPTIONAL,
54 | 'Filter for webspaces (All by default)'
55 | )
56 | ->addOption(
57 | self::OPT_TEMPLATE_KEYS,
58 | null,
59 | InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
60 | 'Filter for template keys (All by default)',
61 | )
62 | ->addOption(
63 | self::OPT_STAGES,
64 | null,
65 | InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
66 | 'Filter for stages (Allowed values: live, draft. All by default)',
67 | suggestedValues: ['live', 'draft'],
68 | )
69 | ;
70 | }
71 |
72 | public function configureDependencies(DependencyBuilder $dependencies): void
73 | {
74 | $dependencies->addClassDependency(
75 | 'Doctrine\Migrations\AbstractMigration',
76 | 'doctrine/doctrine-migrations-bundle'
77 | );
78 | }
79 |
80 | public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
81 | {
82 | /** @var array $locales */
83 | $locales = $input->getOption(self::ARG_LOCALES);
84 |
85 | /** @var string|null $webspace */
86 | $webspace = $input->getOption(self::OPT_WEBSPACE);
87 | /** @var array $templateKeys */
88 | $templateKeys = $input->getOption(self::OPT_TEMPLATE_KEYS) ?? [];
89 | /** @var array $stages */
90 | $stages = $input->getOption(self::OPT_STAGES) ?? [];
91 |
92 | if (null !== $stages && [] !== $stages) {
93 | $allowedStages = ['live', 'draft'];
94 | foreach ($stages as $stage) {
95 | Assert::inArray(
96 | $stage,
97 | $allowedStages,
98 | \sprintf('Stage "%s" is not allowed. Allowed values are: %s', $stage, \implode(', ', $allowedStages))
99 | );
100 | }
101 | }
102 |
103 | $migrationDirectory = $this->projectDirectory . '/migrations';
104 | if (!\is_dir($migrationDirectory)) {
105 | \mkdir($migrationDirectory, 0755, true);
106 | }
107 |
108 | $timestamp = \date('YmdHis');
109 | $className = 'Version' . $timestamp;
110 |
111 | $migrationPath = $migrationDirectory . '/' . $className . '.php';
112 |
113 | if (\file_exists($migrationPath) && !$io->confirm("Migration file '$migrationPath' already exists. Overwrite it?")) {
114 | return;
115 | }
116 |
117 | $useStatements = new UseStatementGenerator([
118 | 'Doctrine\DBAL\Schema\Schema',
119 | 'Doctrine\Migrations\AbstractMigration',
120 | ]);
121 |
122 | $generator->generateFile(
123 | $migrationPath,
124 | __DIR__ . '/migration_template.tpl.php',
125 | [
126 | 'class_name' => $className,
127 | 'namespace' => 'DoctrineMigrations',
128 | 'use_statements' => $useStatements,
129 | 'filters' => new MigrationFilters(
130 | $locales,
131 | $webspace,
132 | $templateKeys,
133 | $stages
134 | ),
135 | ]
136 | );
137 |
138 | $generator->writeChanges();
139 |
140 | $io->success('Migration created successfully at: ' . $migrationPath);
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/src/Maker/ListConfigurationMaker/MakeListConfigurationCommand.php:
--------------------------------------------------------------------------------
1 | addArgument(
53 | self::ARG_RESOURCE_CLASS,
54 | InputArgument::OPTIONAL,
55 | \sprintf('Class that you want to generate the list view for (eg. %s>)', Str::asClassName(Str::getRandomTerm())),
56 | )
57 | ->addArgument(
58 | self::ARG_LIST_DIRECTORY,
59 | InputArgument::OPTIONAL,
60 | 'Directory for list configurations',
61 | $this->projectDirectory . '/config/lists',
62 | )
63 | ->addOption(
64 | self::OPT_ASSUME_DEFAULTS,
65 | '-d',
66 | InputOption::VALUE_NONE,
67 | 'Assuming all visible fields are searchable and use default translations.',
68 | );
69 |
70 | $inputConfig->setArgumentAsNonInteractive(self::ARG_RESOURCE_CLASS);
71 | }
72 |
73 | public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void
74 | {
75 | $this->interactiveEntityArgument($input, self::ARG_RESOURCE_CLASS, $this->doctrineHelper);
76 | }
77 |
78 | public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
79 | {
80 | /** @var string $configDirectory */
81 | $configDirectory = $input->getArgument(self::ARG_LIST_DIRECTORY);
82 | if (!\file_exists($configDirectory)) {
83 | throw new FileNotFoundException('Could not find config directory: ' . $configDirectory);
84 | }
85 |
86 | $io->info('Using config directory: ' . $configDirectory);
87 |
88 | /** @var string $className */
89 | $className = $input->getArgument(self::ARG_RESOURCE_CLASS);
90 | Assert::classExists($className, 'Class does not exist. Please provide an existing entity');
91 |
92 | $resourceKey = $this->nameGenerator->getUniqueName($className);
93 | $filePath = $configDirectory . '/' . $resourceKey . '.xml';
94 | if (\file_exists($filePath)) {
95 | if (!$io->confirm("The list configuration under '$filePath' already exists. Do you want to overwrite it?")) {
96 | return;
97 | }
98 | \unlink($filePath);
99 | }
100 |
101 | $io->writeln('Generating list configuration for ' . $className);
102 |
103 | /** @var bool $assumeDefaults */
104 | $assumeDefaults = $input->getOption(self::OPT_ASSUME_DEFAULTS);
105 |
106 | $this->propertyInfoProvider->setIo($io);
107 |
108 | $metadata = $this->doctrineHelper->getMetadata($className);
109 | Assert::implementsInterface($metadata, 'Doctrine\Persistence\Mapping\ClassMetadata');
110 | $infos = $this->propertyInfoProvider->provide($metadata, $assumeDefaults);
111 |
112 | $generator->generateFile($filePath, __DIR__ . '/list_template.tpl.php', [
113 | 'entityClass' => $className,
114 | 'listKey' => $resourceKey,
115 | 'properties' => $infos['properties'],
116 | 'joins' => $infos['joins'],
117 | ]);
118 | $generator->writeChanges();
119 |
120 | $io->success('Successfully generated list configuration.');
121 | }
122 |
123 | public function configureDependencies(DependencyBuilder $dependencies): void
124 | {
125 | $dependencies->addClassDependency(
126 | 'Doctrine\Persistence\Mapping\ClassMetadata',
127 | 'doctrine/persistence'
128 | );
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/src/Maker/ControllerMaker/controllerTemplate.tpl.php:
--------------------------------------------------------------------------------
1 |
14 |
15 | namespace = $namespace; ?>;
16 |
17 | use = $resourceClass; ?>;
18 | = $use_statements; ?>
19 |
20 | class = $class_name; ?>
21 | {
22 |
23 | public function __construct(
24 | shouldHaveGetListAction) { ?>
25 | private ViewHandlerInterface $viewHandler,
26 | private FieldDescriptorFactoryInterface $fieldDescriptorFactory,
27 | private DoctrineListBuilderFactoryInterface $listBuilderFactory,
28 | private RestHelperInterface $restHelper,
29 |
30 | needsEntityManager()) {?>
31 | private EntityManagerInterface $entityManager,
32 |
33 | shouldHaveTrashing) {?>
34 | private StoreTrashItemHandlerInterface $trashItemHandler,
35 |
36 | ) {
37 | }
38 | shouldHaveGetListAction) { ?>
39 |
40 | #[Route(
41 | '/= $resourceKey; ?>',
42 | name: 'app_admin.= $resourceKey; ?>.list',
43 | methods: ['GET'],
44 | )]
45 | public function cgetAction(): Response
46 | {
47 | $fieldDescriptors = $this->fieldDescriptorFactory->getFieldDescriptors(= $resourceClassName; ?>::RESOURCE_KEY);
48 | $listBuilder = $this->listBuilderFactory->create(= $resourceClassName; ?>::class);
49 | $this->restHelper->initializeListBuilder($listBuilder, $fieldDescriptors);
50 |
51 | $listRepresentation = new PaginatedRepresentation(
52 | $listBuilder->execute(),
53 | = $resourceClassName; ?>::RESOURCE_KEY,
54 | $listBuilder->getCurrentPage(),
55 | $listBuilder->getLimit(),
56 | $listBuilder->count()
57 | );
58 |
59 | return $this->viewHandler->handle(View::create($listRepresentation));
60 | }
61 |
62 | shouldHaveGetAction) { ?>
63 |
64 | #[Route(
65 | '/= $resourceKey; ?>/{id}',
66 | name: 'app_admin.= $resourceKey; ?>.get',
67 | methods: ['GET'],
68 | )]
69 | public function getAction(string $id): Response
70 | {
71 | $entity = $this->entityManager->find(= $resourceClassName; ?>::class, $id);
72 | if ($entity === null) {
73 | return new Response('', Response::HTTP_NOT_FOUND);
74 | }
75 |
76 | return $this->viewHandler->handle(View::create($entity));
77 | }
78 |
79 | shouldHavePostAction) { ?>
80 |
81 | #[Route(
82 | '/= $resourceKey; ?>',
83 | name: 'app_admin.= $resourceKey; ?>.post',
84 | methods: ['POST'],
85 | )]
86 | public function postAction(Request $request): Response
87 | {
88 | $entity = new = $resourceClassName; ?>();
89 | $this->mapDataFromRequest($request, $entity);
90 |
91 | $this->entityManager->persist($entity);
92 | $this->entityManager->flush();
93 |
94 | return $this->viewHandler->handle(View::create($entity));
95 | }
96 |
97 | shouldHavePutAction) { ?>
98 |
99 | #[Route(
100 | '/= $resourceKey; ?>/{id}',
101 | name: 'app_admin.= $resourceKey; ?>.put',
102 | methods: ['PUT'],
103 | )]
104 | public function putAction(string $id, Request $request): Response
105 | {
106 | $entity = $this->entityManager->find(= $resourceClassName; ?>::class, $id);
107 | if ($entity === null) {
108 | return new Response('', Response::HTTP_NOT_FOUND);
109 | }
110 |
111 | $this->mapDataFromRequest($request, $entity);
112 |
113 | $this->entityManager->flush();
114 |
115 | return $this->viewHandler->handle(View::create($entity));
116 | }
117 |
118 | shouldHaveDeleteAction) { ?>
119 |
120 | #[Route(
121 | '/= $resourceKey; ?>/{id}',
122 | name: 'app_admin.= $resourceKey; ?>.delete',
123 | methods: ['DELETE'],
124 | )]
125 | public function deleteAction(string $id): Response
126 | {
127 | $entity = $this->entityManager->find(= $resourceClassName; ?>::class, $id);
128 | if ($entity === null) {
129 | return new Response('', Response::HTTP_NOT_FOUND);
130 | }
131 |
132 | shouldHaveTrashing) { ?>
133 | $this->trashManager->store('= $resourceKey; ?>', $entity);
134 |
135 |
136 | $this->entityManager->remove($entity);
137 | $this->entityManager->flush();
138 |
139 | return $this->viewHandler->handle(View::create(null, Response::HTTP_NO_CONTENT));
140 | }
141 |
142 | shouldHavePostAction || $settings->shouldHavePutAction) { ?>
143 |
144 | public function mapDataFromRequest(Request $request, = $resourceClassName; ?> $entity): void
145 | {
146 | throw new \BadMethodCallException('There was no mapping function defined that can map a request to a = $resourceClass; ?> object. Implement '. self::class. '::mapDataFromRequest to remove the error');
147 | }
148 |
149 |
150 | public function getSecurityContext(): string
151 | {
152 | return 'sulu.app.= $resourceKey; ?>';
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/src/Maker/ListConfigurationMaker/ListPropertyInfoProvider.php:
--------------------------------------------------------------------------------
1 | io = $io;
28 | }
29 |
30 | /**
31 | * @return array{properties: array, joins: array}
32 | */
33 | public function provide(ClassMetadata $reflectionClass, bool $assumeDefaults): array
34 | {
35 | $properties = [];
36 |
37 | foreach ($reflectionClass->fieldMappings as $name => $mapping) {
38 | if (null !== ($property = $this->provideProperty($name, $mapping, $assumeDefaults))) {
39 | $properties[] = $property;
40 | }
41 | }
42 |
43 | $joins = [];
44 | foreach ($reflectionClass->associationMappings as $mapping) {
45 | if (null !== ($join = $this->provideJoin($mapping, $assumeDefaults))) {
46 | $joins[] = $join;
47 | }
48 | }
49 |
50 | return ['properties' => $properties, 'joins' => $joins];
51 | }
52 |
53 | /**
54 | * @param array{id?: true, type?: string} $mapping
55 | */
56 | protected function provideProperty(string $name, array $mapping, bool $assumeDefaults): ?ListPropertyInfo
57 | {
58 | Assert::notNull($this->io, 'No io set. Please call ' . self::class . '::setIo() before');
59 |
60 | // If it's a primary identifier (like id) we don't want to show that.
61 | if ($mapping['id'] ?? false) {
62 | return new ListPropertyInfo($name, Visibility::NO, false, 'sulu_admin.' . $name);
63 | }
64 |
65 | $this->io->info(\sprintf('Configuring property: "%s"', $name));
66 | if (!$assumeDefaults && !$this->io->confirm(\sprintf('Should this property "%s" be configured', $name))) {
67 | $this->io->info(\sprintf('Property "%s" skipped', $name));
68 |
69 | return null;
70 | }
71 |
72 | /** @var Visibility $visibility */
73 | $visibility = $this->askEnum($this->io, 'Visible?', Visibility::class, Visibility::YES);
74 |
75 | $searchable = false;
76 | if ($visibility->isVisible()) {
77 | $searchable = $assumeDefaults || $this->io->confirm('Searchable?');
78 | }
79 |
80 | $type = $this->getType($mapping['type'] ?? 'string');
81 |
82 | if ($assumeDefaults) {
83 | $translation = 'sulu_admin.' . $name;
84 | } else {
85 | $translation = $this->askString($this->io, 'Translation', 'sulu_admin.' . $name);
86 | }
87 |
88 | return new ListPropertyInfo($name, $visibility, $searchable, $translation, $type);
89 | }
90 |
91 | /**
92 | * @param array{fieldName: string, sourceEntity: string} $mapping
93 | */
94 | protected function provideJoin(array $mapping, bool $assumeDefaults): ?ListJoinInfo
95 | {
96 | Assert::notNull($this->io, 'No io set. Please call ' . self::class . '::setIo() before');
97 |
98 | $name = $mapping['fieldName'];
99 | if (!$this->io->confirm(\sprintf('Should this association "%s" be configured', $name))) {
100 | $this->io->info(\sprintf('Association "%s" skipped', $name));
101 |
102 | return null;
103 | }
104 |
105 | $joinType = JoinType::INNER;
106 | if (!$assumeDefaults) {
107 | /** @var JoinType $joinType */
108 | $joinType = $this->askEnum($this->io, 'What type of join should be used', JoinType::class, JoinType::INNER);
109 | }
110 |
111 | $condition = $this->askString($this->io, 'Additional condition (leave empty for none)', '');
112 | if ('' === $condition) {
113 | $condition = null;
114 | $conditionType = null;
115 | } else {
116 | $conditionType = $this->askEnum($this->io, 'What type of condition should be used', ConditionType::class, ConditionType::ON);
117 | }
118 |
119 | return new ListJoinInfo(
120 | $name,
121 | $mapping['sourceEntity'] . '.' . $name,
122 | $joinType,
123 | $condition,
124 | $conditionType,
125 | );
126 | }
127 |
128 | private function getType(string $doctrineType): ?string
129 | {
130 | Assert::notNull($this->io, 'No io set. Please call ' . self::class . '::setIo() before');
131 |
132 | $possibleTypes = $this->typeGuesser->getPossibleTypes($doctrineType);
133 | if ([] === $possibleTypes) {
134 | $this->io->note('Could not find any suggestions for the PHP Type of the property. You can extend the class ' . PropertyToSuluTypeGuesser::class . ' for smarter type guessing.');
135 |
136 | return null;
137 | }
138 |
139 | if (1 === \count($possibleTypes)) {
140 | $keys = \array_keys($possibleTypes);
141 | $type = \reset($keys);
142 | $description = \reset($possibleTypes);
143 | $this->io->info(\sprintf('Choosing the only possible type: %s (%s)', $type ?: 'string', $description));
144 |
145 | return $type;
146 | }
147 |
148 | /** @var string|null $type */
149 | $type = $this->io->choice('Sulu display type', $possibleTypes);
150 |
151 | if (null === $type) {
152 | $keys = \array_keys($possibleTypes);
153 | $type = \reset($keys);
154 | $description = \reset($possibleTypes);
155 | $this->io->info(\sprintf('Choosing the best guess: %s (%s)', $type ?: 'string', $description));
156 | }
157 |
158 | return $type;
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/src/Maker/AdminConfigurationMaker/configurationTemplate.tpl.php:
--------------------------------------------------------------------------------
1 | slug;
9 |
10 | echo "
12 |
13 | namespace = $namespace; ?>;
14 |
15 | = $use_statements; ?>
16 |
17 | class = $class_name; ?> extends Admin
18 | {
19 | public const SECURITY_CONTEXT = '= $translationKey; ?>';
20 |
21 | public const LIST_VIEW = '= $translationKey; ?>.list_view';
22 | shouldHaveEditForm) { ?>
23 |
24 | public const EDIT_FORM_VIEW = '= $translationKey; ?>.edit_form';
25 |
26 | shouldHaveEditForm) { ?>
27 |
28 | public const ADD_FORM_VIEW = '= $translationKey; ?>.add_form';
29 |
30 |
31 | public function __construct(
32 | private SecurityCheckerInterface $securityChecker,
33 | private ViewBuilderFactoryInterface $viewBuilderFactory
34 | shouldHaveReferences) { ?>
35 | private ReferenceViewBuilderFactoryInterface $referenceViewBuilderFactory,
36 |
37 | ) {}
38 |
39 | shouldAddMenuItem) { ?>
40 | public function configureNavigationItems(NavigationItemCollection $navigationItemCollection): void
41 | {
42 | if (!$this->securityChecker->hasPermission(static::SECURITY_CONTEXT, PermissionTypes::VIEW)) {
43 | return;
44 | }
45 |
46 | $menuItem = new NavigationItem('app.menu.= $resourceKey; ?>');
47 | $menuItem->setView(static::LIST_VIEW);
48 |
49 | $navigationItemCollection->get(Admin::SETTINGS_NAVIGATION_ITEM)->addChild($menuItem);
50 | }
51 |
52 |
53 | public function configureViews(ViewCollection $viewCollection): void
54 | {
55 | if (!$this->securityChecker->hasPermission(static::SECURITY_CONTEXT, PermissionTypes::VIEW)) {
56 | return;
57 | }
58 |
59 | $formToolbarActions = [
60 | formToolbarActions as $actionName) { ?>
61 | new ToolbarAction('sulu_admin.= $actionName; ?>'),
62 |
63 | ];
64 | $listToolbarActions = [
65 | listToolbarActions as $actionName) { ?>
66 | new ToolbarAction('sulu_admin.= $actionName; ?>'),
67 |
68 | ];
69 |
70 | // View that displays the table of all entities
71 | $viewCollection->add(
72 | $this->viewBuilderFactory->createListViewBuilder(static::LIST_VIEW, '= $slug; ?>')
73 | ->setResourceKey(= $resourceKey; ?>)
74 | ->setListKey('= $settings->listKey; ?>')
75 | ->setTitle('= $translationKey; ?>')
76 | ->addListAdapters(['table'])
77 | ->setAddView(static::ADD_FORM_VIEW)
78 | shouldHaveEditForm) { ?>
79 | ->setEditView(static::EDIT_FORM_VIEW)
80 |
81 | ->addToolbarActions($listToolbarActions)
82 | );
83 |
84 | shouldHaveAddForm) { ?>
85 | // Add form for the resource
86 | $viewCollection->add(
87 | $this->viewBuilderFactory->createResourceTabViewBuilder(static::ADD_FORM_VIEW, '= $slug; ?>/add')
88 | ->setResourceKey(= $resourceKey; ?>)
89 | ->setBackView(static::LIST_VIEW)
90 | );
91 | $viewCollection->add(
92 | $this->viewBuilderFactory->createFormViewBuilder(self::ADD_FORM_VIEW.'.details', '/details')
93 | ->setResourceKey('= $resourceKey; ?>')
94 | ->setFormKey('= $settings->formKey; ?>')
95 | ->setTabTitle('sulu_admin.details')
96 | shouldHaveEditForm) { ?>
97 | ->setEditView(static::EDIT_FORM_VIEW)
98 |
99 | ->addToolbarActions($formToolbarActions)
100 | ->setParent(static::ADD_FORM_VIEW)
101 | );
102 |
103 |
104 | shouldHaveEditForm) { ?>
105 | // Edit form view
106 | $viewCollection->add(
107 | $this->viewBuilderFactory->createResourceTabViewBuilder(static::EDIT_FORM_VIEW, '= $slug; ?>/:id')
108 | ->setResourceKey('= $resourceKey; ?>')
109 | ->setBackView(static::LIST_VIEW)
110 | ->setTitleProperty('name')
111 | );
112 | $viewCollection->add(
113 | $this->viewBuilderFactory->createFormViewBuilder(self::EDIT_FORM_VIEW.'.details', '/details')
114 | ->setResourceKey('= $resourceKey; ?>')
115 | ->setFormKey('= $settings->formKey; ?>')
116 | ->setTabTitle('sulu_admin.details')
117 | ->addToolbarActions($formToolbarActions)
118 | ->setParent(static::EDIT_FORM_VIEW)
119 | );
120 |
121 |
122 | shouldHaveReferences) { ?>
123 | if ($this->referenceViewBuilderFactory->hasReferenceListPermission()) {
124 | $insightsResourceTabViewName = static::EDIT_TABS_VIEW.'.insights';
125 |
126 | $viewCollection->add(
127 | $this->viewBuilderFactory
128 | ->createResourceTabViewBuilder($insightsResourceTabViewName, '/insights')
129 | ->setResourceKey(ListingTile::RESOURCE_KEY)
130 | ->setTabOrder(6144)
131 | ->setTabTitle('sulu_admin.insights')
132 | ->setTitleProperty('')
133 | ->setParent(static::EDIT_TABS_VIEW),
134 | );
135 |
136 | $viewCollection->add(
137 | $this->referenceViewBuilderFactory
138 | ->createReferenceListViewBuilder(
139 | $insightsResourceTabViewName.'.reference',
140 | '/references',
141 | ListingTile::RESOURCE_KEY,
142 | )
143 | ->setParent($insightsResourceTabViewName),
144 | );
145 | }
146 |
147 | }
148 |
149 | public function getSecurityContexts(): array
150 | {
151 | return [
152 | self::SULU_ADMIN_SECURITY_SYSTEM => [
153 | 'Settings' => [
154 | static::SECURITY_CONTEXT => [
155 | permissionTypes as $permissionType) { ?>
156 | PermissionTypes::= $permissionType; ?>,
157 |
158 | ],
159 | ],
160 | ],
161 | ];
162 | }
163 |
164 | }
165 |
--------------------------------------------------------------------------------
/src/Maker/AdminConfigurationMaker/MakeAdminConfigurationCommand.php:
--------------------------------------------------------------------------------
1 | addArgument(self::ARG_RESOURCE_CLASS, InputArgument::OPTIONAL, \sprintf('Class that you want to generate the list view for (eg. %s>)', Str::asClassName(Str::getRandomTerm())))
67 | ->addOption(self::OPT_PERMISSIONS, null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'List of permissions that should be configurable')
68 | ->addOption(self::OPT_FORCE, '-f', InputOption::VALUE_NONE, 'Force the creation of a new file even if the old one is already there')
69 | ->addOption(self::OPT_ASSUME_DEFAULTS, '-d', InputOption::VALUE_NONE, 'Assume default values and ask less questions')
70 | ;
71 | }
72 |
73 | public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void
74 | {
75 | $this->interactiveEntityArgument($input, self::ARG_RESOURCE_CLASS, $this->doctrineHelper);
76 |
77 | /** @var class-string $resourceClassName */
78 | $resourceClassName = $input->getArgument(self::ARG_RESOURCE_CLASS);
79 | $resourceKey = $this->resourceKeyExtractor->getUniqueName($resourceClassName);
80 |
81 | $this->settings = $this->askMethodsToBeGenerated(
82 | $io,
83 | assumeDefaults: true === $input->getOption(self::OPT_ASSUME_DEFAULTS),
84 | resourceKey: $resourceKey
85 | );
86 | }
87 |
88 | public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
89 | {
90 | /** @var class-string $resourceClassName */
91 | $resourceClassName = $input->getArgument(self::ARG_RESOURCE_CLASS);
92 |
93 | $className = $generator->createClassNameDetails(
94 | Str::getShortClassName($resourceClassName),
95 | namespacePrefix: 'Admin\\',
96 | suffix: 'Admin'
97 | );
98 |
99 | $useStatements = new UseStatementGenerator(
100 | \array_merge(
101 | self::ADMIN_DEPENDENCIES,
102 | [
103 | 'Sulu\Component\Security\Authorization\PermissionTypes',
104 | 'Sulu\Component\Security\Authorization\SecurityCheckerInterface',
105 | ]
106 | )
107 | );
108 |
109 | if ($this->settings->shouldHaveReferences) {
110 | $useStatements->addUseStatement('Sulu\Bundle\ReferenceBundle\Infrastructure\Sulu\Admin\View\ReferenceViewBuilderFactoryInterface');
111 | }
112 |
113 | if (\str_contains($this->settings->slug, '_')) {
114 | $io->warning('Your slug contains an _ this could cause problems when generating a controller for this class. It is recommended to not use underscores in the slug.');
115 | }
116 |
117 | /** @var class-string $permissionTypeClass */
118 | $permissionTypeClass = 'Sulu\Component\Security\Authorization\PermissionTypes';
119 |
120 | /** @var array $availablePermissions */
121 | $availablePermissions = \array_keys((new \ReflectionClass($permissionTypeClass))->getConstants());
122 |
123 | /** @var array $currentOptionvalue */
124 | $currentOptionvalue = $input->getOption(self::OPT_PERMISSIONS);
125 | if ($input->isInteractive() && !$currentOptionvalue) {
126 | // Get available PermissionTypes from Sulu class
127 | $choiceQuestion = new ChoiceQuestion(
128 | 'Which permissions should be configurable in the admin panel? (Multiple selections are allowed: comma separated)',
129 | $availablePermissions
130 | );
131 | $choiceQuestion->setMultiselect(true);
132 |
133 | /** @var array $answer */
134 | $answer = $io->askQuestion($choiceQuestion);
135 |
136 | $this->settings->permissionTypes = $answer;
137 | } else {
138 | $this->settings->permissionTypes = $currentOptionvalue ?: $availablePermissions;
139 | }
140 |
141 | $generator->generateClass(
142 | $className->getFullName(),
143 | __DIR__ . '/configurationTemplate.tpl.php',
144 | [
145 | 'use_statements' => $useStatements,
146 | 'resourceKey' => $this->settings->resourceKey,
147 | 'settings' => $this->settings,
148 | ]
149 | );
150 |
151 | $generator->writeChanges();
152 | }
153 |
154 | public function configureDependencies(DependencyBuilder $dependencies): void
155 | {
156 | foreach (self::ADMIN_DEPENDENCIES as $class) {
157 | $dependencies->addClassDependency($class, 'sulu/sulu-admin');
158 | }
159 | }
160 |
161 | private function askMethodsToBeGenerated(ConsoleStyle $io, bool $assumeDefaults, string $resourceKey): AdminGeneratorSettings
162 | {
163 | $settings = new AdminGeneratorSettings($resourceKey);
164 | if ($assumeDefaults) {
165 | return $settings;
166 | }
167 |
168 | $settings->shouldAddMenuItem = $io->confirm('Do you want to have a menu entry?');
169 | $settings->shouldHaveEditForm = $io->confirm('Do you want to have an edit form?');
170 | $settings->shouldHaveReferences = $io->confirm('Do you want to have an a references tab?');
171 |
172 | $slug = $this->askString($io, 'Enter the API slug', '/' . $settings->resourceKey);
173 | $settings->slug = '/' . \ltrim($slug, '/');
174 |
175 | $settings->formKey = $this->askString($io, 'Form Key', $resourceKey);
176 | $settings->listKey = $this->askString($io, 'List Key', $resourceKey);
177 |
178 | return $settings;
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/src/Maker/ControllerMaker/MakeControllerCommand.php:
--------------------------------------------------------------------------------
1 | addArgument(
65 | self::ARG_RESOURCE_CLASS,
66 | InputArgument::OPTIONAL,
67 | \sprintf('Class that you want to generate the list view for (eg. %s>)', Str::asClassName(Str::getRandomTerm())),
68 | )
69 | ->addOption(
70 | self::OPT_ESCAPE_ROUTEKEY,
71 | null,
72 | InputOption::VALUE_NONE,
73 | 'If your resource key contains underscores they will be removed',
74 | )
75 | ->addOption(
76 | self::OPT_ADD_TRASHING,
77 | null,
78 | InputOption::VALUE_NONE,
79 | 'Adding trashing functionality to the controller (see sulu:make:trash)',
80 | )
81 | ->addOption(
82 | self::OPT_ASSUME_DEFAULTS,
83 | '-d',
84 | InputOption::VALUE_NONE,
85 | 'Assume default values',
86 | )
87 | ;
88 | }
89 |
90 | public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void
91 | {
92 | $this->interactiveEntityArgument($input, self::ARG_RESOURCE_CLASS, $this->doctrineHelper);
93 |
94 | $this->settings = $this->askMethodsToBeGenerated($io, true === $input->getOption(self::OPT_ASSUME_DEFAULTS));
95 | $this->settings->shouldHaveTrashing = true === $input->getOption(self::OPT_ADD_TRASHING);
96 | }
97 |
98 | public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
99 | {
100 | $resourceClass = self::getStringArgument($input, self::ARG_RESOURCE_CLASS);
101 | Assert::classExists($resourceClass);
102 |
103 | $resourceKey = $this->resourceKeyExtractor->getUniqueName($resourceClass);
104 |
105 | $generatedClassName = $generator->createClassNameDetails(
106 | Str::getShortClassName($resourceClass),
107 | namespacePrefix: 'Controller\\Admin\\',
108 | suffix: 'Controller'
109 | );
110 |
111 | $routeResource = $resourceKey;
112 | if (\str_contains($resourceKey, '_')) {
113 | $io->warning('Your resource key "' . $resourceKey . '" contains an underscore. If this is used as a route key this will generate routes like this: "' . \str_replace('_', '/', $resourceClass) . '". This is normally unwanted behaviour. ');
114 | if ($io->confirm('Should the underscores (_) be removed?', false)) {
115 | $routeResource = \str_replace('_', '', $resourceKey);
116 | $io->info('Removed underscore in route key');
117 | }
118 | }
119 |
120 | $useStatements = self::CONTROLLER_DEPENDENCIES;
121 | if ($this->settings->shouldHaveGetListAction) {
122 | $useStatements =
123 | \array_merge(
124 | $useStatements,
125 | [
126 | 'Sulu\Component\Rest\ListBuilder\Doctrine\DoctrineListBuilderFactoryInterface',
127 | 'Sulu\Component\Rest\ListBuilder\Metadata\FieldDescriptorFactoryInterface',
128 | 'Sulu\Component\Rest\ListBuilder\PaginatedRepresentation',
129 | 'Sulu\Component\Rest\RestHelperInterface',
130 | 'Symfony\Component\HttpFoundation\Response',
131 | ]
132 | );
133 | }
134 |
135 | if ($this->settings->shouldHaveTrashing) {
136 | $useStatements[] = 'Sulu\Bundle\TrashBundle\Application\TrashItemHandler\StoreTrashItemHandlerInterface';
137 | }
138 | if ($this->settings->needsEntityManager()) {
139 | $useStatements[] = 'Doctrine\ORM\EntityManagerInterface';
140 | }
141 |
142 | if ($this->settings->hasUpdateActions()) {
143 | $useStatements[] = 'Symfony\Component\HttpFoundation\Request';
144 |
145 | $io->note('You need to implement the "mapDataFromRequest" on the generated class.');
146 | }
147 |
148 | $generator->generateClass(
149 | $generatedClassName->getFullName(),
150 | __DIR__ . '/controllerTemplate.tpl.php',
151 | [
152 | 'use_statements' => new UseStatementGenerator($useStatements),
153 | 'resourceKey' => $resourceKey,
154 | 'route_resource_key' => $resourceKey,
155 | 'resourceClass' => $resourceClass,
156 | 'settings' => $this->settings,
157 | ]
158 | );
159 |
160 | $generator->writeChanges();
161 |
162 | $controllerClassName = $generatedClassName->getFullName();
163 |
164 | $this->suggestAddingRouting($io);
165 |
166 | $io->info([
167 | 'Registering the controller in the admin panel under `config/sulu_admin.yaml`:',
168 | <<addClassDependency($class, 'friendsofsymfony/rest-bundle');
183 | }
184 | }
185 |
186 | private function askMethodsToBeGenerated(ConsoleStyle $io, bool $assumeDefaults): ControllerGeneratorSettings
187 | {
188 | $settings = new ControllerGeneratorSettings();
189 | if ($assumeDefaults) {
190 | return $settings;
191 | }
192 |
193 | $settings->shouldHaveGetListAction = $io->confirm('Should the cgetAction be generated (list view)');
194 | $settings->shouldHaveGetAction = $io->confirm('Should the getAction be generated (single item)');
195 | $settings->shouldHaveDeleteAction = $io->confirm('Should a deleteAction be generated');
196 |
197 | // Settings for the update actions
198 | $settings->shouldHavePostAction = $io->confirm('Should it have a postAction (create)');
199 | $settings->shouldHavePutAction = $io->confirm('Should it have a putAction (update action)');
200 |
201 | return $settings;
202 | }
203 |
204 | private function suggestAddingRouting(ConsoleStyle $io): void
205 | {
206 | // Try to see if the thing is already set up and don't suggest it.
207 | $routesContent = \file_get_contents(\implode(
208 | \DIRECTORY_SEPARATOR,
209 | [$this->projectDirectory, 'config', 'routes', 'sulu_admin.yaml'],
210 | ));
211 | if (\is_string($routesContent) && \str_contains($routesContent, 'admin_controllers:')) {
212 | return;
213 | }
214 |
215 | $io->note(
216 | <<