Skip to content
Extraits de code Groupes Projets

Comparer les révisions

Les modifications sont affichées comme si la révision source était fusionnée avec la révision cible. En savoir plus sur la comparaison des révisions.

Source

Sélectionner le projet cible
No results found

Cible

Sélectionner le projet cible
  • spip-league/composer-installer
1 résultat
Afficher les modifications
Validations sur la source (3)
Affichage de
avec 147 ajouts et 99 suppressions
...@@ -2,5 +2,6 @@ ...@@ -2,5 +2,6 @@
.gitignore export-ignore .gitignore export-ignore
.gitattributes export-ignore .gitattributes export-ignore
/tests/ export-ignore /tests/ export-ignore
/plugin.xml export-ignore /phpunit.xml.dist export-ignore
/.php-cs-fixer.dist.php export-ignore /phpstan-baseline.neon export-ignore
/phpstan.neon.dist export-ignore
/vendor/ /vendor/
/composer.phar /composer.phar
/composer.lock /composer.lock
.php_cs.cache /phpunit.xml
/.phpunit.cache /.phpunit.cache
/phpstan.neon
/tmp/ /tmp/
/.php-cs-fixer.cache
/.php-cs-fixer.php
...@@ -14,7 +14,15 @@ ...@@ -14,7 +14,15 @@
}, },
"require-dev": { "require-dev": {
"composer/composer": "^2.8", "composer/composer": "^2.8",
"phpunit/phpunit": "^11.4" "phpunit/phpunit": "^11.4",
"rector/rector": "^2.0",
"spip-league/easy-coding-standard": "^1.1"
},
"repositories": {
"spip": {
"type": "composer",
"url": "https://get.spip.net/composer"
}
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
...@@ -31,5 +39,14 @@ ...@@ -31,5 +39,14 @@
"dev-main": "0.8.x-dev" "dev-main": "0.8.x-dev"
}, },
"class": "SpipLeague\\Composer\\SpipInstallerPlugin" "class": "SpipLeague\\Composer\\SpipInstallerPlugin"
},
"scripts": {
"analyse": "vendor/bin/phpstan",
"check-cs": "vendor/bin/ecs check --ansi",
"fix-cs": "vendor/bin/ecs check --fix --ansi",
"rector": "vendor/bin/rector process --ansi",
"rector-dry-run": "vendor/bin/rector process --dry-run --ansi",
"test": "phpunit --colors --no-coverage",
"test-coverage": "@php -d xdebug.mode=coverage vendor/bin/phpunit --colors"
} }
} }
<?php
declare(strict_types=1);
use SpipLeague\EasyCodingStandard\Set\SetList;
use Symplify\EasyCodingStandard\Config\ECSConfig;
return ECSConfig::configure()
->withSets([SetList::SPIP_LEAGUE])
->withPaths([__DIR__ . '/src', __DIR__ . '/tests'])
->withRootFiles()
->withParallel()
;
...@@ -6,4 +6,5 @@ parameters: ...@@ -6,4 +6,5 @@ parameters:
phpVersion: 70400 phpVersion: 70400
paths: paths:
- src - src
- tests
level: max level: max
Fichier déplacé
<?php
declare(strict_types=1);
use Rector\CodeQuality\Rector\LogicalAnd\LogicalToBooleanRector;
use Rector\Config\RectorConfig;
use Rector\Php81\Rector\FuncCall\NullToStrictStringFuncCallArgRector;
return RectorConfig::configure()
->withPaths([__DIR__ . '/src', __DIR__ . '/tests'])
->withRootFiles()
->withPhpSets(php74: true)
->withRules([LogicalToBooleanRector::class])
->withSkip([NullToStrictStringFuncCallArgRector::class]);
...@@ -11,31 +11,30 @@ use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem; ...@@ -11,31 +11,30 @@ use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem;
*/ */
class AssetsClearCache class AssetsClearCache
{ {
/** @var string[] */ /**
private static array $dirs = [ * @var string[]
SpipPaths::DIR_ASSETS_CSS, */
SpipPaths::DIR_ASSETS_JS, private static array $dirs = [SpipPaths::DIR_ASSETS_CSS, SpipPaths::DIR_ASSETS_JS];
];
/** /**
* To delete the cache files dedicated for public compiled assets * To delete the cache files dedicated for public compiled assets
*
* @param $event
*
* @return void
*/ */
public static function clearCache(Event $event) public static function clearCache(Event $event)
{ {
$event->getIO()->write('Clearing the assets cache ...'); $event->getIO()
$vendorDir = $event->getComposer()->getConfig()->get('vendor-dir') . '/'; ->write('Clearing the assets cache ...');
$vendorDir = $event->getComposer()
->getConfig()
->get('vendor-dir') . '/';
$fs = new Filesystem($event->getComposer()->getLoop()->getProcessExecutor()); $fs = new Filesystem($event->getComposer()->getLoop()->getProcessExecutor());
$sffs = new SymfonyFilesystem; $sffs = new SymfonyFilesystem();
foreach (self::$dirs as $dir) { foreach (self::$dirs as $dir) {
$fs->emptyDirectory($vendorDir . '../' . SpipPaths::interpolate($dir)); $fs->emptyDirectory($vendorDir . '../' . SpipPaths::interpolate($dir));
$sffs->chmod($vendorDir . '../' . SpipPaths::interpolate($dir), SpipPaths::CHMOD, SpipPaths::UMASK); $sffs->chmod($vendorDir . '../' . SpipPaths::interpolate($dir), SpipPaths::CHMOD, SpipPaths::UMASK);
} }
$event->getIO()->write('Done.'); $event->getIO()
->write('Done.');
} }
} }
...@@ -10,12 +10,14 @@ use Symfony\Component\Filesystem\Filesystem; ...@@ -10,12 +10,14 @@ use Symfony\Component\Filesystem\Filesystem;
*/ */
class BaseDirectories class BaseDirectories
{ {
/** @var string[] */ /**
private static array $readOnlyDirs = [ * @var string[]
SpipPaths::ETC, */
]; private static array $readOnlyDirs = [SpipPaths::ETC];
/** @var string[] */ /**
* @var string[]
*/
private static array $writeableDirs = [ private static array $writeableDirs = [
SpipPaths::VAR, SpipPaths::VAR,
SpipPaths::TMP, SpipPaths::TMP,
...@@ -26,46 +28,57 @@ class BaseDirectories ...@@ -26,46 +28,57 @@ class BaseDirectories
/** /**
* To create base directories * To create base directories
*
* @param $event
*
* @return void
*/ */
public static function createBaseDirectories(Event $event, bool $check = false) public static function createBaseDirectories(Event $event, bool $check = false)
{ {
if ($check) { if ($check) {
$event->getIO()->write('Checking base directories ...'); $event->getIO()
->write('Checking base directories ...');
} else { } else {
$event->getIO()->write('Creating base directories ...'); $event->getIO()
->write('Creating base directories ...');
} }
$vendorDir = $event->getComposer()->getConfig()->get('vendor-dir') . '/'; $vendorDir = $event->getComposer()
->getConfig()
->get('vendor-dir') . '/';
$fs = new Filesystem(); $fs = new Filesystem();
foreach (self::$readOnlyDirs as $dir) { foreach (self::$readOnlyDirs as $dir) {
$toCreate = $vendorDir . '../' . SpipPaths::interpolate($dir); $toCreate = $vendorDir . '../' . SpipPaths::interpolate($dir);
if (!$fs->exists($toCreate)) { if (!$fs->exists($toCreate)) {
$event->getIO()->write('Creating ' . SpipPaths::interpolate($dir) . ' ...'); $event->getIO()
->write('Creating ' . SpipPaths::interpolate($dir) . ' ...');
$fs->mkdir($toCreate); $fs->mkdir($toCreate);
} else { } else {
$event->getIO()->write(SpipPaths::interpolate($dir) . ' OK'); $event->getIO()
->write(SpipPaths::interpolate($dir) . ' OK');
} }
$event->getIO()->write(\sprintf('Applying chmod %04o on ', SpipPaths::CHMOD & ~0022) . SpipPaths::interpolate($dir) . ' ...'); $event->getIO()
->write(
\sprintf('Applying chmod %04o on ', SpipPaths::CHMOD & ~0022) . SpipPaths::interpolate(
$dir,
) . ' ...',
);
$fs->chmod($toCreate, SpipPaths::CHMOD, SpipPaths::UMASK | 0022); $fs->chmod($toCreate, SpipPaths::CHMOD, SpipPaths::UMASK | 0022);
} }
foreach (self::$writeableDirs as $dir) { foreach (self::$writeableDirs as $dir) {
$toCreate = $vendorDir . '../' . SpipPaths::interpolate($dir); $toCreate = $vendorDir . '../' . SpipPaths::interpolate($dir);
if (!$fs->exists($toCreate)) { if (!$fs->exists($toCreate)) {
$event->getIO()->write('Creating ' . SpipPaths::interpolate($dir) . ' ...'); $event->getIO()
->write('Creating ' . SpipPaths::interpolate($dir) . ' ...');
$fs->mkdir($toCreate); $fs->mkdir($toCreate);
} else { } else {
$event->getIO()->write(SpipPaths::interpolate($dir) . ' OK'); $event->getIO()
->write(SpipPaths::interpolate($dir) . ' OK');
} }
$event->getIO()->write(\sprintf('Applying chmod %04o on ', SpipPaths::CHMOD) . SpipPaths::interpolate($dir) . ' ...'); $event->getIO()
->write(\sprintf('Applying chmod %04o on ', SpipPaths::CHMOD) . SpipPaths::interpolate($dir) . ' ...');
$fs->chmod($toCreate, SpipPaths::CHMOD, SpipPaths::UMASK); $fs->chmod($toCreate, SpipPaths::CHMOD, SpipPaths::UMASK);
} }
$event->getIO()->write('Done.'); $event->getIO()
->write('Done.');
} }
} }
...@@ -9,7 +9,8 @@ abstract class AbstractSpipCommand extends BaseCommand ...@@ -9,7 +9,8 @@ abstract class AbstractSpipCommand extends BaseCommand
protected function getRootDir(): string protected function getRootDir(): string
{ {
$composer = $this->requireComposer(); $composer = $this->requireComposer();
$vendorDir = $composer->getConfig()->get('vendor-dir'); $vendorDir = $composer->getConfig()
->get('vendor-dir');
return \realpath($vendorDir . '/..') ?: ''; return \realpath($vendorDir . '/..') ?: '';
} }
......
...@@ -13,12 +13,15 @@ use Symfony\Component\Console\Output\OutputInterface; ...@@ -13,12 +13,15 @@ use Symfony\Component\Console\Output\OutputInterface;
/** /**
* @since 0.7.0 * @since 0.7.0
*
* @author JamesRezo <jamees@rezo.net>
*/ */
abstract class AbstractSwitchCommand extends AbstractSpipCommand abstract class AbstractSwitchCommand extends AbstractSpipCommand
{ {
abstract protected function doSwitch(Analyzer $analyzer, Switcher $switcher, OutputInterface $output, callable $reset): int; abstract protected function doSwitch(
Analyzer $analyzer,
Switcher $switcher,
OutputInterface $output,
callable $reset,
): int;
protected function execute(InputInterface $input, OutputInterface $output): int protected function execute(InputInterface $input, OutputInterface $output): int
{ {
...@@ -51,12 +54,15 @@ abstract class AbstractSwitchCommand extends AbstractSpipCommand ...@@ -51,12 +54,15 @@ abstract class AbstractSwitchCommand extends AbstractSpipCommand
if ( if (
$exitCode === AbstractSpipCommand::SUCCESS $exitCode === AbstractSpipCommand::SUCCESS
) { ) {
$exitCode = $this->getApplication()->doRun(new ArrayInput(['command' => 'update']), $output); $exitCode = $this->getApplication()
->doRun(new ArrayInput(['command' => 'update']), $output);
if ( if (
$exitCode === AbstractSpipCommand::SUCCESS $exitCode === AbstractSpipCommand::SUCCESS
&& $this->getApplication()->has('normalize') && $this->getApplication()
->has('normalize')
) { ) {
$exitCode = $this->getApplication()->doRun(new ArrayInput(['command' => 'normalize']), $output); $exitCode = $this->getApplication()
->doRun(new ArrayInput(['command' => 'normalize']), $output);
} }
} }
...@@ -73,7 +79,7 @@ abstract class AbstractSwitchCommand extends AbstractSpipCommand ...@@ -73,7 +79,7 @@ abstract class AbstractSwitchCommand extends AbstractSpipCommand
} else { } else {
\file_put_contents( \file_put_contents(
$distributionFile, $distributionFile,
\json_encode($distribution, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES) \json_encode($distribution, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES),
); );
} }
} }
......
...@@ -66,14 +66,13 @@ class LocalCommand extends AbstractSpipCommand ...@@ -66,14 +66,13 @@ class LocalCommand extends AbstractSpipCommand
// create new input without "local" command prefix // create new input without "local" command prefix
$input = new StringInput(Preg::replace('{\bl(?:o(?:c(?:a(?:l)?)?)?)?\b}', '', $input->__toString(), 1)); $input = new StringInput(Preg::replace('{\bl(?:o(?:c(?:a(?:l)?)?)?)?\b}', '', $input->__toString(), 1));
$this->getApplication()->resetComposer(); $this->getApplication()
->resetComposer();
return $this->getApplication()->run($input, $output); return $this->getApplication()
->run($input, $output);
} }
/**
* @inheritDoc
*/
public function isProxyCommand(): bool public function isProxyCommand(): bool
{ {
return true; return true;
......
...@@ -27,13 +27,14 @@ class ModeDevCommand extends AbstractSpipCommand ...@@ -27,13 +27,14 @@ class ModeDevCommand extends AbstractSpipCommand
{ {
$composerFile = Factory::getComposerFile(); $composerFile = Factory::getComposerFile();
$composer = $this->tryComposer(); $composer = $this->tryComposer();
if (\is_null($composer)) { if ($composer === null) {
return AbstractSpipCommand::FAILURE; return AbstractSpipCommand::FAILURE;
} }
$output->writeln('Looking into ' . $composerFile); $output->writeln('Looking into ' . $composerFile);
if (SpipPaths::LOCAL_COMPOSER == $composerFile) { if ($composerFile == SpipPaths::LOCAL_COMPOSER) {
$tmp = $composer->getConfig()->get('preferred-install'); $tmp = $composer->getConfig()
->get('preferred-install');
if (is_string($tmp)) { if (is_string($tmp)) {
$tmp = [$tmp]; $tmp = [$tmp];
} }
...@@ -42,7 +43,9 @@ class ModeDevCommand extends AbstractSpipCommand ...@@ -42,7 +43,9 @@ class ModeDevCommand extends AbstractSpipCommand
} }
if (empty(\array_intersect(['spip/*' => 'source'], $tmp))) { if (empty(\array_intersect(['spip/*' => 'source'], $tmp))) {
$output->writeln('<warning>Missing preferred-install in ' . $composerFile . '</warning>'); $output->writeln('<warning>Missing preferred-install in ' . $composerFile . '</warning>');
if (!$this->getIO()->askConfirmation('<info>Adding default preferred-install ?</info> [<comment>Y,n</comment>]?')) { if (!$this->getIO()->askConfirmation(
'<info>Adding default preferred-install ?</info> [<comment>Y,n</comment>]?',
)) {
$output->writeln('Aborting.'); $output->writeln('Aborting.');
return AbstractSpipCommand::FAILURE; return AbstractSpipCommand::FAILURE;
} }
...@@ -52,7 +55,7 @@ class ModeDevCommand extends AbstractSpipCommand ...@@ -52,7 +55,7 @@ class ModeDevCommand extends AbstractSpipCommand
$json->addConfigSetting('preferred-install', ['spip/*' => 'source']); $json->addConfigSetting('preferred-install', ['spip/*' => 'source']);
$this->resetComposer(); $this->resetComposer();
$composer = $this->tryComposer(); $composer = $this->tryComposer();
if (\is_null($composer)) { if ($composer === null) {
return AbstractSpipCommand::FAILURE; return AbstractSpipCommand::FAILURE;
} }
} }
......
...@@ -11,13 +11,11 @@ use Symfony\Component\Console\Output\OutputInterface; ...@@ -11,13 +11,11 @@ use Symfony\Component\Console\Output\OutputInterface;
/** /**
* @since 0.7.0 * @since 0.7.0
*
* @author JamesRezo <jamees@rezo.net>
*/ */
#[AsCommand( #[AsCommand(
name: 'spip:extensions:switch-back', name: 'spip:extensions:switch-back',
description: 'Switch back to a SPIP4-Like installation of extensions', description: 'Switch back to a SPIP4-Like installation of extensions',
aliases: ['switch:back', 'back'] aliases: ['switch:back', 'back'],
)] )]
class SwitchBackCommand extends AbstractSwitchCommand class SwitchBackCommand extends AbstractSwitchCommand
{ {
......
...@@ -10,13 +10,11 @@ use Symfony\Component\Console\Output\OutputInterface; ...@@ -10,13 +10,11 @@ use Symfony\Component\Console\Output\OutputInterface;
/** /**
* @since 0.7.0 * @since 0.7.0
*
* @author JamesRezo <jamees@rezo.net>
*/ */
#[AsCommand( #[AsCommand(
name: 'spip:extensions:switch-forward', name: 'spip:extensions:switch-forward',
description: 'Switch to a SPIP5-Like installation of extensions', description: 'Switch to a SPIP5-Like installation of extensions',
aliases: ['switch:forward', 'forward'] aliases: ['switch:forward', 'forward'],
)] )]
class SwitchForwardCommand extends AbstractSwitchCommand class SwitchForwardCommand extends AbstractSwitchCommand
{ {
......
...@@ -13,16 +13,8 @@ use SpipLeague\Composer\Command\SwitchForwardCommand; ...@@ -13,16 +13,8 @@ use SpipLeague\Composer\Command\SwitchForwardCommand;
*/ */
class CommandProvider implements CommandProviderCapability class CommandProvider implements CommandProviderCapability
{ {
/**
* {@inheritDoc}
*/
public function getCommands() public function getCommands()
{ {
return [ return [new LocalCommand(), new ModeDevCommand(), new SwitchForwardCommand(), new SwitchBackCommand()];
new LocalCommand(),
new ModeDevCommand(),
new SwitchForwardCommand(),
new SwitchBackCommand(),
];
} }
} }
...@@ -27,10 +27,9 @@ class PreferredInstall ...@@ -27,10 +27,9 @@ class PreferredInstall
} }
$toCheck = array_keys(array_filter( $toCheck = array_keys(array_filter(
$this->composer->getConfig()->get('preferred-install'), $this->composer->getConfig()
function ($install) { ->get('preferred-install'),
return $install === 'source'; fn($install) => $install === 'source',
},
)); ));
// packages to change by mode-dev // packages to change by mode-dev
...@@ -41,7 +40,8 @@ class PreferredInstall ...@@ -41,7 +40,8 @@ class PreferredInstall
$template = $extra['spip']['template'] ?? ''; // -> SpipPaths::TEMPLATE/ $template = $extra['spip']['template'] ?? ''; // -> SpipPaths::TEMPLATE/
$privateTemplate = $extra['spip']['private_template'] ?? ''; // -> SpipPaths::PRIVATE_TEMPLATE/ $privateTemplate = $extra['spip']['private_template'] ?? ''; // -> SpipPaths::PRIVATE_TEMPLATE/
$backOffice = $extra['spip']['back_office'] ?? ''; // -> SpipPaths::BACK_OFFICE/ $backOffice = $extra['spip']['back_office'] ?? ''; // -> SpipPaths::BACK_OFFICE/
$vendorDir = $this->composer->getConfig()->get('vendor-dir'); // -> vendor/ $vendorDir = $this->composer->getConfig()
->get('vendor-dir'); // -> vendor/
$rootDir = realpath($vendorDir . '/..'); $rootDir = realpath($vendorDir . '/..');
// -> plugins/ ? // -> plugins/ ?
......
...@@ -9,8 +9,6 @@ use SpipLeague\Composer\SpipPaths; ...@@ -9,8 +9,6 @@ use SpipLeague\Composer\SpipPaths;
* Déduite du contenu du fichier `./plugins-dist.json` * Déduite du contenu du fichier `./plugins-dist.json`
* *
* @since 0.7.0 * @since 0.7.0
*
* @author JamesRezo <james@rezo.net>
*/ */
class Collection implements CollectionInterface class Collection implements CollectionInterface
{ {
...@@ -24,9 +22,10 @@ class Collection implements CollectionInterface ...@@ -24,9 +22,10 @@ class Collection implements CollectionInterface
public function __construct(array $distribution, ?string $file = \null) public function __construct(array $distribution, ?string $file = \null)
{ {
$originalCount = \count($distribution); $originalCount = \count($distribution);
$distribution = \array_filter($distribution, function ($specification) { $distribution = \array_filter(
return $specification instanceof SpecificationInterface && !empty($specification->getPrefix()); $distribution,
}); fn($specification) => $specification instanceof SpecificationInterface && !empty($specification->getPrefix()),
);
$count = \count($distribution); $count = \count($distribution);
if ($originalCount != 0 && ($count < 1 || $originalCount != $count)) { if ($originalCount != 0 && ($count < 1 || $originalCount != $count)) {
throw new InvalidSpecificationException('A collection must contain at least one valid specification.', 2); throw new InvalidSpecificationException('A collection must contain at least one valid specification.', 2);
...@@ -40,24 +39,19 @@ class Collection implements CollectionInterface ...@@ -40,24 +39,19 @@ class Collection implements CollectionInterface
$this->file = $file; $this->file = $file;
} }
/**
* @throws \RuntimeException on any composer errror
* @throws \LogicException if plugins-dist.json is missing
*/
public static function fromJsonFile(RemoteUrlsInterface $changer, ?string $file = null): self public static function fromJsonFile(RemoteUrlsInterface $changer, ?string $file = null): self
{ {
if (\is_null($file)) { if ($file === null) {
$file = SpipPaths::LOCAL_PLUGINS_DIST; $file = SpipPaths::LOCAL_PLUGINS_DIST;
} }
if (!(\file_exists($file) || \file_put_contents($file, '{}'))) { if (!(\file_exists($file) || \file_put_contents($file, '{}'))) {
throw new \LogicException('<error>File "' . $file . '" is missing and can\' be created. Can\'t upgrade/downgrade.</error>'); throw new \LogicException(
'<error>File "' . $file . '" is missing and can\' be created. Can\'t upgrade/downgrade.</error>',
);
} }
$pluginsDist = \json_decode( $pluginsDist = \json_decode(\file_get_contents($file) ?: '', \true);
\file_get_contents($file) ?: '',
\true
);
if (!\is_array($pluginsDist)) { if (!\is_array($pluginsDist)) {
throw new \LogicException('<error>File "' . $file . '" malformed. Can\'t upgrade/downgrade.</error>'); throw new \LogicException('<error>File "' . $file . '" malformed. Can\'t upgrade/downgrade.</error>');
} }
......
...@@ -9,8 +9,6 @@ namespace SpipLeague\Composer\Extensions; ...@@ -9,8 +9,6 @@ namespace SpipLeague\Composer\Extensions;
* @extends \Iterator<string,SpecificationInterface> * @extends \Iterator<string,SpecificationInterface>
* *
* @since 0.7.0 * @since 0.7.0
*
* @author JamesRezo <james@rezo.net>
*/ */
interface CollectionInterface extends \Countable, \Iterator, \JsonSerializable, \ArrayAccess interface CollectionInterface extends \Countable, \Iterator, \JsonSerializable, \ArrayAccess
{ {
......
...@@ -6,17 +6,19 @@ namespace SpipLeague\Composer\Extensions; ...@@ -6,17 +6,19 @@ namespace SpipLeague\Composer\Extensions;
* Implémentation concrète de \Countable, \Iterator, \JsonSerializable, \ArrayAccess. * Implémentation concrète de \Countable, \Iterator, \JsonSerializable, \ArrayAccess.
* *
* @since 0.7.0 * @since 0.7.0
*
* @author JamesRezo <james@rezo.net>
*/ */
trait CollectionTrait trait CollectionTrait
{ {
protected int $position = 0; protected int $position = 0;
/** @var string[] */ /**
* @var string[]
*/
protected array $keys = []; protected array $keys = [];
/** @var array<string,SpecificationInterface> */ /**
* @var array<string,SpecificationInterface>
*/
protected array $collection = []; protected array $collection = [];
public function count(): int public function count(): int
...@@ -76,7 +78,7 @@ trait CollectionTrait ...@@ -76,7 +78,7 @@ trait CollectionTrait
public function offsetGet(mixed $offset): mixed public function offsetGet(mixed $offset): mixed
{ {
return isset($this->collection[$offset]) ? $this->collection[$offset] : null; return $this->collection[$offset] ?? null;
} }
public function offsetSet(mixed $offset, mixed $value): void public function offsetSet(mixed $offset, mixed $value): void
...@@ -85,7 +87,7 @@ trait CollectionTrait ...@@ -85,7 +87,7 @@ trait CollectionTrait
throw new InvalidSpecificationException('A collection must only contain valid specifications.', 3); throw new InvalidSpecificationException('A collection must only contain valid specifications.', 3);
} }
if (is_null($offset) || !\is_string($offset)) { if ($offset === null || !\is_string($offset)) {
$offset = $value->getPrefix(); $offset = $value->getPrefix();
} }
......