diff --git a/.github/workflows/build-docs.yaml b/.github/workflows/build-docs.yaml index a9f8084..5c7d617 100644 --- a/.github/workflows/build-docs.yaml +++ b/.github/workflows/build-docs.yaml @@ -2,7 +2,7 @@ name: Build docs on: push: - branches: ['5.x'] + branches: ['6.x'] jobs: deploy: diff --git a/.github/workflows/coding-standards.yaml b/.github/workflows/coding-standards.yaml index 6e5487b..9385578 100644 --- a/.github/workflows/coding-standards.yaml +++ b/.github/workflows/coding-standards.yaml @@ -2,9 +2,9 @@ name: Coding Standards on: pull_request: - branches: ['5.x', '4.x', '3.x'] + branches: ["*.x"] push: - branches: ['5.x', '4.x', '3.x'] + branches: ["*.x"] jobs: easy-coding-standard: @@ -14,7 +14,7 @@ jobs: strategy: matrix: php-version: - - '8.4' + - '8.5' steps: - name: Checkout code diff --git a/.github/workflows/continuous-integration.yaml b/.github/workflows/continuous-integration.yaml index 272530d..01d3e35 100644 --- a/.github/workflows/continuous-integration.yaml +++ b/.github/workflows/continuous-integration.yaml @@ -2,9 +2,9 @@ name: Continuous Integration on: pull_request: - branches: ['5.x', '4.x', '3.x'] + branches: ["*.x"] push: - branches: ['5.x', '4.x', '3.x'] + branches: ["*.x"] jobs: phpunit: @@ -17,20 +17,14 @@ jobs: strategy: matrix: php-version: - - '8.2' - - '8.3' - '8.4' - '8.5' symfony-version: - - '6.4.*' - - '7.3.*' - - '7.4.*@dev' + - '7.4.*' + - '8.0.*' include: - - php-version: '8.4' - symfony-version: '8.0.*@dev' - continue-on-error: true - php-version: '8.5' - symfony-version: '8.0.*@dev' + symfony-version: '8.1.*@dev' continue-on-error: true steps: @@ -61,4 +55,4 @@ jobs: - name: Upload to Codecov uses: codecov/codecov-action@v5 - if: matrix.php-version == '8.2' && matrix.symfony-version == '6.4.*' + if: matrix.php-version == '8.4' && matrix.symfony-version == '7.4.*' diff --git a/.github/workflows/static-analysis.yaml b/.github/workflows/static-analysis.yaml index 442fabe..565920b 100644 --- a/.github/workflows/static-analysis.yaml +++ b/.github/workflows/static-analysis.yaml @@ -2,9 +2,9 @@ name: Static Analysis on: pull_request: - branches: ['5.x', '4.x', '3.x'] + branches: ["*.x"] push: - branches: ['5.x', '4.x', '3.x'] + branches: ["*.x"] jobs: phpstan: @@ -14,7 +14,7 @@ jobs: strategy: matrix: php-version: - - '8.4' + - '8.5' steps: - name: Checkout code diff --git a/CHANGELOG-6.x.md b/CHANGELOG-6.x.md new file mode 100644 index 0000000..e174405 --- /dev/null +++ b/CHANGELOG-6.x.md @@ -0,0 +1,6 @@ +6.0.0 +----- + +* Drop support for PHP < 8.4 +* Drop support for Symfony < 7.4 & Twig < 3.23 +* Require leapt/core-bundle 6 diff --git a/README.md b/README.md index 8cad50b..646bf8e 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,10 @@ Leapt IM Bundle =============== [![Package version](https://img.shields.io/packagist/v/leapt/im-bundle.svg?style=flat-square)](https://packagist.org/packages/leapt/im-bundle) -[![Build Status](https://img.shields.io/github/actions/workflow/status/leapt/im-bundle/continuous-integration.yaml?branch=5.x&style=flat-square)](https://github.com/leapt/im-bundle/actions?query=workflow%3A%22Continuous+Integration%22) -![PHP Version](https://img.shields.io/packagist/php-v/leapt/im-bundle/v5.0.0?branch=5.x&style=flat-square) +[![Build Status](https://img.shields.io/github/actions/workflow/status/leapt/im-bundle/continuous-integration.yaml?branch=6.x&style=flat-square)](https://github.com/leapt/im-bundle/actions?query=workflow%3A%22Continuous+Integration%22) +![PHP Version](https://img.shields.io/packagist/php-v/leapt/im-bundle/v6.0.0?branch=6.x&style=flat-square) [![License](https://img.shields.io/badge/license-MIT-red.svg?style=flat-square)](LICENSE) -[![Code coverage](https://img.shields.io/codecov/c/github/leapt/im-bundle?style=flat-square)](https://codecov.io/gh/leapt/im-bundle/branch/5.x) +[![Code coverage](https://img.shields.io/codecov/c/github/leapt/im-bundle?style=flat-square)](https://codecov.io/gh/leapt/im-bundle/branch/6.x) This bundle is a fork of the SnowcapImBundle. @@ -24,21 +24,22 @@ See the [Documentation and examples](https://im-bundle.leapt.dev/) Versions & dependencies ----------------------- -The current version (5.x) of the bundle works with Symfony 6.4, 7.0+ & 8.0+. +The current version (6.x) of the bundle works with Symfony 7.4 & 8.0+. The project follows SemVer. Only the last major version is maintained. -You can check the [changelog](CHANGELOG-5.x.md) for version 5 and the [upgrade document](UPGRADE-5.x.md) when upgrading -from 4.x bundle version. - -| ImBundle version | Symfony version | PHP version -|------------------|--------------------------| ----------- -| 5.x | ^6.4 \|\| ^7.0 | ^8.2 -| 4.x | ^5.4 \|\| ^6.0 | ^8.0 -| 3.x | ^4.4 \|\| ^5.0 | ^7.2 \|\| ^8.0 -| 2.1+ | ^3.3 \|\| ^4.0 | >=5.5 -| 2.0, < 2.1 | ^2.7 \|\| ^3.0 \|\| ^4.0 | >=5.4 -| 1.x | ^2.7 | >=5.3.3 +You can check the [changelog](CHANGELOG-6.x.md) for version 5 and the [upgrade document](UPGRADE-6.x.md) when upgrading +from 5.x bundle version. + +| ImBundle version | Symfony version | PHP version | +|------------------|--------------------------|----------------| +| 6.x | ^7.4 \|\| ^8.0 | ^8.4 | +| 5.x | ^6.4 \|\| ^7.0 \|\| ^8.0 | ^8.2 | +| 4.x | ^5.4 \|\| ^6.0 | ^8.0 | +| 3.x | ^4.4 \|\| ^5.0 | ^7.2 \|\| ^8.0 | +| 2.1+ | ^3.3 \|\| ^4.0 | >=5.5 | +| 2.0, < 2.1 | ^2.7 \|\| ^3.0 \|\| ^4.0 | >=5.4 | +| 1.x | ^2.7 | >=5.3.3 | Contributing ------------ diff --git a/UPGRADE-6.x.md b/UPGRADE-6.x.md new file mode 100644 index 0000000..3e83e23 --- /dev/null +++ b/UPGRADE-6.x.md @@ -0,0 +1,6 @@ +Upgrade from 5.x to 6.x +----------------------- + +* Drop support for PHP < 8.4 +* Drop support for Symfony < 7.4 & Twig < 3.23 +* Require leapt/core-bundle 6 diff --git a/composer.json b/composer.json index 6a7a440..9ad376f 100644 --- a/composer.json +++ b/composer.json @@ -25,27 +25,27 @@ ], "minimum-stability": "stable", "require": { - "php": "^8.2", + "php": "^8.4", "ext-curl": "*", - "doctrine/orm": "^2.5 || ^3.0", - "leapt/core-bundle": "^5.0", - "symfony/css-selector": "^6.4 || ^7.0 || ^8.0", - "symfony/console": "^6.4 || ^7.0 || ^8.0", - "symfony/dom-crawler": "^6.4 || ^7.0 || ^8.0", - "symfony/filesystem": "^6.4 || ^7.0 || ^8.0", - "symfony/finder": "^6.4 || ^7.0 || ^8.0", - "symfony/form": "^6.4 || ^7.0 || ^8.0", - "symfony/framework-bundle": "^6.4 || ^7.0 || ^8.0", - "symfony/process": "^6.4 || ^7.0 || ^8.0", - "twig/twig": "^3.0" + "doctrine/orm": "^3.0", + "leapt/core-bundle": "^6.0", + "symfony/css-selector": "^7.4 || ^8.0", + "symfony/console": "^7.4 || ^8.0", + "symfony/dom-crawler": "^7.4 || ^8.0", + "symfony/filesystem": "^7.4 || ^8.0", + "symfony/finder": "^7.4 || ^8.0", + "symfony/form": "^7.4 || ^8.0", + "symfony/framework-bundle": "^7.4 || ^8.0", + "symfony/process": "^7.4 || ^8.0", + "twig/twig": "^3.23" }, "require-dev": { "mikey179/vfsstream": "^1.6.11", - "phpstan/phpstan": "^2.1.22", - "phpstan/phpstan-deprecation-rules": "^2.0.3", - "phpunit/phpunit": "^11.5.35", - "symfony/browser-kit": "^6.4 || ^7.0 || ^8.0", - "symplify/easy-coding-standard": "^12.5.24" + "phpstan/phpstan": "^2.1.40", + "phpstan/phpstan-deprecation-rules": "^2.0.4", + "phpunit/phpunit": "^13.0.5", + "symfony/browser-kit": "^7.4 || ^8.0", + "symplify/easy-coding-standard": "^13.0.4" }, "scripts": { "ci": [ @@ -53,9 +53,9 @@ "@phpstan", "vendor/bin/phpunit --colors=auto" ], - "cs:dry": "vendor/bin/ecs", - "cs:fix": "vendor/bin/ecs --fix", - "phpstan": "vendor/bin/phpstan analyse --ansi" + "cs:dry": "@php vendor/bin/ecs", + "cs:fix": "@php vendor/bin/ecs --fix", + "phpstan": "@php vendor/bin/phpstan analyse --ansi" }, "autoload": { "psr-4": { diff --git a/docs/index.md b/docs/index.md index 3e3ab58..662bad7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -11,7 +11,7 @@ It allows you to use all the convert/mogrify power, from your controllers, servi - [Extra information](extra.md) If you find a bug or want to add a functionality, -[please create an issue or a pull request on Github](https://github.com/leapt/im-bundle)! +[please create an issue or a pull request on GitHub](https://github.com/leapt/im-bundle)! ## License diff --git a/docs/installation.md b/docs/installation.md index 0764a55..bca0b79 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -3,9 +3,9 @@ ## Requirements * You need to have the ImageMagick binaries available (convert & mogrify) -* You need to have a cache folder in your web dir, writeable by the webserver -* Symfony 6.4/7.0+/8.0+ -* PHP 8.2+ +* You need to have a cache folder in your public dir, writeable by the webserver +* Symfony 7.4/8.0+ +* PHP 8.4+ ## Install steps diff --git a/mkdocs.yml b/mkdocs.yml index 326ccee..a0cf3b3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -3,7 +3,7 @@ site_description: Documentation for leapt/im-bundle repo_name: leapt/im-bundle repo_url: https://github.com/leapt/im-bundle -edit_uri: https://github.com/leapt/im-bundle/edit/4.x/docs/ +edit_uri: https://github.com/leapt/im-bundle/edit/6.x/docs/ theme: name: material diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index f6dafc0..ff74534 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,118 +1,124 @@ parameters: ignoreErrors: - - message: '#^Method Leapt\\ImBundle\\Doctrine\\Mapping\\Mogrify\:\:__construct\(\) has parameter \$params with no value type specified in iterable type array\.$#' + rawMessage: 'Method Leapt\ImBundle\Doctrine\Mapping\Mogrify::__construct() has parameter $params with no value type specified in iterable type array.' identifier: missingType.iterableValue count: 1 path: src/Doctrine/Mapping/Mogrify.php - - message: ''' - #^Call to deprecated method getReflectionProperties\(\) of class Doctrine\\ORM\\Mapping\\ClassMetadata\: - Use getPropertyAccessors\(\) instead\.$# + rawMessage: ''' + Call to deprecated method getReflectionProperties() of class Doctrine\ORM\Mapping\ClassMetadata: + Use getPropertyAccessors() instead. ''' identifier: method.deprecated count: 1 path: src/Listener/MogrifySubscriber.php - - message: '#^Call to function method_exists\(\) with Doctrine\\ORM\\Mapping\\ClassMetadata\ and ''getPropertyAccessors'' will always evaluate to true\.$#' + rawMessage: 'Call to function method_exists() with Doctrine\ORM\Mapping\ClassMetadata and ''getPropertyAccessors'' will always evaluate to true.' identifier: function.alreadyNarrowedType count: 1 path: src/Listener/MogrifySubscriber.php - - message: '#^Call to method getUnderlyingReflector\(\) of internal interface Doctrine\\ORM\\Mapping\\PropertyAccessors\\PropertyAccessor from outside its root namespace Doctrine\.$#' + rawMessage: 'Call to method getUnderlyingReflector() of internal interface Doctrine\ORM\Mapping\PropertyAccessors\PropertyAccessor from outside its root namespace Doctrine.' identifier: method.internalInterface count: 5 path: src/Listener/MogrifySubscriber.php - - message: '#^Cannot access property \$name on ReflectionProperty\|string\.$#' + rawMessage: Cannot access property $name on ReflectionProperty|string. identifier: property.nonObject count: 1 path: src/Listener/MogrifySubscriber.php - - message: '#^Method Leapt\\ImBundle\\Listener\\MogrifySubscriber\:\:getFiles\(\) return type has no value type specified in iterable type array\.$#' + rawMessage: 'Method Leapt\ImBundle\Listener\MogrifySubscriber::getFiles() return type has no value type specified in iterable type array.' identifier: missingType.iterableValue count: 1 path: src/Listener/MogrifySubscriber.php - - message: '#^Parameter \#1 \$format of method Leapt\\ImBundle\\Manager\:\:mogrify\(\) expects array\|string, ReflectionProperty\|string given\.$#' + rawMessage: 'Parameter #1 $format of method Leapt\ImBundle\Manager::mogrify() expects array|string, ReflectionProperty|string given.' identifier: argument.type count: 1 path: src/Listener/MogrifySubscriber.php - - message: '#^Property Leapt\\ImBundle\\Listener\\MogrifySubscriber\:\:\$config type has no value type specified in iterable type array\.$#' + rawMessage: Property Leapt\ImBundle\Listener\MogrifySubscriber::$config type has no value type specified in iterable type array. identifier: missingType.iterableValue count: 1 path: src/Listener/MogrifySubscriber.php - - message: '#^Strict comparison using \!\=\= between null and ReflectionProperty will always evaluate to true\.$#' + rawMessage: 'Strict comparison using !== between null and ReflectionProperty will always evaluate to true.' identifier: notIdentical.alwaysTrue count: 1 path: src/Listener/MogrifySubscriber.php - - message: '#^Method Leapt\\ImBundle\\Manager\:\:__construct\(\) has parameter \$formats with no value type specified in iterable type array\.$#' + rawMessage: 'Method Leapt\ImBundle\Manager::__construct() has parameter $formats with no value type specified in iterable type array.' identifier: missingType.iterableValue count: 1 path: src/Manager.php - - message: '#^Method Leapt\\ImBundle\\Manager\:\:convert\(\) has parameter \$format with no value type specified in iterable type array\.$#' + rawMessage: 'Method Leapt\ImBundle\Manager::convert() has parameter $format with no value type specified in iterable type array.' identifier: missingType.iterableValue count: 1 path: src/Manager.php - - message: '#^Method Leapt\\ImBundle\\Manager\:\:getCacheContent\(\) should return string but returns string\|false\.$#' + rawMessage: 'Method Leapt\ImBundle\Manager::getCacheContent() should return string but returns string|false.' identifier: return.type count: 1 path: src/Manager.php - - message: '#^Method Leapt\\ImBundle\\Manager\:\:mogrify\(\) has parameter \$format with no value type specified in iterable type array\.$#' + rawMessage: 'Method Leapt\ImBundle\Manager::mogrify() has parameter $format with no value type specified in iterable type array.' identifier: missingType.iterableValue count: 1 path: src/Manager.php - - message: '#^Parameter \#3 \$length of function substr expects int\|null, int\<0, max\>\|false given\.$#' + rawMessage: 'Parameter #3 $length of function substr expects int|null, int<0, max>|false given.' identifier: argument.type count: 1 path: src/Manager.php - - message: '#^Method Leapt\\ImBundle\\Tests\\LeaptImTestingKernel\:\:configureContainer\(\) is unused\.$#' + rawMessage: 'Method Leapt\ImBundle\Tests\LeaptImTestingKernel::configureContainer() is unused.' identifier: method.unused count: 1 path: tests/LeaptImTestingKernel.php - - message: '#^Method Leapt\\ImBundle\\Tests\\LeaptImTestingKernel\:\:configureRoutes\(\) is unused\.$#' + rawMessage: 'Method Leapt\ImBundle\Tests\LeaptImTestingKernel::configureRoutes() is unused.' identifier: method.unused count: 1 path: tests/LeaptImTestingKernel.php - - message: '#^Property Leapt\\ImBundle\\Tests\\ManagerTest\:\:\$root is never read, only written\.$#' + rawMessage: 'Method Leapt\ImBundle\Tests\LeaptImTestingKernel::getConfigDir() is unused.' + identifier: method.unused + count: 1 + path: tests/LeaptImTestingKernel.php + + - + rawMessage: 'Property Leapt\ImBundle\Tests\ManagerTest::$root is never read, only written.' identifier: property.onlyWritten count: 1 path: tests/ManagerTest.php - - message: '#^Unreachable statement \- code above always terminates\.$#' + rawMessage: Unreachable statement - code above always terminates. identifier: deadCode.unreachable count: 3 path: tests/ManagerTest.php - - message: '#^Method Leapt\\ImBundle\\Tests\\Mock\\Process\:\:run\(\) overrides @final method Symfony\\Component\\Process\\Process\:\:run\(\)\.$#' + rawMessage: 'Method Leapt\ImBundle\Tests\Mock\Process::run() overrides @final method Symfony\Component\Process\Process::run().' identifier: method.parentMethodFinalByPhpDoc count: 1 path: tests/Mock/Process.php diff --git a/src/Manager.php b/src/Manager.php index 7e0c80f..f08c748 100644 --- a/src/Manager.php +++ b/src/Manager.php @@ -140,7 +140,6 @@ public function downloadExternalImage(string $format, string $path): string curl_setopt($ch, \CURLOPT_HEADER, false); curl_exec($ch); - curl_close($ch); fclose($fp); return $newPath; diff --git a/tests/LeaptImTestingKernel.php b/tests/LeaptImTestingKernel.php index 50cf959..649d321 100644 --- a/tests/LeaptImTestingKernel.php +++ b/tests/LeaptImTestingKernel.php @@ -42,4 +42,9 @@ private function configureRoutes(RoutingConfigurator $routes): void { $routes->import(__DIR__ . '/../config/routing.php'); } + + private function getConfigDir(): string + { + return __DIR__ . '/../var/config'; + } } diff --git a/tests/Listener/MogrifySubscriberTest.php b/tests/Listener/MogrifySubscriberTest.php index 05021f8..f646ef1 100644 --- a/tests/Listener/MogrifySubscriberTest.php +++ b/tests/Listener/MogrifySubscriberTest.php @@ -55,10 +55,7 @@ private function buildEntityManager(): EntityManagerInterface $config = ORMSetup::createConfiguration(true, sys_get_temp_dir()); $config->setMetadataDriverImpl(new AttributeDriver([__DIR__ . '/Fixtures'])); $config->setAutoGenerateProxyClasses(true); - - if (\PHP_VERSION_ID >= 80400) { - $config->enableNativeLazyObjects(true); - } + $config->enableNativeLazyObjects(true); $params = [ 'driver' => 'pdo_sqlite', diff --git a/tests/ManagerTest.php b/tests/ManagerTest.php index 9d38178..3481598 100644 --- a/tests/ManagerTest.php +++ b/tests/ManagerTest.php @@ -120,7 +120,6 @@ public function testGetUrl(Manager $manager): void public function testConvertFormat(Manager $manager): void { $method = new \ReflectionMethod($manager, 'convertFormat'); - $method->setAccessible(true); $this->assertEquals(['resize' => '100x100'], $method->invoke($manager, 'list')); $this->assertEquals(['resize' => '100x100', 'crop' => '50x50+1+1'], $method->invoke($manager, ['resize' => '100x100', 'crop' => '50x50+1+1'])); @@ -135,7 +134,6 @@ public function testConvertFormatException(Manager $manager): void $this->expectException(InvalidArgumentException::class); $method = new \ReflectionMethod($manager, 'convertFormat'); - $method->setAccessible(true); $method->invoke($manager, 'someunknownformat'); } @@ -156,7 +154,6 @@ public function testCheckImage(Manager $manager): void $this->root->addChild($structureStream); $method = new \ReflectionMethod($manager, 'checkImage'); - $method->setAccessible(true); $method->invoke($manager, 'uploads/somefile'); $method->invoke($manager, 'vfs://public/uploads/somefile'); @@ -169,7 +166,6 @@ public function testCheckImageException(Manager $manager): void $this->expectException(NotFoundException::class); $method = new \ReflectionMethod($manager, 'checkImage'); - $method->setAccessible(true); $method->invoke($manager, 'someinexistantfile'); } @@ -178,7 +174,6 @@ public function testCheckImageException(Manager $manager): void public function testPathify(Manager $manager): void { $method = new \ReflectionMethod($manager, 'pathify'); - $method->setAccessible(true); $simplePath = $method->invoke($manager, '200x150'); $this->assertIsString($simplePath); @@ -198,7 +193,6 @@ private function getManagerPrivateValue(string $propertyName, Manager $manager): $reflection = new \ReflectionClass($manager); $property = $reflection->getProperty($propertyName); - $property->setAccessible(true); return $property->getValue($manager); } diff --git a/tests/WrapperTest.php b/tests/WrapperTest.php index 211c69c..8bbbc09 100644 --- a/tests/WrapperTest.php +++ b/tests/WrapperTest.php @@ -33,7 +33,6 @@ protected function setUp(): void public function testPrepareAttributes(array $attributes, string $expected): void { $method = new \ReflectionMethod($this->wrapper, 'prepareAttributes'); - $method->setAccessible(true); $this->assertEquals($expected, $method->invoke($this->wrapper, $attributes)); } @@ -56,7 +55,7 @@ public static function providerPrepareAttributes(): iterable yield 'array_config' => [ [ 'resize' => '120x', - null => '+opaque -transparent', + '' => '+opaque -transparent', ], ' -resize "120x" +opaque -transparent', ]; @@ -67,7 +66,6 @@ public function testPrepareAttributesException(mixed $attributes): void { $this->expectException(\TypeError::class); $method = new \ReflectionMethod($this->wrapper, 'prepareAttributes'); - $method->setAccessible(true); $method->invoke($this->wrapper, $attributes); } @@ -88,7 +86,6 @@ public static function providerPrepareAttributesException(): iterable public function testBuildCommand(string $command, string $inputFile, array $attributes, string $outputFile, string $expected): void { $method = new \ReflectionMethod($this->wrapper, 'buildCommand'); - $method->setAccessible(true); $this->assertEquals($expected, $method->invoke($this->wrapper, $command, $inputFile, $attributes, $outputFile)); } @@ -112,7 +109,6 @@ public function testBuildCommandException(string $command, string $inputFile, ar $this->expectException(InvalidArgumentException::class); $method = new \ReflectionMethod($this->wrapper, 'buildCommand'); - $method->setAccessible(true); $method->invoke($this->wrapper, $command, $inputFile, $attributes, $outputFile); } @@ -149,7 +145,6 @@ public function testRawRunRuntimeException(): void public function testValidateCommand(string $commandString): void { $method = new \ReflectionMethod($this->wrapper, 'validateCommand'); - $method->setAccessible(true); $this->assertTrue($method->invoke($this->wrapper, $commandString)); } @@ -169,7 +164,6 @@ public function testValidateCommandException(string $commandString): void $this->expectException(\InvalidArgumentException::class); $method = new \ReflectionMethod($this->wrapper, 'validateCommand'); - $method->setAccessible(true); $method->invoke($this->wrapper, $commandString); } @@ -199,7 +193,6 @@ public function testCheckDirectoryException(): void $this->expectException(RuntimeException::class); $method = new \ReflectionMethod($this->wrapper, 'checkDirectory'); - $method->setAccessible(true); \assert(vfsStreamWrapper::getRoot() instanceof vfsStreamContent); vfsStreamWrapper::getRoot()->chmod(0400);