From 6b4c839631062d48f2963f3d19e2985bb51f8488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Noco=C5=84?= Date: Sat, 30 Nov 2024 10:42:30 +0100 Subject: [PATCH 01/16] Added code samples testing with Deptrac --- .github/workflows/build.yaml | 134 +++++++++++++++++++++++++++++++++++ .gitignore | 1 + README.md | 19 +++++ composer.json | 14 +++- deptrac.yaml | 27 +++++++ 5 files changed, 192 insertions(+), 3 deletions(-) create mode 100644 deptrac.yaml diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 438b94da7a..fd9e3c3dcb 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -86,3 +86,137 @@ jobs: with: reporter: github-check filter_mode: added + + code-samples: + name: Validate code samples + runs-on: "ubuntu-22.04" + strategy: + matrix: + php: + - "8.3" + steps: + - uses: actions/checkout@v4 + + - name: Setup PHP Action + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: none + extensions: "pdo_sqlite, gd" + tools: cs2pr + + - name: Add composer keys for private packagist + run: | + composer config http-basic.updates.ibexa.co $SATIS_NETWORK_KEY $SATIS_NETWORK_TOKEN + composer config github-oauth.github.com $TRAVIS_GITHUB_TOKEN + env: + SATIS_NETWORK_KEY: ${{ secrets.SATIS_NETWORK_KEY }} + SATIS_NETWORK_TOKEN: ${{ secrets.SATIS_NETWORK_TOKEN }} + TRAVIS_GITHUB_TOKEN: ${{ secrets.TRAVIS_GITHUB_TOKEN }} + + - uses: ramsey/composer-install@v3 + with: + dependency-versions: highest + + - name: Run PHPStan analysis + run: composer phpstan + + - name: Deptrac + run: composer deptrac + + code-samples-inclusion-check: + name: Check code samples inclusion + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + permissions: + # Needed to manage the comment + pull-requests: write + + steps: + - name: List modified files + id: list + run: | + URL="https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files" + echo 'CODE_SAMPLES_CHANGE<> "$GITHUB_OUTPUT" + curl -s -X GET -G $URL | jq -r '.[] | .filename,.previous_filename' | grep '^code_samples/' | tr '\n' ' ' >> "$GITHUB_OUTPUT" + echo '' >> "$GITHUB_OUTPUT" + echo 'CODE_SAMPLES_CHANGE_DELIMITER' >> "$GITHUB_OUTPUT" + + - name: Checkout target branch (base_ref) + if: steps.list.outputs.CODE_SAMPLES_CHANGE != '' + uses: actions/checkout@v3 + with: + ref: ${{ github.base_ref }} + - name: Log target branch code_samples usage + if: steps.list.outputs.CODE_SAMPLES_CHANGE != '' + run: | + git fetch origin + git checkout origin/${{ github.head_ref }} -- tools/code_samples/code_samples_usage.php + php tools/code_samples/code_samples_usage.php ${{ steps.list.outputs.CODE_SAMPLES_CHANGE }} > $HOME/code_samples_usage_target.txt + + - name: Checkout source branch (head_ref) + if: steps.list.outputs.CODE_SAMPLES_CHANGE != '' + uses: actions/checkout@v3 + with: + ref: ${{ github.head_ref }} + - name: Log source branch code_samples usage + if: steps.list.outputs.CODE_SAMPLES_CHANGE != '' + run: php tools/code_samples/code_samples_usage.php ${{ steps.list.outputs.CODE_SAMPLES_CHANGE }} > $HOME/code_samples_usage_source.txt + + - name: Compare code_samples usages (diff --unified) + if: steps.list.outputs.CODE_SAMPLES_CHANGE != '' + # diff returns 1 if there is a difference, this is normal but seen as an error by the job. + continue-on-error: true + run: | + source_length=`wc -l < $HOME/code_samples_usage_source.txt` + target_length=`wc -l < $HOME/code_samples_usage_target.txt` + diff -U $(( source_length > target_length ? source_length : target_length )) $HOME/code_samples_usage_target.txt $HOME/code_samples_usage_source.txt > $HOME/code_samples_usage.diff + - name: Check for differences + id: diff + if: steps.list.outputs.CODE_SAMPLES_CHANGE != '' + run: | + echo "CODE_SAMPLES_DIFF=$(wc -l < $HOME/code_samples_usage.diff | xargs)" >> "$GITHUB_OUTPUT" + - name: Convert code_samples usages differences (diff2html) + if: steps.list.outputs.CODE_SAMPLES_CHANGE != '' && steps.diff.outputs.CODE_SAMPLES_DIFF != '0' + run: | + npm install -g diff2html-cli + diff2html -f html -s side -t 'code_samples/ changes report' --su hidden --fct false -o stdout -i file -- $HOME/code_samples_usage.diff > $HOME/code_samples_usage.diff.html + - name: Upload code_samples usages differences artifact + id: artifact + if: steps.list.outputs.CODE_SAMPLES_CHANGE != '' && steps.diff.outputs.CODE_SAMPLES_DIFF != '0' + uses: actions/upload-artifact@v4 + with: + name: code_samples_usage.diff.html + path: ~/code_samples_usage.diff.html + overwrite: true + - name: Convert code_samples usages for comment + if: steps.list.outputs.CODE_SAMPLES_CHANGE != '' && steps.diff.outputs.CODE_SAMPLES_DIFF != '0' + run: | + echo '# code_samples/ change report' >> code_samples_usage.diff.md + echo '' >> code_samples_usage.diff.md + php tools/code_samples/code_samples_usage_diff2html.php $HOME/code_samples_usage.diff >> code_samples_usage.diff.md + echo 'Download colorized diff' >> code_samples_usage.diff.md + - name: Find Comment + id: find-comment + uses: peter-evans/find-comment@v3 + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: 'code_samples/ change report' + - name: Delete comment + if: steps.find-comment.outputs.comment-id != '' + uses: actions/github-script@v6 + with: + script: | + github.rest.issues.deleteComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: ${{ steps.find-comment.outputs.comment-id }} + }) + - name: Create comment + if: steps.list.outputs.CODE_SAMPLES_CHANGE != '' && steps.diff.outputs.CODE_SAMPLES_DIFF != '0' + uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{ github.event.pull_request.number }} + body-path: code_samples_usage.diff.md + edit-mode: replace diff --git a/.gitignore b/.gitignore index 3868734b6a..d243a010bd 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ auth.json .yarn yarn.lock docs/css/*.map +.deptrac.cache diff --git a/README.md b/README.md index a813260691..022518f948 100644 --- a/README.md +++ b/README.md @@ -54,12 +54,31 @@ of the command. ## Testing the code samples +### PHPStan This repository uses PHPStan to test the code samples. To run the tests locally execute the commands below: ```bash composer update composer phpstan ``` +Regenerate the baseline by running: +```bash +composer phpstan -- --generate-baseline +``` + +### Deptrac + +This repository uses Deptrac to test the code samples. To run the tests locally execute the commands below: +```bash +composer update +composer deptrac +``` + +Regenerate the baseline by running: +```bash +vendor/bin/deptrac --formatter=baseline +``` + ## Where to View https://doc.ibexa.co diff --git a/composer.json b/composer.json index b21f1dc068..4ffc8dbf3e 100644 --- a/composer.json +++ b/composer.json @@ -73,14 +73,22 @@ "ibexa/tree-builder": "~5.0.x-dev", "ibexa/discounts": "~5.0.x-dev", "ibexa/discounts-codes": "~5.0.x-dev", - "ibexa/core-search": "~5.0.x-dev", "ibexa/product-catalog-symbol-attribute": "~5.0.x-dev", - "ibexa/messenger": "~5.0.x-dev" + "ibexa/core-search": "~5.0.x-dev", + "ibexa/messenger": "~5.0.x-dev", + "qossmic/deptrac": "^2.0" }, "scripts": { "fix-cs": "php-cs-fixer fix --config=.php-cs-fixer.php -v --show-progress=dots", "check-cs": "@fix-cs --dry-run", - "phpstan": "phpstan analyse" + "phpstan": "phpstan analyse", + "deptrac": "deptrac analyse" + }, + "scripts-descriptions": { + "fix-cs": "Automatically fixes code style in all files", + "check-cs": "Run code style checker for all files", + "phpstan": "Run static code analysis", + "deptrac": "Run Deptrac architecture testing" }, "config": { "allow-plugins": false diff --git a/deptrac.yaml b/deptrac.yaml new file mode 100644 index 0000000000..8e3a9ab831 --- /dev/null +++ b/deptrac.yaml @@ -0,0 +1,27 @@ +imports: + - deptrac.baseline.yaml + +deptrac: + paths: + - ./code_samples + layers: + - name: CodeSamples + collectors: + - type: directory + value: code_samples + - name: IbexaContracts + collectors: + - type: classLike + value: .*Ibexa\\Contracts\\.* + - name: IbexaNotContracts + collectors: + - type: bool + must: + - type: classLike + value: .*Ibexa\\.* + must_not: + - type: classLike + value: .*Ibexa\\Contracts.* + ruleset: + CodeSamples: + - IbexaContracts From 01bc786d9c1bde7336ed5abaef3d6118b1dd3781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Noco=C5=84?= Date: Sat, 30 Nov 2024 11:20:20 +0100 Subject: [PATCH 02/16] Generated baseline and fixed some usages --- .../api/commerce/src/Command/CartCommand.php | 2 +- .../src/Command/MigrationCommand.php | 2 +- .../src/Command/SegmentCommand.php | 4 +- .../images/src/PlaceholderProvider.php | 18 -- .../Limitation/CustomLimitationType.php | 2 +- deptrac.baseline.yaml | 185 ++++++++++++++++++ docs/content_management/images/images.md | 6 +- 7 files changed, 191 insertions(+), 28 deletions(-) delete mode 100644 code_samples/back_office/images/src/PlaceholderProvider.php create mode 100644 deptrac.baseline.yaml diff --git a/code_samples/api/commerce/src/Command/CartCommand.php b/code_samples/api/commerce/src/Command/CartCommand.php index d44e01be36..136c263ad6 100644 --- a/code_samples/api/commerce/src/Command/CartCommand.php +++ b/code_samples/api/commerce/src/Command/CartCommand.php @@ -12,11 +12,11 @@ use Ibexa\Contracts\Cart\Value\EntryAddStruct; use Ibexa\Contracts\Cart\Value\EntryUpdateStruct; use Ibexa\Contracts\Checkout\Reorder\ReorderService; +use Ibexa\Contracts\Core\Repository\PermissionResolver; use Ibexa\Contracts\Core\Repository\UserService; use Ibexa\Contracts\OrderManagement\OrderServiceInterface; use Ibexa\Contracts\ProductCatalog\CurrencyServiceInterface; use Ibexa\Contracts\ProductCatalog\ProductServiceInterface; -use Ibexa\Core\Repository\Permission\PermissionResolver; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; diff --git a/code_samples/api/migration/src/Command/MigrationCommand.php b/code_samples/api/migration/src/Command/MigrationCommand.php index df0bf887b5..7f4938bbea 100644 --- a/code_samples/api/migration/src/Command/MigrationCommand.php +++ b/code_samples/api/migration/src/Command/MigrationCommand.php @@ -2,7 +2,7 @@ namespace App\Command; -use Ibexa\Migration\MigrationService; +use Ibexa\Contracts\Migration\MigrationService; use Ibexa\Migration\Repository\Migration; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; diff --git a/code_samples/api/public_php_api/src/Command/SegmentCommand.php b/code_samples/api/public_php_api/src/Command/SegmentCommand.php index abf71dad71..3b3fdd5f0f 100644 --- a/code_samples/api/public_php_api/src/Command/SegmentCommand.php +++ b/code_samples/api/public_php_api/src/Command/SegmentCommand.php @@ -4,7 +4,7 @@ use Ibexa\Contracts\Core\Repository\PermissionResolver; use Ibexa\Contracts\Core\Repository\UserService; -use Ibexa\Segmentation\Service\SegmentationService; +use Ibexa\Contracts\Segmentation\SegmentationServiceInterface; use Ibexa\Segmentation\Value\SegmentCreateStruct; use Ibexa\Segmentation\Value\SegmentGroupCreateStruct; use Symfony\Component\Console\Attribute\AsCommand; @@ -18,7 +18,7 @@ class SegmentCommand extends Command { public function __construct( - private readonly SegmentationService $segmentationService, + private readonly SegmentationServiceInterface $segmentationService, private readonly UserService $userService, private readonly PermissionResolver $permissionResolver ) { diff --git a/code_samples/back_office/images/src/PlaceholderProvider.php b/code_samples/back_office/images/src/PlaceholderProvider.php deleted file mode 100644 index 0d2024d3e3..0000000000 --- a/code_samples/back_office/images/src/PlaceholderProvider.php +++ /dev/null @@ -1,18 +0,0 @@ - Date: Thu, 31 Jul 2025 12:43:01 +0200 Subject: [PATCH 03/16] Make PHPStan happy after rebase --- .../Limitation/CustomLimitationType.php | 2 +- phpstan-baseline.neon | 74 ++++++++++++++++++- 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/code_samples/back_office/limitation/src/Security/Limitation/CustomLimitationType.php b/code_samples/back_office/limitation/src/Security/Limitation/CustomLimitationType.php index e95f96d3eb..cb31e87500 100644 --- a/code_samples/back_office/limitation/src/Security/Limitation/CustomLimitationType.php +++ b/code_samples/back_office/limitation/src/Security/Limitation/CustomLimitationType.php @@ -56,7 +56,7 @@ public function buildValue(array $limitationValues): CustomLimitationValue * * @return bool|null */ - public function evaluate(Limitation $value, UserReference $currentUser, object $object, array $targets = null): ?bool + public function evaluate(Limitation $value, UserReference $currentUser, object $object, ?array $targets = null): ?bool { if (!$value instanceof CustomLimitationValue) { throw new InvalidArgumentException('$value', 'Must be of type: CustomLimitationValue'); diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index d5067dcdcc..e2d90ca89d 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,5 +1,11 @@ parameters: ignoreErrors: + - + message: '#^Method App\\AI\\REST\\Output\\ValueObjectVisitor\\AudioText\:\:visit\(\) has parameter \$data with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: code_samples/ai_actions/src/AI/REST/Output/ValueObjectVisitor/AudioText.php + - message: '#^Class App\\Form\\Type\\TextToTextOptionsType extends generic class Symfony\\Component\\Form\\AbstractType but does not specify its types\: TData$#' identifier: missingType.generics @@ -25,7 +31,7 @@ parameters: path: code_samples/api/graphql/src/DependencyInjection/Compiler/MyCustomTypeGraphQLCompilerPass.php - - message: '#^Parameter \#1 \$migration of method Ibexa\\Migration\\MigrationService\:\:executeOne\(\) expects Ibexa\\Migration\\Repository\\Migration, Ibexa\\Migration\\Repository\\Migration\|null given\.$#' + message: '#^Parameter \#1 \$migration of method Ibexa\\Contracts\\Migration\\MigrationService\:\:executeOne\(\) expects Ibexa\\Migration\\Repository\\Migration, Ibexa\\Migration\\Repository\\Migration\|null given\.$#' identifier: argument.type count: 1 path: code_samples/api/migration/src/Command/MigrationCommand.php @@ -204,6 +210,12 @@ parameters: count: 1 path: code_samples/api/rest_api/src/Rest/ValueObjectVisitor/Greeting.php + - + message: '#^Method App\\Rest\\ValueObjectVisitor\\Greeting\:\:visit\(\) has parameter \$data with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: code_samples/api/rest_api/src/Rest/ValueObjectVisitor/Greeting.php + - message: '#^Parameter \#1 \.\.\.\$arrays of function array_merge expects array, iterable\ given\.$#' identifier: argument.type @@ -235,10 +247,16 @@ parameters: path: code_samples/back_office/content_type/src/EventListener/TextAnchorMenuTabListener.php - - message: '#^Method Ibexa\\Bundle\\Core\\Imagine\\PlaceholderProvider\:\:getPlaceholder\(\) has parameter \$options with no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue + message: '#^Method App\\Security\\Limitation\\Mapper\\CustomLimitationFormMapper\:\:mapLimitationForm\(\) has parameter \$form with generic interface Symfony\\Component\\Form\\FormInterface but does not specify its types\: TData$#' + identifier: missingType.generics + count: 1 + path: code_samples/back_office/limitation/src/Security/Limitation/Mapper/CustomLimitationFormMapper.php + + - + message: '#^Return type \(bool\) of method App\\Security\\Limitation\\Mapper\\CustomLimitationValueMapper\:\:mapLimitationValue\(\) should be compatible with return type \(array\\) of method Ibexa\\AdminUi\\Limitation\\LimitationValueMapperInterface\:\:mapLimitationValue\(\)$#' + identifier: method.childReturnType count: 1 - path: code_samples/back_office/images/src/PlaceholderProvider.php + path: code_samples/back_office/limitation/src/Security/Limitation/Mapper/CustomLimitationValueMapper.php - message: '#^Method App\\Security\\MyPolicyProvider\:\:getFiles\(\) return type has no value type specified in iterable type array\.$#' @@ -366,6 +384,18 @@ parameters: count: 1 path: code_samples/customer_portal/src/Form/VerifyType.php + - + message: '#^Property App\\Command\\ManageDiscountsCommand\:\:\$defaultName has no type specified\.$#' + identifier: missingType.property + count: 1 + path: code_samples/discounts/src/Command/ManageDiscountsCommand.php + + - + message: '#^Access to protected property Ibexa\\Contracts\\ContentForms\\Data\\Content\\FieldData\:\:\$fieldDefinition\.$#' + identifier: property.protected + count: 1 + path: code_samples/field_types/2dpoint_ft/src/FieldType/Point2D/Type.php + - message: '#^Method App\\FieldType\\Point2D\\Type\:\:getSettingsSchema\(\) return type has no value type specified in iterable type array\.$#' identifier: missingType.iterableValue @@ -408,12 +438,24 @@ parameters: count: 1 path: code_samples/field_types/2dpoint_ft/steps/step_3/Point2DType.php + - + message: '#^Access to protected property Ibexa\\Contracts\\ContentForms\\Data\\Content\\FieldData\:\:\$fieldDefinition\.$#' + identifier: property.protected + count: 1 + path: code_samples/field_types/2dpoint_ft/steps/step_3/Type.php + - message: '#^Method App\\FieldType\\Point2D\\Type\:\:mapFieldValueForm\(\) has parameter \$fieldForm with generic interface Symfony\\Component\\Form\\FormInterface but does not specify its types\: TData$#' identifier: missingType.generics count: 1 path: code_samples/field_types/2dpoint_ft/steps/step_3/Type.php + - + message: '#^Access to protected property Ibexa\\Contracts\\ContentForms\\Data\\Content\\FieldData\:\:\$fieldDefinition\.$#' + identifier: property.protected + count: 1 + path: code_samples/field_types/2dpoint_ft/steps/step_6/Type.php + - message: '#^Method App\\FieldType\\Point2D\\Type\:\:getSettingsSchema\(\) return type has no value type specified in iterable type array\.$#' identifier: missingType.iterableValue @@ -432,6 +474,12 @@ parameters: count: 1 path: code_samples/field_types/generic_ft/src/FieldType/HelloWorld/Comparison/Comparable.php + - + message: '#^Access to protected property Ibexa\\Contracts\\ContentForms\\Data\\Content\\FieldData\:\:\$fieldDefinition\.$#' + identifier: property.protected + count: 1 + path: code_samples/field_types/generic_ft/src/FieldType/HelloWorld/Type.php + - message: '#^Method App\\FieldType\\HelloWorld\\Type\:\:mapFieldValueForm\(\) has parameter \$fieldForm with generic interface Symfony\\Component\\Form\\FormInterface but does not specify its types\: TData$#' identifier: missingType.generics @@ -648,6 +696,12 @@ parameters: count: 1 path: code_samples/search/custom/src/Query/Aggregation/Elasticsearch/PriorityRangeAggregationResultExtractor.php + - + message: '#^Class App\\Query\\Aggregation\\PriorityRangeAggregation extends generic class Ibexa\\Contracts\\Core\\Repository\\Values\\Content\\Query\\Aggregation\\AbstractRangeAggregation but does not specify its types\: TValue$#' + identifier: missingType.generics + count: 1 + path: code_samples/search/custom/src/Query/Aggregation/PriorityRangeAggregation.php + - message: '#^Argument of an invalid type stdClass supplied for foreach, only iterables are supported\.$#' identifier: foreach.nonIterable @@ -666,6 +720,18 @@ parameters: count: 1 path: code_samples/search/custom/src/Query/Criterion/Elasticsearch/CameraManufacturerVisitor.php + - + message: '#^Method App\\Query\\Criterion\\Solr\\CameraManufacturerVisitor\:\:canVisit\(\) has no return type specified\.$#' + identifier: missingType.return + count: 1 + path: code_samples/search/custom/src/Query/Criterion/Solr/CameraManufacturerVisitor.php + + - + message: '#^Method App\\Query\\Criterion\\Solr\\CameraManufacturerVisitor\:\:visit\(\) has no return type specified\.$#' + identifier: missingType.return + count: 1 + path: code_samples/search/custom/src/Query/Criterion/Solr/CameraManufacturerVisitor.php + - message: '#^Parameter \#2 \$array of function array_map expects array, array\\|bool\|float\|int\|string given\.$#' identifier: argument.type From 493e4fe3afd2e1d505572e61343240d48fd8abed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Noco=C5=84?= Date: Thu, 31 Jul 2025 12:44:55 +0200 Subject: [PATCH 04/16] Regenrated Deptrac baseline --- deptrac.baseline.yaml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/deptrac.baseline.yaml b/deptrac.baseline.yaml index 58a3b08d33..3a92f516cf 100644 --- a/deptrac.baseline.yaml +++ b/deptrac.baseline.yaml @@ -1,5 +1,17 @@ deptrac: skip_violations: + Acme\ExampleBundle\DependencyInjection\AcmeExampleExtension: + - Ibexa\Bundle\Core\DependencyInjection\Configuration\SiteAccessAware\ConfigurationProcessor + - Ibexa\Bundle\Core\DependencyInjection\Configuration\SiteAccessAware\ContextualizerInterface + Acme\ExampleBundle\DependencyInjection\Configuration: + - Ibexa\Bundle\Core\DependencyInjection\Configuration\SiteAccessAware\Configuration + App\AI\REST\Input\Parser\TranscribeAudio: + - Ibexa\ConnectorAi\REST\Input\Parser\Action + - Ibexa\Rest\Input\BaseParser + App\AI\REST\Output\Resolver\AudioTextResolver: + - Ibexa\ConnectorAi\REST\Output\ResolverInterface + App\AI\REST\Value\AudioText: + - Ibexa\ConnectorAi\REST\Value\RestActionResponse App\Attribute\Percent\Form\PercentValueFormMapper: - Ibexa\Bundle\ProductCatalog\Validator\Constraints\AttributeValue App\Attribute\Percent\PercentOptionsFormMapper: @@ -8,6 +20,10 @@ deptrac: - Ibexa\ProductCatalog\Local\Persistence\Legacy\Attribute\Float\StorageSchema App\Attribute\Percent\Storage\PercentStorageDefinition: - Ibexa\ProductCatalog\Local\Persistence\Legacy\Attribute\Float\StorageSchema + App\AutomatedTranslation\AiClient: + - Ibexa\AutomatedTranslation\Exception\ClientNotConfiguredException + App\AutomatedTranslation\ImageFieldEncoder: + - Ibexa\Core\FieldType\Image\Value App\Block\Listener\MyBlockListener: - Ibexa\FieldTypePage\FieldType\Page\Block\Renderer\BlockRenderEvents - Ibexa\FieldTypePage\FieldType\Page\Block\Renderer\Event\PreRenderEvent @@ -17,12 +33,21 @@ deptrac: - Ibexa\Checkout\Value\Workflow\Workflow App\Checkout\Workflow\Strategy\NewWorkflowConditionalStep: - Ibexa\Checkout\Value\Workflow\Workflow + App\Command\AddMissingAltTextCommand: + - Ibexa\Core\FieldType\Image\Value + - Ibexa\Core\IO\IOBinarydataHandler App\Command\CalendarCommand: - Ibexa\Scheduler\Calendar\EventAction\RescheduleEventActionContext App\Command\CatalogCommand: - Ibexa\ProductCatalog\Local\Repository\Values\Catalog\Status App\Command\CreateImageCommand: - Ibexa\Core\FieldType\Image\Value + App\Command\ManageDiscountsCommand: + - Ibexa\DiscountsCodes\Value\DiscountCondition\IsValidDiscountCode + - Ibexa\Discounts\Value\DiscountCondition\IsInCurrency + - Ibexa\Discounts\Value\DiscountCondition\IsInRegions + - Ibexa\Discounts\Value\DiscountCondition\IsProductInArray + - Ibexa\Discounts\Value\DiscountRule\FixedAmount App\Command\MigrationCommand: - Ibexa\Migration\Repository\Migration App\Command\PaymentMethodCommand: From c41f6c76b393ed945400d83b41fc3d2bc73fe77d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Noco=C5=84?= Date: Mon, 22 Sep 2025 14:13:15 +0200 Subject: [PATCH 05/16] Adjusted code samples for Notifications --- .../notifications/src/Notification/ListRenderer.php | 6 +++++- .../notifications/src/Notification/MyRenderer.php | 6 ++++-- code_samples/notifications/Src/Query/search.php | 8 +++++--- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/code_samples/back_office/notifications/src/Notification/ListRenderer.php b/code_samples/back_office/notifications/src/Notification/ListRenderer.php index ced5b5dd1d..1c4eb18d20 100644 --- a/code_samples/back_office/notifications/src/Notification/ListRenderer.php +++ b/code_samples/back_office/notifications/src/Notification/ListRenderer.php @@ -9,6 +9,7 @@ use Ibexa\Core\Notification\Renderer\TypedNotificationRendererInterface; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Routing\RouterInterface; +use Symfony\Contracts\Translation\TranslatorInterface; use Twig\Environment; class ListRenderer implements NotificationRenderer, TypedNotificationRendererInterface @@ -19,11 +20,14 @@ class ListRenderer implements NotificationRenderer, TypedNotificationRendererInt protected RequestStack $requestStack; - public function __construct(Environment $twig, RouterInterface $router, RequestStack $requestStack) + protected TranslatorInterface $translator; + + public function __construct(Environment $twig, RouterInterface $router, RequestStack $requestStack, TranslatorInterface $translator) { $this->twig = $twig; $this->router = $router; $this->requestStack = $requestStack; + $this->translator = $translator; } public function render(Notification $notification): string diff --git a/code_samples/back_office/notifications/src/Notification/MyRenderer.php b/code_samples/back_office/notifications/src/Notification/MyRenderer.php index 0c2d38ccd4..e0bbd56e02 100644 --- a/code_samples/back_office/notifications/src/Notification/MyRenderer.php +++ b/code_samples/back_office/notifications/src/Notification/MyRenderer.php @@ -8,13 +8,15 @@ use Ibexa\Core\Notification\Renderer\NotificationRenderer; use Ibexa\Core\Notification\Renderer\TypedNotificationRendererInterface; use Symfony\Component\Routing\RouterInterface; +use Symfony\Contracts\Translation\TranslatorInterface; use Twig\Environment; class MyRenderer implements NotificationRenderer, TypedNotificationRendererInterface { public function __construct( protected Environment $twig, - protected RouterInterface $router + protected RouterInterface $router, + protected TranslatorInterface $translator ) { } @@ -22,7 +24,7 @@ public function render(Notification $notification): string { return $this->twig->render('@ibexadesign/notification.html.twig', [ 'notification' => $notification, - 'template_to_extend' => $templateToExtend, + 'template_to_extend' => '@ibexadesign/account/notifications/list_item.html.twig', ]); } diff --git a/code_samples/notifications/Src/Query/search.php b/code_samples/notifications/Src/Query/search.php index d771b8b343..f7f0e4e2d2 100644 --- a/code_samples/notifications/Src/Query/search.php +++ b/code_samples/notifications/Src/Query/search.php @@ -2,10 +2,12 @@ declare(strict_types=1); -use Ibexa\Contracts\Core\Repository\Values\NotificationQuery; +use Ibexa\Contracts\Core\Repository\Values\Notification\Query\Criterion\DateCreated; +use Ibexa\Contracts\Core\Repository\Values\Notification\Query\Criterion\Status; +use Ibexa\Contracts\Core\Repository\Values\Notification\Query\Criterion\Type; +use Ibexa\Contracts\Core\Repository\Values\Notification\Query\NotificationQuery; -$repository = $this->getRepository(); -$notificationService = $repository->getNotificationService(); +/** @var \Ibexa\Contracts\Core\Repository\NotificationService $notificationService */ $query = new NotificationQuery([], 0, 25); $query->addCriterion(new Type('Workflow:Review')); From 43acba477b3a82db3ee186f6121ca77df9420881 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Noco=C5=84?= Date: Mon, 22 Sep 2025 18:29:40 +0200 Subject: [PATCH 06/16] Adjusted custom limitation example --- phpstan-baseline.neon | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index e2d90ca89d..92463bf06c 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -250,13 +250,7 @@ parameters: message: '#^Method App\\Security\\Limitation\\Mapper\\CustomLimitationFormMapper\:\:mapLimitationForm\(\) has parameter \$form with generic interface Symfony\\Component\\Form\\FormInterface but does not specify its types\: TData$#' identifier: missingType.generics count: 1 - path: code_samples/back_office/limitation/src/Security/Limitation/Mapper/CustomLimitationFormMapper.php - - - - message: '#^Return type \(bool\) of method App\\Security\\Limitation\\Mapper\\CustomLimitationValueMapper\:\:mapLimitationValue\(\) should be compatible with return type \(array\\) of method Ibexa\\AdminUi\\Limitation\\LimitationValueMapperInterface\:\:mapLimitationValue\(\)$#' - identifier: method.childReturnType - count: 1 - path: code_samples/back_office/limitation/src/Security/Limitation/Mapper/CustomLimitationValueMapper.php + path: code_samples/back_office/limitation/src/Controller/CustomController.php - message: '#^Method App\\Security\\MyPolicyProvider\:\:getFiles\(\) return type has no value type specified in iterable type array\.$#' From 66571ee3b4f18d69e7097bc7999ddaf11e0d5cff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Noco=C5=84?= Date: Mon, 22 Sep 2025 18:29:43 +0200 Subject: [PATCH 07/16] Revert "Adjusted code samples for Notifications" This reverts commit a0555c75d49a2e8790ef7c2067d99efc52260a58. --- .../notifications/src/Notification/ListRenderer.php | 6 +----- .../notifications/src/Notification/MyRenderer.php | 8 ++++---- code_samples/notifications/Src/Query/search.php | 8 +++----- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/code_samples/back_office/notifications/src/Notification/ListRenderer.php b/code_samples/back_office/notifications/src/Notification/ListRenderer.php index 1c4eb18d20..ced5b5dd1d 100644 --- a/code_samples/back_office/notifications/src/Notification/ListRenderer.php +++ b/code_samples/back_office/notifications/src/Notification/ListRenderer.php @@ -9,7 +9,6 @@ use Ibexa\Core\Notification\Renderer\TypedNotificationRendererInterface; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Routing\RouterInterface; -use Symfony\Contracts\Translation\TranslatorInterface; use Twig\Environment; class ListRenderer implements NotificationRenderer, TypedNotificationRendererInterface @@ -20,14 +19,11 @@ class ListRenderer implements NotificationRenderer, TypedNotificationRendererInt protected RequestStack $requestStack; - protected TranslatorInterface $translator; - - public function __construct(Environment $twig, RouterInterface $router, RequestStack $requestStack, TranslatorInterface $translator) + public function __construct(Environment $twig, RouterInterface $router, RequestStack $requestStack) { $this->twig = $twig; $this->router = $router; $this->requestStack = $requestStack; - $this->translator = $translator; } public function render(Notification $notification): string diff --git a/code_samples/back_office/notifications/src/Notification/MyRenderer.php b/code_samples/back_office/notifications/src/Notification/MyRenderer.php index e0bbd56e02..c79a26f0a1 100644 --- a/code_samples/back_office/notifications/src/Notification/MyRenderer.php +++ b/code_samples/back_office/notifications/src/Notification/MyRenderer.php @@ -8,15 +8,13 @@ use Ibexa\Core\Notification\Renderer\NotificationRenderer; use Ibexa\Core\Notification\Renderer\TypedNotificationRendererInterface; use Symfony\Component\Routing\RouterInterface; -use Symfony\Contracts\Translation\TranslatorInterface; use Twig\Environment; class MyRenderer implements NotificationRenderer, TypedNotificationRendererInterface { public function __construct( protected Environment $twig, - protected RouterInterface $router, - protected TranslatorInterface $translator + protected RouterInterface $router ) { } @@ -24,7 +22,7 @@ public function render(Notification $notification): string { return $this->twig->render('@ibexadesign/notification.html.twig', [ 'notification' => $notification, - 'template_to_extend' => '@ibexadesign/account/notifications/list_item.html.twig', + 'template_to_extend' => $templateToExtend, ]); } @@ -37,6 +35,8 @@ public function generateUrl(Notification $notification): ?string return null; } + } + public function getTypeLabel(): string { return /** @Desc("Workflow stage changed") */ diff --git a/code_samples/notifications/Src/Query/search.php b/code_samples/notifications/Src/Query/search.php index f7f0e4e2d2..d771b8b343 100644 --- a/code_samples/notifications/Src/Query/search.php +++ b/code_samples/notifications/Src/Query/search.php @@ -2,12 +2,10 @@ declare(strict_types=1); -use Ibexa\Contracts\Core\Repository\Values\Notification\Query\Criterion\DateCreated; -use Ibexa\Contracts\Core\Repository\Values\Notification\Query\Criterion\Status; -use Ibexa\Contracts\Core\Repository\Values\Notification\Query\Criterion\Type; -use Ibexa\Contracts\Core\Repository\Values\Notification\Query\NotificationQuery; +use Ibexa\Contracts\Core\Repository\Values\NotificationQuery; -/** @var \Ibexa\Contracts\Core\Repository\NotificationService $notificationService */ +$repository = $this->getRepository(); +$notificationService = $repository->getNotificationService(); $query = new NotificationQuery([], 0, 25); $query->addCriterion(new Type('Workflow:Review')); From 40a63bbb02de42a5edc78fcb98a9f60e5bc0061d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Noco=C5=84?= Date: Mon, 22 Sep 2025 18:30:25 +0200 Subject: [PATCH 08/16] Put Notification errors into the baseline --- .../back_office/notifications/src/Notification/MyRenderer.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/code_samples/back_office/notifications/src/Notification/MyRenderer.php b/code_samples/back_office/notifications/src/Notification/MyRenderer.php index c79a26f0a1..5c3adade2e 100644 --- a/code_samples/back_office/notifications/src/Notification/MyRenderer.php +++ b/code_samples/back_office/notifications/src/Notification/MyRenderer.php @@ -34,8 +34,6 @@ public function generateUrl(Notification $notification): ?string return null; } - - } public function getTypeLabel(): string { From c32fb605672f4c86d525899da6f37d14a653fe86 Mon Sep 17 00:00:00 2001 From: mnocon Date: Mon, 22 Sep 2025 16:47:07 +0000 Subject: [PATCH 09/16] PHP & JS CS Fixes --- .../back_office/notifications/src/Notification/MyRenderer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code_samples/back_office/notifications/src/Notification/MyRenderer.php b/code_samples/back_office/notifications/src/Notification/MyRenderer.php index 5c3adade2e..0c2d38ccd4 100644 --- a/code_samples/back_office/notifications/src/Notification/MyRenderer.php +++ b/code_samples/back_office/notifications/src/Notification/MyRenderer.php @@ -34,7 +34,7 @@ public function generateUrl(Notification $notification): ?string return null; } - + public function getTypeLabel(): string { return /** @Desc("Workflow stage changed") */ From 4bdddcd376734678928ef36ae31fd9df29f9a060 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Noco=C5=84?= Date: Tue, 30 Sep 2025 15:48:42 +0200 Subject: [PATCH 10/16] Fixed merge --- phpstan-baseline.neon | 60 ------------------------------------------- 1 file changed, 60 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 92463bf06c..e1134208d9 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,11 +1,5 @@ parameters: ignoreErrors: - - - message: '#^Method App\\AI\\REST\\Output\\ValueObjectVisitor\\AudioText\:\:visit\(\) has parameter \$data with no type specified\.$#' - identifier: missingType.parameter - count: 1 - path: code_samples/ai_actions/src/AI/REST/Output/ValueObjectVisitor/AudioText.php - - message: '#^Class App\\Form\\Type\\TextToTextOptionsType extends generic class Symfony\\Component\\Form\\AbstractType but does not specify its types\: TData$#' identifier: missingType.generics @@ -210,12 +204,6 @@ parameters: count: 1 path: code_samples/api/rest_api/src/Rest/ValueObjectVisitor/Greeting.php - - - message: '#^Method App\\Rest\\ValueObjectVisitor\\Greeting\:\:visit\(\) has parameter \$data with no type specified\.$#' - identifier: missingType.parameter - count: 1 - path: code_samples/api/rest_api/src/Rest/ValueObjectVisitor/Greeting.php - - message: '#^Parameter \#1 \.\.\.\$arrays of function array_merge expects array, iterable\ given\.$#' identifier: argument.type @@ -378,18 +366,6 @@ parameters: count: 1 path: code_samples/customer_portal/src/Form/VerifyType.php - - - message: '#^Property App\\Command\\ManageDiscountsCommand\:\:\$defaultName has no type specified\.$#' - identifier: missingType.property - count: 1 - path: code_samples/discounts/src/Command/ManageDiscountsCommand.php - - - - message: '#^Access to protected property Ibexa\\Contracts\\ContentForms\\Data\\Content\\FieldData\:\:\$fieldDefinition\.$#' - identifier: property.protected - count: 1 - path: code_samples/field_types/2dpoint_ft/src/FieldType/Point2D/Type.php - - message: '#^Method App\\FieldType\\Point2D\\Type\:\:getSettingsSchema\(\) return type has no value type specified in iterable type array\.$#' identifier: missingType.iterableValue @@ -432,24 +408,12 @@ parameters: count: 1 path: code_samples/field_types/2dpoint_ft/steps/step_3/Point2DType.php - - - message: '#^Access to protected property Ibexa\\Contracts\\ContentForms\\Data\\Content\\FieldData\:\:\$fieldDefinition\.$#' - identifier: property.protected - count: 1 - path: code_samples/field_types/2dpoint_ft/steps/step_3/Type.php - - message: '#^Method App\\FieldType\\Point2D\\Type\:\:mapFieldValueForm\(\) has parameter \$fieldForm with generic interface Symfony\\Component\\Form\\FormInterface but does not specify its types\: TData$#' identifier: missingType.generics count: 1 path: code_samples/field_types/2dpoint_ft/steps/step_3/Type.php - - - message: '#^Access to protected property Ibexa\\Contracts\\ContentForms\\Data\\Content\\FieldData\:\:\$fieldDefinition\.$#' - identifier: property.protected - count: 1 - path: code_samples/field_types/2dpoint_ft/steps/step_6/Type.php - - message: '#^Method App\\FieldType\\Point2D\\Type\:\:getSettingsSchema\(\) return type has no value type specified in iterable type array\.$#' identifier: missingType.iterableValue @@ -468,12 +432,6 @@ parameters: count: 1 path: code_samples/field_types/generic_ft/src/FieldType/HelloWorld/Comparison/Comparable.php - - - message: '#^Access to protected property Ibexa\\Contracts\\ContentForms\\Data\\Content\\FieldData\:\:\$fieldDefinition\.$#' - identifier: property.protected - count: 1 - path: code_samples/field_types/generic_ft/src/FieldType/HelloWorld/Type.php - - message: '#^Method App\\FieldType\\HelloWorld\\Type\:\:mapFieldValueForm\(\) has parameter \$fieldForm with generic interface Symfony\\Component\\Form\\FormInterface but does not specify its types\: TData$#' identifier: missingType.generics @@ -690,12 +648,6 @@ parameters: count: 1 path: code_samples/search/custom/src/Query/Aggregation/Elasticsearch/PriorityRangeAggregationResultExtractor.php - - - message: '#^Class App\\Query\\Aggregation\\PriorityRangeAggregation extends generic class Ibexa\\Contracts\\Core\\Repository\\Values\\Content\\Query\\Aggregation\\AbstractRangeAggregation but does not specify its types\: TValue$#' - identifier: missingType.generics - count: 1 - path: code_samples/search/custom/src/Query/Aggregation/PriorityRangeAggregation.php - - message: '#^Argument of an invalid type stdClass supplied for foreach, only iterables are supported\.$#' identifier: foreach.nonIterable @@ -714,18 +666,6 @@ parameters: count: 1 path: code_samples/search/custom/src/Query/Criterion/Elasticsearch/CameraManufacturerVisitor.php - - - message: '#^Method App\\Query\\Criterion\\Solr\\CameraManufacturerVisitor\:\:canVisit\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: code_samples/search/custom/src/Query/Criterion/Solr/CameraManufacturerVisitor.php - - - - message: '#^Method App\\Query\\Criterion\\Solr\\CameraManufacturerVisitor\:\:visit\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: code_samples/search/custom/src/Query/Criterion/Solr/CameraManufacturerVisitor.php - - message: '#^Parameter \#2 \$array of function array_map expects array, array\\|bool\|float\|int\|string given\.$#' identifier: argument.type From 3fae1abeae3412d74e1ec5bc0d4b5357f6ea75b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Noco=C5=84?= Date: Tue, 30 Sep 2025 16:50:38 +0200 Subject: [PATCH 11/16] Expanded the rules to cover additional use cases --- README.md | 1 + .../Storage/PercentStorageConverter.php | 5 ++- .../Storage/PercentStorageDefinition.php | 5 ++- deptrac.baseline.yaml | 26 +++------------- deptrac.yaml | 31 +++++++++++++++++-- docs/pim/create_custom_attribute_type.md | 19 ++++++++++++ 6 files changed, 57 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 022518f948..274283f000 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ composer phpstan -- --generate-baseline ### Deptrac This repository uses Deptrac to test the code samples. To run the tests locally execute the commands below: + ```bash composer update composer deptrac diff --git a/code_samples/catalog/custom_attribute_type/src/Attribute/Percent/Storage/PercentStorageConverter.php b/code_samples/catalog/custom_attribute_type/src/Attribute/Percent/Storage/PercentStorageConverter.php index 720d550cc2..6ee2a72701 100644 --- a/code_samples/catalog/custom_attribute_type/src/Attribute/Percent/Storage/PercentStorageConverter.php +++ b/code_samples/catalog/custom_attribute_type/src/Attribute/Percent/Storage/PercentStorageConverter.php @@ -5,14 +5,13 @@ namespace App\Attribute\Percent\Storage; use Ibexa\Contracts\ProductCatalog\Local\Attribute\StorageConverterInterface; -use Ibexa\ProductCatalog\Local\Persistence\Legacy\Attribute\Float\StorageSchema; use Webmozart\Assert\Assert; final class PercentStorageConverter implements StorageConverterInterface { public function fromPersistence(array $data) { - $value = $data[StorageSchema::COLUMN_VALUE]; + $value = $data['value']; Assert::nullOrNumeric($value); return $value; @@ -23,7 +22,7 @@ public function toPersistence($value): array Assert::nullOrNumeric($value); return [ - StorageSchema::COLUMN_VALUE => $value, + 'value' => $value, ]; } } diff --git a/code_samples/catalog/custom_attribute_type/src/Attribute/Percent/Storage/PercentStorageDefinition.php b/code_samples/catalog/custom_attribute_type/src/Attribute/Percent/Storage/PercentStorageDefinition.php index 415c784344..eab81dd4c5 100644 --- a/code_samples/catalog/custom_attribute_type/src/Attribute/Percent/Storage/PercentStorageDefinition.php +++ b/code_samples/catalog/custom_attribute_type/src/Attribute/Percent/Storage/PercentStorageDefinition.php @@ -10,19 +10,18 @@ use Doctrine\DBAL\Types\Types; use Ibexa\Contracts\ProductCatalog\Local\Attribute\StorageDefinitionInterface; -use Ibexa\ProductCatalog\Local\Persistence\Legacy\Attribute\Float\StorageSchema; final class PercentStorageDefinition implements StorageDefinitionInterface { public function getColumns(): array { return [ - StorageSchema::COLUMN_VALUE => Types::FLOAT, + 'value' => Types::FLOAT, ]; } public function getTableName(): string { - return StorageSchema::TABLE_NAME; + return 'app_product_specification_attribute_percent'; } } diff --git a/deptrac.baseline.yaml b/deptrac.baseline.yaml index 3a92f516cf..e5d5ea812f 100644 --- a/deptrac.baseline.yaml +++ b/deptrac.baseline.yaml @@ -1,27 +1,7 @@ deptrac: skip_violations: - Acme\ExampleBundle\DependencyInjection\AcmeExampleExtension: - - Ibexa\Bundle\Core\DependencyInjection\Configuration\SiteAccessAware\ConfigurationProcessor - - Ibexa\Bundle\Core\DependencyInjection\Configuration\SiteAccessAware\ContextualizerInterface - Acme\ExampleBundle\DependencyInjection\Configuration: - - Ibexa\Bundle\Core\DependencyInjection\Configuration\SiteAccessAware\Configuration App\AI\REST\Input\Parser\TranscribeAudio: - - Ibexa\ConnectorAi\REST\Input\Parser\Action - Ibexa\Rest\Input\BaseParser - App\AI\REST\Output\Resolver\AudioTextResolver: - - Ibexa\ConnectorAi\REST\Output\ResolverInterface - App\AI\REST\Value\AudioText: - - Ibexa\ConnectorAi\REST\Value\RestActionResponse - App\Attribute\Percent\Form\PercentValueFormMapper: - - Ibexa\Bundle\ProductCatalog\Validator\Constraints\AttributeValue - App\Attribute\Percent\PercentOptionsFormMapper: - - Ibexa\Bundle\ProductCatalog\Validator\Constraints\AttributeDefinitionOptions - App\Attribute\Percent\Storage\PercentStorageConverter: - - Ibexa\ProductCatalog\Local\Persistence\Legacy\Attribute\Float\StorageSchema - App\Attribute\Percent\Storage\PercentStorageDefinition: - - Ibexa\ProductCatalog\Local\Persistence\Legacy\Attribute\Float\StorageSchema - App\AutomatedTranslation\AiClient: - - Ibexa\AutomatedTranslation\Exception\ClientNotConfiguredException App\AutomatedTranslation\ImageFieldEncoder: - Ibexa\Core\FieldType\Image\Value App\Block\Listener\MyBlockListener: @@ -73,7 +53,7 @@ deptrac: App\Controller\CustomCheckoutController: - Ibexa\Bundle\Core\Controller App\Controller\CustomController: - - Ibexa\Bundle\Core\Controller + - Ibexa\Core\MVC\Symfony\Security\Authorization\Attribute App\Controller\CustomFilterController: - Ibexa\Bundle\Core\Controller - Ibexa\Core\MVC\Symfony\View\ContentView @@ -158,8 +138,12 @@ deptrac: - Ibexa\Migration\ValueObject\Step\StepInterface App\Migrations\Step\ReplaceNameStepNormalizer: - Ibexa\Migration\ValueObject\Step\StepInterface + App\Notification\ListRenderer: + - Ibexa\Core\Notification\Renderer\NotificationRenderer + - Ibexa\Core\Notification\Renderer\TypedNotificationRendererInterface App\Notification\MyRenderer: - Ibexa\Core\Notification\Renderer\NotificationRenderer + - Ibexa\Core\Notification\Renderer\TypedNotificationRendererInterface App\OAuth\GoogleResourceOwnerMapper: - Ibexa\OAuth2Client\ResourceOwner\ResourceOwnerToExistingOrNewUserMapper App\QueryType\LatestContentQueryType: diff --git a/deptrac.yaml b/deptrac.yaml index 8e3a9ab831..cbeb214bad 100644 --- a/deptrac.yaml +++ b/deptrac.yaml @@ -13,15 +13,40 @@ deptrac: collectors: - type: classLike value: .*Ibexa\\Contracts\\.* - - name: IbexaNotContracts + - name: IbexaSiteAccessConfiguration + collectors: + - type: classLike + value: .*Ibexa\\Bundle\\Core\\DependencyInjection\\Configuration\\SiteAccessAware\\.* + - name: IbexaInternal + collectors: + - type: tagValueRegex + tag: '@internal' + - name: IbexaRest + collectors: + - type: classLike + value: .*Ibexa\\.*\\Rest\\.* + - name: IbexaConstraints + collectors: + - type: classLike + value: .*Ibexa\\.*\\Constraints\\.* + - name: IbexaNotAllowed collectors: - type: bool must: - type: classLike value: .*Ibexa\\.* must_not: - - type: classLike - value: .*Ibexa\\Contracts.* + - type: layer + value: IbexaContracts + - type: layer + value: IbexaSiteAccessConfiguration + - type: layer + value: IbexaRest + - type: layer + value: IbexaConstraints ruleset: CodeSamples: - IbexaContracts + - IbexaSiteAccessConfiguration + - IbexaRest + - IbexaConstraints diff --git a/docs/pim/create_custom_attribute_type.md b/docs/pim/create_custom_attribute_type.md index 6e2907a99b..3d13794ee5 100644 --- a/docs/pim/create_custom_attribute_type.md +++ b/docs/pim/create_custom_attribute_type.md @@ -120,6 +120,25 @@ Register the validator as a service and tag it with `ibexa.product_catalog.attri To ensure that values of the new attributes are stored correctly, you need to provide a storage converter and storage definition services. +### Database schema design + +The values are going to be stored within a table named `app_product_specification_attribute_percent`, in a column named `value`. + +=== "MySQL" + + ``` sql + CREATE TABLE app_product_specification_attribute_percent (id INT NOT NULL, value DOUBLE PRECISION DEFAULT NULL, INDEX app_product_specification_attribute_percent_value_idx (value), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_520_ci` ENGINE = InnoDB; + ALTER TABLE app_product_specification_attribute_percent ADD CONSTRAINT app_product_specification_attribute_percent_fk FOREIGN KEY (id) REFERENCES ibexa_product_specification_attribute (id) ON UPDATE CASCADE ON DELETE CASCADE; + ``` + +=== "PostgreSQL" + + ``` sql + CREATE TABLE app_product_specification_attribute_percent (id INT NOT NULL, value DOUBLE PRECISION DEFAULT NULL, PRIMARY KEY(id)); + CREATE INDEX app_product_specification_attribute_percent_value_idx ON app_product_specification_attribute_percent (value); + ALTER TABLE app_product_specification_attribute_percent ADD CONSTRAINT app_product_specification_attribute_percent_fk FOREIGN KEY (id) REFERENCES ibexa_product_specification_attribute (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE; + ``` + ### Storage converter Start by creating a `PercentStorageConverter` class, which implements `Ibexa\Contracts\ProductCatalog\Local\Attribute\StorageConverterInterface`. From 4eb93ee8f3cd9a6fba78135201dff92b059dd6f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Noco=C5=84?= Date: Wed, 1 Oct 2025 09:25:17 +0200 Subject: [PATCH 12/16] Fixed SQL --- docs/pim/create_custom_attribute_type.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/pim/create_custom_attribute_type.md b/docs/pim/create_custom_attribute_type.md index 3d13794ee5..71fd03254f 100644 --- a/docs/pim/create_custom_attribute_type.md +++ b/docs/pim/create_custom_attribute_type.md @@ -127,8 +127,12 @@ The values are going to be stored within a table named `app_product_specificatio === "MySQL" ``` sql - CREATE TABLE app_product_specification_attribute_percent (id INT NOT NULL, value DOUBLE PRECISION DEFAULT NULL, INDEX app_product_specification_attribute_percent_value_idx (value), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_520_ci` ENGINE = InnoDB; - ALTER TABLE app_product_specification_attribute_percent ADD CONSTRAINT app_product_specification_attribute_percent_fk FOREIGN KEY (id) REFERENCES ibexa_product_specification_attribute (id) ON UPDATE CASCADE ON DELETE CASCADE; + CREATE TABLE app_product_specification_attribute_percent ( + id INT NOT NULL, + value DOUBLE PRECISION DEFAULT NULL, + INDEX app_product_specification_attribute_percent_value_idx (value), + PRIMARY KEY (id) + ) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_520_ci` ENGINE = InnoDB; ``` === "PostgreSQL" From 6325d54197feca3a21f6f1ac071c9fefabcd2fa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Noco=C5=84?= Date: Wed, 1 Oct 2025 09:34:13 +0200 Subject: [PATCH 13/16] Expanded REST layer --- deptrac.baseline.yaml | 10 ---------- deptrac.yaml | 2 ++ 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/deptrac.baseline.yaml b/deptrac.baseline.yaml index e5d5ea812f..f8b3d5c3b6 100644 --- a/deptrac.baseline.yaml +++ b/deptrac.baseline.yaml @@ -1,7 +1,5 @@ deptrac: skip_violations: - App\AI\REST\Input\Parser\TranscribeAudio: - - Ibexa\Rest\Input\BaseParser App\AutomatedTranslation\ImageFieldEncoder: - Ibexa\Core\FieldType\Image\Value App\Block\Listener\MyBlockListener: @@ -153,14 +151,6 @@ deptrac: App\QueryType\OptionsBasedLatestContentQueryType: - Ibexa\Core\QueryType\OptionsResolverBasedQueryType - Ibexa\Core\QueryType\QueryType - App\Rest\Controller\DefaultController: - - Ibexa\Rest\Message - - Ibexa\Rest\Server\Controller - App\Rest\InputParser\GreetingInput: - - Ibexa\Rest\Input\BaseParser - App\Rest\ValueObjectVisitor\RestLocation: - - Ibexa\Rest\Server\Output\ValueObjectVisitor\RestLocation - - Ibexa\Rest\Server\Values\URLAliasRefList App\Search\Model\Suggestion\ProductSuggestion: - Ibexa\ProductCatalog\Local\Repository\Values\Product App\Security\Limitation\CustomLimitationType: diff --git a/deptrac.yaml b/deptrac.yaml index cbeb214bad..c46f25cfc7 100644 --- a/deptrac.yaml +++ b/deptrac.yaml @@ -25,6 +25,8 @@ deptrac: collectors: - type: classLike value: .*Ibexa\\.*\\Rest\\.* + - type: classLike + value: .*Ibexa\\Rest\\.* - name: IbexaConstraints collectors: - type: classLike From 2cd60a50ad6d54677ff80df97ddf9ad49e5aa1ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Noco=C5=84?= Date: Wed, 1 Oct 2025 09:55:22 +0200 Subject: [PATCH 14/16] Updated dependency --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 4ffc8dbf3e..2f68258b95 100644 --- a/composer.json +++ b/composer.json @@ -76,7 +76,7 @@ "ibexa/product-catalog-symbol-attribute": "~5.0.x-dev", "ibexa/core-search": "~5.0.x-dev", "ibexa/messenger": "~5.0.x-dev", - "qossmic/deptrac": "^2.0" + "deptrac/deptrac": "^3.0" }, "scripts": { "fix-cs": "php-cs-fixer fix --config=.php-cs-fixer.php -v --show-progress=dots", From 5b5a66ad9f318284c31eb400f089c31d6a5bc6aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Noco=C5=84?= Date: Wed, 1 Oct 2025 10:20:16 +0200 Subject: [PATCH 15/16] Regenerated baseline --- phpstan-baseline.neon | 6 ------ 1 file changed, 6 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index e1134208d9..17c4645013 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -234,12 +234,6 @@ parameters: count: 1 path: code_samples/back_office/content_type/src/EventListener/TextAnchorMenuTabListener.php - - - message: '#^Method App\\Security\\Limitation\\Mapper\\CustomLimitationFormMapper\:\:mapLimitationForm\(\) has parameter \$form with generic interface Symfony\\Component\\Form\\FormInterface but does not specify its types\: TData$#' - identifier: missingType.generics - count: 1 - path: code_samples/back_office/limitation/src/Controller/CustomController.php - - message: '#^Method App\\Security\\MyPolicyProvider\:\:getFiles\(\) return type has no value type specified in iterable type array\.$#' identifier: missingType.iterableValue From 65ea1aff66d9f26980929965ef592f0e4b325814 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Noco=C5=84?= Date: Thu, 2 Oct 2025 10:12:22 +0200 Subject: [PATCH 16/16] Reworked CI --- .github/workflows/build.yaml | 134 ---------------------------- .github/workflows/code_samples.yaml | 3 + 2 files changed, 3 insertions(+), 134 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index fd9e3c3dcb..438b94da7a 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -86,137 +86,3 @@ jobs: with: reporter: github-check filter_mode: added - - code-samples: - name: Validate code samples - runs-on: "ubuntu-22.04" - strategy: - matrix: - php: - - "8.3" - steps: - - uses: actions/checkout@v4 - - - name: Setup PHP Action - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - coverage: none - extensions: "pdo_sqlite, gd" - tools: cs2pr - - - name: Add composer keys for private packagist - run: | - composer config http-basic.updates.ibexa.co $SATIS_NETWORK_KEY $SATIS_NETWORK_TOKEN - composer config github-oauth.github.com $TRAVIS_GITHUB_TOKEN - env: - SATIS_NETWORK_KEY: ${{ secrets.SATIS_NETWORK_KEY }} - SATIS_NETWORK_TOKEN: ${{ secrets.SATIS_NETWORK_TOKEN }} - TRAVIS_GITHUB_TOKEN: ${{ secrets.TRAVIS_GITHUB_TOKEN }} - - - uses: ramsey/composer-install@v3 - with: - dependency-versions: highest - - - name: Run PHPStan analysis - run: composer phpstan - - - name: Deptrac - run: composer deptrac - - code-samples-inclusion-check: - name: Check code samples inclusion - runs-on: ubuntu-latest - if: github.event_name == 'pull_request' - permissions: - # Needed to manage the comment - pull-requests: write - - steps: - - name: List modified files - id: list - run: | - URL="https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files" - echo 'CODE_SAMPLES_CHANGE<> "$GITHUB_OUTPUT" - curl -s -X GET -G $URL | jq -r '.[] | .filename,.previous_filename' | grep '^code_samples/' | tr '\n' ' ' >> "$GITHUB_OUTPUT" - echo '' >> "$GITHUB_OUTPUT" - echo 'CODE_SAMPLES_CHANGE_DELIMITER' >> "$GITHUB_OUTPUT" - - - name: Checkout target branch (base_ref) - if: steps.list.outputs.CODE_SAMPLES_CHANGE != '' - uses: actions/checkout@v3 - with: - ref: ${{ github.base_ref }} - - name: Log target branch code_samples usage - if: steps.list.outputs.CODE_SAMPLES_CHANGE != '' - run: | - git fetch origin - git checkout origin/${{ github.head_ref }} -- tools/code_samples/code_samples_usage.php - php tools/code_samples/code_samples_usage.php ${{ steps.list.outputs.CODE_SAMPLES_CHANGE }} > $HOME/code_samples_usage_target.txt - - - name: Checkout source branch (head_ref) - if: steps.list.outputs.CODE_SAMPLES_CHANGE != '' - uses: actions/checkout@v3 - with: - ref: ${{ github.head_ref }} - - name: Log source branch code_samples usage - if: steps.list.outputs.CODE_SAMPLES_CHANGE != '' - run: php tools/code_samples/code_samples_usage.php ${{ steps.list.outputs.CODE_SAMPLES_CHANGE }} > $HOME/code_samples_usage_source.txt - - - name: Compare code_samples usages (diff --unified) - if: steps.list.outputs.CODE_SAMPLES_CHANGE != '' - # diff returns 1 if there is a difference, this is normal but seen as an error by the job. - continue-on-error: true - run: | - source_length=`wc -l < $HOME/code_samples_usage_source.txt` - target_length=`wc -l < $HOME/code_samples_usage_target.txt` - diff -U $(( source_length > target_length ? source_length : target_length )) $HOME/code_samples_usage_target.txt $HOME/code_samples_usage_source.txt > $HOME/code_samples_usage.diff - - name: Check for differences - id: diff - if: steps.list.outputs.CODE_SAMPLES_CHANGE != '' - run: | - echo "CODE_SAMPLES_DIFF=$(wc -l < $HOME/code_samples_usage.diff | xargs)" >> "$GITHUB_OUTPUT" - - name: Convert code_samples usages differences (diff2html) - if: steps.list.outputs.CODE_SAMPLES_CHANGE != '' && steps.diff.outputs.CODE_SAMPLES_DIFF != '0' - run: | - npm install -g diff2html-cli - diff2html -f html -s side -t 'code_samples/ changes report' --su hidden --fct false -o stdout -i file -- $HOME/code_samples_usage.diff > $HOME/code_samples_usage.diff.html - - name: Upload code_samples usages differences artifact - id: artifact - if: steps.list.outputs.CODE_SAMPLES_CHANGE != '' && steps.diff.outputs.CODE_SAMPLES_DIFF != '0' - uses: actions/upload-artifact@v4 - with: - name: code_samples_usage.diff.html - path: ~/code_samples_usage.diff.html - overwrite: true - - name: Convert code_samples usages for comment - if: steps.list.outputs.CODE_SAMPLES_CHANGE != '' && steps.diff.outputs.CODE_SAMPLES_DIFF != '0' - run: | - echo '# code_samples/ change report' >> code_samples_usage.diff.md - echo '' >> code_samples_usage.diff.md - php tools/code_samples/code_samples_usage_diff2html.php $HOME/code_samples_usage.diff >> code_samples_usage.diff.md - echo 'Download colorized diff' >> code_samples_usage.diff.md - - name: Find Comment - id: find-comment - uses: peter-evans/find-comment@v3 - with: - issue-number: ${{ github.event.pull_request.number }} - comment-author: 'github-actions[bot]' - body-includes: 'code_samples/ change report' - - name: Delete comment - if: steps.find-comment.outputs.comment-id != '' - uses: actions/github-script@v6 - with: - script: | - github.rest.issues.deleteComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: ${{ steps.find-comment.outputs.comment-id }} - }) - - name: Create comment - if: steps.list.outputs.CODE_SAMPLES_CHANGE != '' && steps.diff.outputs.CODE_SAMPLES_DIFF != '0' - uses: peter-evans/create-or-update-comment@v4 - with: - issue-number: ${{ github.event.pull_request.number }} - body-path: code_samples_usage.diff.md - edit-mode: replace diff --git a/.github/workflows/code_samples.yaml b/.github/workflows/code_samples.yaml index 63c601f4d9..5ab0edcf37 100644 --- a/.github/workflows/code_samples.yaml +++ b/.github/workflows/code_samples.yaml @@ -46,6 +46,9 @@ jobs: - name: Run PHPStan analysis run: composer phpstan + - name: Deptrac + run: composer deptrac + - name: Run rector run: vendor/bin/rector process --dry-run --ansi