From 76a219a3bd13ebf3652b0446daf16cc8fbbd4966 Mon Sep 17 00:00:00 2001 From: Alexander Schranz Date: Wed, 4 Sep 2024 10:15:56 +0200 Subject: [PATCH] Add symfony 7 support (#50) * Add support for Symfony 7 * Add support for Symfony 7 * Update retina example * Make sure it still runs on lowest --- .github/workflows/test-application.yaml | 21 +++- .php_cs.dist => .php-cs-fixer.dist.php | 30 +++-- composer.json | 16 +-- docs/image.md | 34 ++++++ phpstan-baseline.neon | 151 ++++++++++++++++++++++++ phpstan.neon | 3 +- src/IconExtension.php | 4 +- src/ImageExtension.php | 2 +- src/Node/PortalNode.php | 2 +- src/UrlExtension.php | 24 ++-- tests/Unit/ImageExtensionTest.php | 28 ++++- tests/Unit/UrlExtensionTest.php | 2 +- 12 files changed, 272 insertions(+), 45 deletions(-) rename .php_cs.dist => .php-cs-fixer.dist.php (54%) create mode 100644 phpstan-baseline.neon diff --git a/.github/workflows/test-application.yaml b/.github/workflows/test-application.yaml index 1493235..08b3c6d 100644 --- a/.github/workflows/test-application.yaml +++ b/.github/workflows/test-application.yaml @@ -34,9 +34,15 @@ jobs: env: SYMFONY_DEPRECATIONS_HELPER: weak + - php-version: '8.3' + dependency-versions: 'highest' + tools: 'composer:v2' + env: + SYMFONY_DEPRECATIONS_HELPER: weak + steps: - name: Checkout project - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Install and configure PHP uses: shivammathur/setup-php@v2 @@ -44,8 +50,13 @@ jobs: php-version: ${{ matrix.php-version }} tools: ${{ matrix.tools }} + - name: Remove Lint Tools + # These tools are not required to run tests, so we are removing them to improve dependency resolving and + # testing lowest versions. + run: composer remove "*php-cs-fixer*" "*phpstan*" "*rector*" --dev --no-update + - name: Install composer dependencies - uses: ramsey/composer-install@v1 + uses: ramsey/composer-install@v2 with: dependency-versions: ${{ matrix.dependency-versions }} @@ -59,16 +70,16 @@ jobs: steps: - name: Checkout project - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Install and configure PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.0 + php-version: 8.3 tools: composer - name: Install composer dependencies - uses: ramsey/composer-install@v1 + uses: ramsey/composer-install@v2 - name: Lint code run: composer lint diff --git a/.php_cs.dist b/.php-cs-fixer.dist.php similarity index 54% rename from .php_cs.dist rename to .php-cs-fixer.dist.php index d7b95fb..bf00999 100644 --- a/.php_cs.dist +++ b/.php-cs-fixer.dist.php @@ -9,8 +9,12 @@ with this source code in the file LICENSE. EOF; -return PhpCsFixer\Config::create() - ->setRiskyAllowed(true) +$finder = PhpCsFixer\Finder::create() + ->exclude(['vendor']) + ->in(__DIR__); + +$config = new PhpCsFixer\Config(); +$config->setRiskyAllowed(true) ->setRules([ '@Symfony' => true, '@Symfony:risky' => true, @@ -18,9 +22,7 @@ 'concat_space' => ['spacing' => 'one'], 'array_syntax' => ['syntax' => 'short'], 'phpdoc_align' => false, - 'class_definition' => [ - 'multiLineExtendsEachSingleLine' => true, - ], + 'class_definition' => false, 'linebreak_after_opening_tag' => true, 'declare_strict_types' => true, 'mb_str_functions' => false, @@ -35,9 +37,17 @@ 'strict_comparison' => true, 'strict_param' => true, 'header_comment' => ['header' => $header], + 'native_constant_invocation' => true, + 'native_function_casing' => true, + 'native_function_invocation' => false, + 'get_class_to_class_keyword' => false, // should be enabled as soon as support for php < 8 is dropped + 'nullable_type_declaration_for_default_null_value' => true, + 'no_null_property_initialization' => false, + 'fully_qualified_strict_types' => false, + 'new_with_parentheses' => true, + 'modernize_strpos' => false, + 'trailing_comma_in_multiline' => ['after_heredoc' => true, 'elements' => ['array_destructuring', 'arrays']], ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->exclude('vendor') - ->in(__DIR__) - ); + ->setFinder($finder); + +return $config; diff --git a/composer.json b/composer.json index 7666dce..3ae51b8 100644 --- a/composer.json +++ b/composer.json @@ -14,18 +14,18 @@ "twig/twig": "^1.38 || ^2.7 || ^3.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.11", - "jangregor/phpstan-prophecy": "^0.8", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", + "php-cs-fixer/shim": "^3.64", + "jangregor/phpstan-prophecy": "^1.0", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", "phpunit/phpunit": "^7.5 || ^8.5", "symfony/intl": "^2.8 || ^3.0 || ^4.0 || ^5.0 || ^6.0", - "symfony/property-access": "^2.8 || ^3.0 || ^4.0 || ^5.0 || ^6.0", - "thecodingmachine/phpstan-strict-rules": "^0.12" + "symfony/property-access": "^2.8 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "thecodingmachine/phpstan-strict-rules": "^1.0" }, "conflict": { - "symfony/intl": ">=7.0", - "symfony/property-access": ">=7.0" + "symfony/intl": ">=8.0", + "symfony/property-access": ">=8.0" }, "suggest": { "symfony/property-access": "The ImageExtension requires the symfony/property-access service.", diff --git a/docs/image.md b/docs/image.md index 30a5a8d..3a8b4b6 100644 --- a/docs/image.md +++ b/docs/image.md @@ -130,6 +130,26 @@ This could be: ) }} ``` +Retina example: + +```twig +Title +``` + +This could be: + +```twig +{{ get_image(image, + { + src: '452x452', + srcset: '452x452 1x, 452x452@2x 2x', + } +) }} +``` + ##### 5. Lazy images > See also 6. Native lazy loading for a modern implementation. @@ -197,6 +217,20 @@ services: loading: 'lazy' ``` +If you are setting the default to lazy which is recommended you maybe want the +first image of hero slider be loaded immediately this can be achieved via +setting `loading` to `null` for the first element: + +```twig +{% for image in images %} + {{ get_image(image, { + src: '452x452', + srcset: '452x452 1x, 452x452@2x 2x', + loading: loop.first ? null : 'lazy', + }) }} +{% endfor %} +``` + ##### 7. Webp Support If your server supports converting images to webp you can automatically enable webp diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000..56204f5 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,151 @@ +parameters: + ignoreErrors: + - + message: "#^Cannot access offset 'className' on mixed\\.$#" + count: 2 + path: src/IconExtension.php + + - + message: "#^Cannot access offset 'classPrefix' on mixed\\.$#" + count: 2 + path: src/IconExtension.php + + - + message: "#^Cannot access offset 'classSuffix' on mixed\\.$#" + count: 2 + path: src/IconExtension.php + + - + message: "#^Cannot access offset 'path' on mixed\\.$#" + count: 1 + path: src/IconExtension.php + + - + message: "#^Cannot access offset 'type' on mixed\\.$#" + count: 1 + path: src/IconExtension.php + + - + message: "#^Method Sulu\\\\Twig\\\\Extensions\\\\IconExtension\\:\\:getIconSet\\(\\) should return array but returns mixed\\.$#" + count: 1 + path: src/IconExtension.php + + - + message: "#^Parameter \\#3 \\.\\.\\.\\$values of function sprintf expects bool\\|float\\|int\\|string\\|null, mixed given\\.$#" + count: 1 + path: src/IconExtension.php + + - + message: "#^Parameter \\#4 \\.\\.\\.\\$values of function sprintf expects bool\\|float\\|int\\|string\\|null, mixed given\\.$#" + count: 1 + path: src/IconExtension.php + + - + message: "#^Parameter \\#6 \\.\\.\\.\\$values of function sprintf expects bool\\|float\\|int\\|string\\|null, mixed given\\.$#" + count: 1 + path: src/IconExtension.php + + - + message: "#^Cannot access offset 'height' on mixed\\.$#" + count: 1 + path: src/ImageExtension.php + + - + message: "#^Cannot access offset 'media' on mixed\\.$#" + count: 1 + path: src/ImageExtension.php + + - + message: "#^Cannot access offset 'srcset' on mixed\\.$#" + count: 1 + path: src/ImageExtension.php + + - + message: "#^Cannot access offset 'type' on mixed\\.$#" + count: 1 + path: src/ImageExtension.php + + - + message: "#^Cannot access offset 'width' on mixed\\.$#" + count: 1 + path: src/ImageExtension.php + + - + message: "#^Method Sulu\\\\Twig\\\\Extensions\\\\ImageExtension\\:\\:getMimeType\\(\\) should return string but returns mixed\\.$#" + count: 1 + path: src/ImageExtension.php + + - + message: "#^Offset 'filename' on array\\{dirname\\?\\: string, basename\\: string, extension\\?\\: string, filename\\: string\\} on left side of \\?\\? always exists and is not nullable\\.$#" + count: 1 + path: src/ImageExtension.php + + - + message: "#^Parameter \\#1 \\$array of function reset expects array\\|object, mixed given\\.$#" + count: 1 + path: src/ImageExtension.php + + - + message: "#^Parameter \\#1 \\$objectOrArray of method Symfony\\\\Component\\\\PropertyAccess\\\\PropertyAccessor\\:\\:getValue\\(\\) expects array\\|object, mixed given\\.$#" + count: 6 + path: src/ImageExtension.php + + - + message: "#^Parameter \\#1 \\$objectOrArray of method Symfony\\\\Component\\\\PropertyAccess\\\\PropertyAccessor\\:\\:isReadable\\(\\) expects array\\|object, mixed given\\.$#" + count: 3 + path: src/ImageExtension.php + + - + message: "#^Parameter \\#1 \\$path of function pathinfo expects string, mixed given\\.$#" + count: 1 + path: src/ImageExtension.php + + - + message: "#^Parameter \\#1 \\$srcsets of method Sulu\\\\Twig\\\\Extensions\\\\ImageExtension\\:\\:addExtension\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/ImageExtension.php + + - + message: "#^Parameter \\#1 \\$thumbnails of method Sulu\\\\Twig\\\\Extensions\\\\ImageExtension\\:\\:getLazyThumbnails\\(\\) expects array\\, mixed given\\.$#" + count: 1 + path: src/ImageExtension.php + + - + message: "#^Parameter \\#1 \\.\\.\\.\\$arrays of function array_merge expects array, mixed given\\.$#" + count: 1 + path: src/ImageExtension.php + + - + message: "#^Parameter \\#2 \\$attributes of method Sulu\\\\Twig\\\\Extensions\\\\ImageExtension\\:\\:createTag\\(\\) expects array\\, array given\\.$#" + count: 1 + path: src/ImageExtension.php + + - + message: "#^Parameter \\#2 \\$attributes of method Sulu\\\\Twig\\\\Extensions\\\\ImageExtension\\:\\:createTag\\(\\) expects array\\, mixed given\\.$#" + count: 1 + path: src/ImageExtension.php + + - + message: "#^Parameter \\#2 \\.\\.\\.\\$arrays of function array_merge expects array, mixed given\\.$#" + count: 1 + path: src/ImageExtension.php + + - + message: "#^Parameter \\#3 \\$thumbnails of method Sulu\\\\Twig\\\\Extensions\\\\ImageExtension\\:\\:createTag\\(\\) expects array\\, mixed given\\.$#" + count: 3 + path: src/ImageExtension.php + + - + message: "#^Parameter \\#5 \\$additionalTypes of method Sulu\\\\Twig\\\\Extensions\\\\ImageExtension\\:\\:createImage\\(\\) expects array\\, array given\\.$#" + count: 2 + path: src/ImageExtension.php + + - + message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#" + count: 1 + path: src/PortalExtension.php + + - + message: "#^Cannot access an offset on mixed\\.$#" + count: 1 + path: src/PortalExtension.php diff --git a/phpstan.neon b/phpstan.neon index 7b1fe07..68b5cd0 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,4 +1,5 @@ includes: + - phpstan-baseline.neon - vendor/jangregor/phpstan-prophecy/extension.neon - vendor/phpstan/phpstan-phpunit/extension.neon - vendor/phpstan/phpstan-phpunit/rules.neon @@ -10,7 +11,7 @@ parameters: - src - tests inferPrivatePropertyTypeFromConstructor: true - excludes_analyse: + excludePaths: - %currentWorkingDirectory%/vendor/* ignoreErrors: - '#Call to an undefined static method Symfony\\Component\\Intl\\Intl#' diff --git a/src/IconExtension.php b/src/IconExtension.php index a29b048..af88857 100644 --- a/src/IconExtension.php +++ b/src/IconExtension.php @@ -21,8 +21,8 @@ */ class IconExtension extends AbstractExtension { - const ICON_SET_TYPE_SVG = 'svg'; - const ICON_SET_TYPE_FONT = 'font'; + public const ICON_SET_TYPE_SVG = 'svg'; + public const ICON_SET_TYPE_FONT = 'font'; /** * @var mixed[] diff --git a/src/ImageExtension.php b/src/ImageExtension.php index 361267e..1346501 100644 --- a/src/ImageExtension.php +++ b/src/ImageExtension.php @@ -100,7 +100,7 @@ public function __construct( array $defaultAttributes = [], array $defaultAdditionalTypes = [], bool $aspectRatio = false, - array $imageFormatConfiguration = null + ?array $imageFormatConfiguration = null ) { if (null !== $placeholderPath) { $this->placeholderPath = rtrim($placeholderPath, '/') . '/'; diff --git a/src/Node/PortalNode.php b/src/Node/PortalNode.php index d9a1dbf..3df41a5 100644 --- a/src/Node/PortalNode.php +++ b/src/Node/PortalNode.php @@ -24,7 +24,7 @@ final class PortalNode extends Node /** * @param Node $body */ - public function __construct(string $name, Node $body, int $lineno, string $tag = null) + public function __construct(string $name, Node $body, int $lineno, ?string $tag = null) { parent::__construct(['body' => $body], ['name' => $name], $lineno, $tag); } diff --git a/src/UrlExtension.php b/src/UrlExtension.php index 0f4bb48..1e90b43 100644 --- a/src/UrlExtension.php +++ b/src/UrlExtension.php @@ -21,18 +21,18 @@ */ class UrlExtension extends AbstractExtension { - const DEFAULT_SCHEME = 'http'; - - const SCHEME = 'scheme'; - const USER = 'user'; - const PASS = 'pass'; - const HOST = 'host'; - const PORT = 'port'; - const PATH = 'path'; - const QUERY = 'query'; - const FRAGMENT = 'fragment'; - - const DEFAULT_FLAGS = [ + public const DEFAULT_SCHEME = 'http'; + + public const SCHEME = 'scheme'; + public const USER = 'user'; + public const PASS = 'pass'; + public const HOST = 'host'; + public const PORT = 'port'; + public const PATH = 'path'; + public const QUERY = 'query'; + public const FRAGMENT = 'fragment'; + + public const DEFAULT_FLAGS = [ self::SCHEME => true, self::USER => true, self::PASS => true, diff --git a/tests/Unit/ImageExtensionTest.php b/tests/Unit/ImageExtensionTest.php index 3bebc9c..d76b475 100644 --- a/tests/Unit/ImageExtensionTest.php +++ b/tests/Unit/ImageExtensionTest.php @@ -509,9 +509,9 @@ public function testIgnoreAdditionalTypesForSvg(): void $imageExtension = new ImageExtension(null, [], ['webp' => 'image/webp']); $this->assertSame( - 'Title', + 'Title', $imageExtension->getImage($this->svgImage, [ 'src' => 'sulu-100x100', ]) @@ -556,6 +556,26 @@ public function testAdditionalTypesWithSrcSet(): void ); } + public function testAdditionalTypesWithSrcSetWith1x(): void + { + $imageExtension = new ImageExtension(null, [], []); + + $this->assertSame( + '' . + '' . + 'Title' . + '', + $imageExtension->getImage($this->image, [ + 'src' => 'sulu-100x100', + 'srcset' => 'sulu-100x100 1x, sulu-100x100@2x 2x', + ], [], ['webp' => 'image/webp']) + ); + } + public function testAdditionalLazyComplexPictureTag(): void { $imageExtension = new ImageExtension('/lazy', [], ['webp' => 'image/webp']); @@ -645,7 +665,7 @@ public function testAspectRatioNoProperties(): void [ 'thumbnails' => [ '200x100-inset' => '/uploads/media/200x100-inset/01/image.jpg?v=1-0', - '200x100-inset' . '.webp' => '/uploads/media/200x100-inset/01/image.webp?v=1-0', + '200x100-inset.webp' => '/uploads/media/200x100-inset/01/image.webp?v=1-0', ], ] ); diff --git a/tests/Unit/UrlExtensionTest.php b/tests/Unit/UrlExtensionTest.php index 0cf4c91..3a10cf3 100644 --- a/tests/Unit/UrlExtensionTest.php +++ b/tests/Unit/UrlExtensionTest.php @@ -18,7 +18,7 @@ class UrlExtensionTest extends TestCase { - const URL = 'https://john.doe:hidden@example.org:8080/admin?resource=pages&limit=20#1234'; + private const URL = 'https://john.doe:hidden@example.org:8080/admin?resource=pages&limit=20#1234'; /** * @var UrlExtension