diff --git a/Neos.Cache/Classes/Backend/AbstractBackend.php b/Neos.Cache/Classes/Backend/AbstractBackend.php index 05dab6c87b..99b4afbf99 100644 --- a/Neos.Cache/Classes/Backend/AbstractBackend.php +++ b/Neos.Cache/Classes/Backend/AbstractBackend.php @@ -57,21 +57,18 @@ abstract class AbstractBackend implements BackendInterface /** * Constructs this backend * - * @param EnvironmentConfiguration $environmentConfiguration - * @param array $options Configuration options - depends on the actual backend + * @param array $options Configuration options - depends on the actual backend * @api */ - public function __construct(?EnvironmentConfiguration $environmentConfiguration = null, array $options = []) + public function __construct(EnvironmentConfiguration $environmentConfiguration = null, array $options = []) { $this->environmentConfiguration = $environmentConfiguration; - if (is_array($options) || $options instanceof \Iterator) { - $this->setProperties($options); - } + $this->setProperties($options); } /** - * @param array $properties + * @param array $properties * @param boolean $throwExceptionIfPropertyNotSettable * @return void * @throws \InvalidArgumentException @@ -117,7 +114,7 @@ public function setCache(FrontendInterface $cache): void { $this->cache = $cache; $this->cacheIdentifier = $this->cache->getIdentifier(); - $applicationIdentifier = $this->environmentConfiguration instanceof EnvironmentConfiguration ? $this->environmentConfiguration->getApplicationIdentifier() : ''; + $applicationIdentifier = $this->environmentConfiguration->getApplicationIdentifier(); $this->identifierPrefix = md5($applicationIdentifier) . ':' . $this->cacheIdentifier . ':'; } diff --git a/Neos.Cache/Classes/Backend/TransientMemoryBackend.php b/Neos.Cache/Classes/Backend/TransientMemoryBackend.php index 1ea561187c..8bceea8ab8 100644 --- a/Neos.Cache/Classes/Backend/TransientMemoryBackend.php +++ b/Neos.Cache/Classes/Backend/TransientMemoryBackend.php @@ -22,8 +22,14 @@ * * @api */ -class TransientMemoryBackend extends IndependentAbstractBackend implements TaggableBackendInterface +class TransientMemoryBackend implements TaggableBackendInterface { + /** + * Reference to the cache frontend which uses this backend + * @var FrontendInterface + */ + protected $cache; + /** * @var array */ @@ -34,6 +40,12 @@ class TransientMemoryBackend extends IndependentAbstractBackend implements Tagga */ protected $tagsAndEntries = []; + // todo there is no real contract it will just be invoked with the env configuration and options .. :( + public function __construct() + { + + } + /** * Saves data in the cache. * @@ -175,4 +187,15 @@ public function flushByTags(array $tags): int public function collectGarbage(): void { } + + public function setCache(FrontendInterface $cache): void + { + $this->cache = $cache; + } + + public function getPrefixedIdentifier(string $entryIdentifier): string + { + $identifierPrefix = $this->cache->getIdentifier() . ':'; + return $identifierPrefix . $entryIdentifier; + } } diff --git a/Neos.Flow.Log/Classes/Backend/AbstractBackend.php b/Neos.Flow.Log/Classes/Backend/AbstractBackend.php index 6279d6cd85..1e879d90db 100644 --- a/Neos.Flow.Log/Classes/Backend/AbstractBackend.php +++ b/Neos.Flow.Log/Classes/Backend/AbstractBackend.php @@ -42,7 +42,7 @@ abstract class AbstractBackend implements BackendInterface */ public function __construct($options = []) { - if (is_array($options) || $options instanceof \ArrayAccess) { + if (is_iterable($options)) { foreach ($options as $optionKey => $optionValue) { $methodName = 'set' . ucfirst($optionKey); if (method_exists($this, $methodName)) { diff --git a/Neos.Flow.Log/Classes/Backend/AnsiConsoleBackend.php b/Neos.Flow.Log/Classes/Backend/AnsiConsoleBackend.php index 5749b5df81..30784f9aef 100644 --- a/Neos.Flow.Log/Classes/Backend/AnsiConsoleBackend.php +++ b/Neos.Flow.Log/Classes/Backend/AnsiConsoleBackend.php @@ -40,7 +40,7 @@ class AnsiConsoleBackend extends ConsoleBackend const END = "\033[0m"; /** - * @var array + * @var array */ protected $tagFormats = []; @@ -75,12 +75,7 @@ public function open(): void /** * Appends the given message along with the additional information into the log. * - * @param string $message - * @param int $severity - * @param array $additionalData - * @param string $packageKey - * @param string $className - * @param string $methodName + * @param array|null $additionalData * @return void */ public function append(string $message, int $severity = LOG_INFO, $additionalData = null, ?string $packageKey = null, ?string $className = null, ?string $methodName = null): void @@ -118,7 +113,7 @@ protected function formatOutput($output) } else { return str_replace('|', $matches[3], $format); } - }, $output); + }, $output) ?: ''; } while ($lastOutput !== $output); return $output; } @@ -126,7 +121,7 @@ protected function formatOutput($output) /** * @param boolean $disableAnsi */ - public function setDisableAnsi($disableAnsi) + public function setDisableAnsi($disableAnsi): void { $this->disableAnsi = $disableAnsi; } diff --git a/Neos.Flow.Log/Classes/Backend/ConsoleBackend.php b/Neos.Flow.Log/Classes/Backend/ConsoleBackend.php index def5dd734e..1398b288c9 100644 --- a/Neos.Flow.Log/Classes/Backend/ConsoleBackend.php +++ b/Neos.Flow.Log/Classes/Backend/ConsoleBackend.php @@ -25,7 +25,7 @@ class ConsoleBackend extends AbstractBackend { /** * An array of severity labels, indexed by their integer constant - * @var array + * @var array */ protected $severityLabels; @@ -61,10 +61,11 @@ public function open(): void LOG_DEBUG => 'DEBUG ', ]; - $this->streamHandle = fopen('php://' . $this->streamName, 'w'); - if (!is_resource($this->streamHandle)) { + $streamHandle = fopen('php://' . $this->streamName, 'w'); + if (!is_resource($streamHandle)) { throw new CouldNotOpenResourceException('Could not open stream "' . $this->streamName . '" for write access.', 1310986609); } + $this->streamHandle = $streamHandle; } /** diff --git a/Neos.Flow.Log/Classes/Backend/FileBackend.php b/Neos.Flow.Log/Classes/Backend/FileBackend.php index 67a3ac2aaf..7a19df4a97 100644 --- a/Neos.Flow.Log/Classes/Backend/FileBackend.php +++ b/Neos.Flow.Log/Classes/Backend/FileBackend.php @@ -27,7 +27,7 @@ class FileBackend extends AbstractBackend { /** * An array of severity labels, indexed by their integer constant - * @var array + * @var array */ protected $severityLabels; diff --git a/Neos.Flow.Log/Classes/PlainTextFormatter.php b/Neos.Flow.Log/Classes/PlainTextFormatter.php index 0a15efb126..5590dbbed2 100644 --- a/Neos.Flow.Log/Classes/PlainTextFormatter.php +++ b/Neos.Flow.Log/Classes/PlainTextFormatter.php @@ -22,10 +22,9 @@ public function __construct($variable) } /** - * @param $spaces - * @return string + * @return ?string */ - public function format($spaces = 4) + public function format(int $spaces = 4) { return $this->renderVariableAsPlaintext($this->variable, $spaces); } diff --git a/Neos.Flow.Log/Classes/Psr/Logger.php b/Neos.Flow.Log/Classes/Psr/Logger.php index 6ebad73f56..c1a18be6f8 100644 --- a/Neos.Flow.Log/Classes/Psr/Logger.php +++ b/Neos.Flow.Log/Classes/Psr/Logger.php @@ -45,7 +45,7 @@ class Logger implements LoggerInterface /** * Constructs the PSR-3 Logger. * - * @param iterable $backends + * @param iterable $backends */ public function __construct(iterable $backends) { @@ -61,7 +61,7 @@ public function __construct(iterable $backends) /** * @param mixed $level * @param string|\Stringable $message - * @param array $context + * @param array $context * @api */ public function log($level, string|\Stringable $message, array $context = []): void @@ -80,8 +80,8 @@ public function log($level, string|\Stringable $message, array $context = []): v } /** - * @param array $context - * @return array list of packageKey, className and methodName either string or null + * @param array $context + * @return array list of packageKey, className and methodName either string or null */ protected function extractLegacyDataFromContext(array $context): array { @@ -93,8 +93,8 @@ protected function extractLegacyDataFromContext(array $context): array } /** - * @param array $context - * @return array + * @param array $context + * @return array */ protected function removeLegacyDataFromContext(array $context): array { diff --git a/Neos.Flow/Classes/Package/FlowPackageKey.php b/Neos.Flow/Classes/Package/FlowPackageKey.php index 5e5a72ec6e..fe23b20851 100644 --- a/Neos.Flow/Classes/Package/FlowPackageKey.php +++ b/Neos.Flow/Classes/Package/FlowPackageKey.php @@ -37,7 +37,7 @@ private function __construct( } } - public static function fromString(string $value) + public static function fromString(string $value): self { return new self($value); } @@ -63,6 +63,9 @@ public static function isPackageKeyValid(string $string): bool * case version of the composer name / namespace will be used, with backslashes replaced by dots. * * Else the composer name will be used with the slash replaced by a dot + * + * // @todo replace with ComposerManifest array shape / object from ComposerUtility + * @param array $manifest */ public static function deriveFromManifestOrPath(array $manifest, string $packagePath): self { @@ -77,6 +80,7 @@ public static function deriveFromManifestOrPath(array $manifest, string $package $type = $manifest['type'] ?? null; if (isset($manifest['autoload']['psr-0']) && is_array($manifest['autoload']['psr-0'])) { $namespaces = array_keys($manifest['autoload']['psr-0']); + /** @var string $autoloadNamespace */ $autoloadNamespace = reset($namespaces); } return self::derivePackageKeyInternal($composerName, $type, $packagePath, $autoloadNamespace); @@ -110,7 +114,7 @@ private static function derivePackageKeyInternal(string $composerName, ?string $ } $packageKey = trim($packageKey, '.'); - $packageKey = preg_replace('/[^A-Za-z0-9.]/', '', $packageKey); + $packageKey = preg_replace('/[^A-Za-z0-9.]/', '', $packageKey) ?: ''; return new self($packageKey); } diff --git a/Neos.Flow/Classes/Package/GenericPackage.php b/Neos.Flow/Classes/Package/GenericPackage.php index 20d63c4a5d..2b72702659 100644 --- a/Neos.Flow/Classes/Package/GenericPackage.php +++ b/Neos.Flow/Classes/Package/GenericPackage.php @@ -17,6 +17,12 @@ /** * The generic base package that represents third party packages + * + * @phpstan-type AutoloadConfiguration array{ + * namespace: string, + * classPath: string, + * mappingType: string, + * } */ class GenericPackage implements PackageInterface, PackageKeyAwareInterface { @@ -54,12 +60,12 @@ class GenericPackage implements PackageInterface, PackageKeyAwareInterface protected $autoloadTypes; /** - * @var array + * @var array */ protected $autoloadConfiguration; /** - * @var array + * @var array */ protected $flattenedAutoloadConfiguration; @@ -69,7 +75,7 @@ class GenericPackage implements PackageInterface, PackageKeyAwareInterface * @param string $packageKey Key of this package * @param string $composerName * @param string $packagePath Absolute path to the location of the package's composer manifest - * @param array $autoloadConfiguration + * @param array $autoloadConfiguration */ public function __construct($packageKey, $composerName, $packagePath, array $autoloadConfiguration = []) { @@ -82,7 +88,7 @@ public function __construct($packageKey, $composerName, $packagePath, array $aut /** * Returns the array of filenames of the class files * - * @return iterable A Generator for class names (key) and their filename, including the absolute path. + * @return iterable A Generator for class names (key) and their filename, including the absolute path. */ public function getClassFiles() { @@ -121,7 +127,7 @@ public function getComposerName() /** * Returns array of all declared autoload namespaces contained in this package * - * @return array + * @return array * @api */ public function getNamespaces() @@ -157,7 +163,7 @@ public function getPackagePath() } /** - * @return array + * @return array */ public function getAutoloadPaths() { @@ -169,9 +175,9 @@ public function getAutoloadPaths() /** * Get the autoload configuration for this package. Any valid composer "autoload" configuration. * - * @return array + * @return array */ - public function getAutoloadConfiguration() + public function getAutoloadConfiguration(): array { return $this->autoloadConfiguration; } @@ -179,7 +185,7 @@ public function getAutoloadConfiguration() /** * Get a flattened array of autoload configurations that have a predictable pattern (PSR-0, PSR-4) * - * @return array Keys: "namespace", "classPath", "mappingType" + * @return array */ public function getFlattenedAutoloadConfiguration() { diff --git a/Neos.Flow/Classes/Package/Package.php b/Neos.Flow/Classes/Package/Package.php index 45a4b47c30..4245509a17 100644 --- a/Neos.Flow/Classes/Package/Package.php +++ b/Neos.Flow/Classes/Package/Package.php @@ -42,8 +42,12 @@ public function getFunctionalTestsClassFiles() $namespaces = $this->getNamespaces(); if (is_dir($this->packagePath . self::DIRECTORY_TESTS_FUNCTIONAL)) { // TODO REFACTOR replace with usage of "autoload-dev" + $namespace = reset($namespaces); + if (!$namespace) { + throw new \Exception('Missing namespace', 1744144110); + } $namespacePrefix = str_replace('/', '\\', Files::concatenatePaths([ - reset($namespaces), + $namespace, '\\Tests\\Functional\\' ])); foreach ($this->getClassesInNormalizedAutoloadPath($this->packagePath . FlowPackageInterface::DIRECTORY_TESTS_FUNCTIONAL, $namespacePrefix) as $className => $classPath) { diff --git a/Neos.Flow/Classes/Package/PackageFactory.php b/Neos.Flow/Classes/Package/PackageFactory.php index 8f9632e175..d67f720f70 100644 --- a/Neos.Flow/Classes/Package/PackageFactory.php +++ b/Neos.Flow/Classes/Package/PackageFactory.php @@ -27,7 +27,7 @@ class PackageFactory * @param string $packagePath path to package, relative to base path * @param FlowPackageKey $packageKey key / name of the package * @param string $composerName - * @param array $autoloadConfiguration Autoload configuration as defined in composer.json + * @param array $autoloadConfiguration Autoload configuration as defined in composer.json * @param array{className: class-string, pathAndFilename: string}|null $packageClassInformation * @return PackageInterface&PackageKeyAwareInterface * @throws Exception\CorruptPackageException @@ -49,9 +49,6 @@ public function create(string $packagesBasePath, string $packagePath, FlowPackag /** dynamic construction {@see GenericPackage::__construct} */ $package = new $packageClassName($packageKey->value, $composerName, $absolutePackagePath, $autoloadConfiguration); - if (!$package instanceof PackageInterface) { - throw new Exception\CorruptPackageException(sprintf('The package class of package "%s" does not implement \Neos\Flow\Package\PackageInterface. Check the file "%s".', $packageKey->value, $packageClassInformation['pathAndFilename']), 1427193370); - } if (!$package instanceof PackageKeyAwareInterface) { throw new Exception\CorruptPackageException(sprintf('The package class of package "%s" does not implement \Neos\Flow\Package\PackageKeyAwareInterface. Check the file "%s".', $packageKey->value, $packageClassInformation['pathAndFilename']), 1711665156); } @@ -100,8 +97,11 @@ public function detectFlowPackageFilePath(FlowPackageKey $packageKey, $absoluteP $absolutePackageClassPath = Files::concatenatePaths([$absolutePackagePath, $packageClassPathAndFilename]); $packageClassContents = file_get_contents($absolutePackageClassPath); + if (!$packageClassContents) { + throw new \Exception('Could not read package class file', 1744142431); + } $packageClassName = (new PhpAnalyzer($packageClassContents))->extractFullyQualifiedClassName(); - if ($packageClassName === null) { + if ($packageClassName === null || !is_subclass_of($packageClassName, PackageInterface::class)) { throw new Exception\CorruptPackageException(sprintf('The package "%s" does not contain a valid package class. Check if the file "%s" really contains a class.', $packageKey->value, $packageClassPathAndFilename), 1327587091); } diff --git a/Neos.Flow/Classes/Package/PackageInterface.php b/Neos.Flow/Classes/Package/PackageInterface.php index b8ce3e2efd..d08d764258 100644 --- a/Neos.Flow/Classes/Package/PackageInterface.php +++ b/Neos.Flow/Classes/Package/PackageInterface.php @@ -25,7 +25,7 @@ interface PackageInterface /** * Returns the array of filenames of the class files * - * @return iterable An array or yields the class names (key) and their filename, including the relative path to the package's directory + * @return iterable An array or yields the class names (key) and their filename, including the relative path to the package's directory * @api */ public function getClassFiles(); @@ -41,7 +41,7 @@ public function getComposerName(); /** * Returns an array of all namespaces declared for this package. * - * @return array + * @return array * @api */ public function getNamespaces(); diff --git a/Neos.Flow/Classes/Package/PackageManager.php b/Neos.Flow/Classes/Package/PackageManager.php index d2b0522118..fdea0ef576 100644 --- a/Neos.Flow/Classes/Package/PackageManager.php +++ b/Neos.Flow/Classes/Package/PackageManager.php @@ -27,6 +27,13 @@ /** * The default Flow Package Manager * + * @phpstan-type ComposerManifest array{ + * require?: array, + * extra?: array{neos?: array{loading-order?: array{after?: mixed}}}, + * autoload?: array>, + * name: string, + * } + * * @api * @Flow\Scope("singleton") */ @@ -60,7 +67,7 @@ class PackageManager /** * Array of available packages, indexed by package key (case sensitive) * - * @var array + * @var array */ protected $packages = []; @@ -74,7 +81,7 @@ class PackageManager /** * A map between ComposerName and PackageKey, only available when scanAvailablePackages is run * - * @var array + * @var array */ protected $composerNameToPackageKeyMap = []; @@ -93,12 +100,12 @@ class PackageManager /** * Package states configuration as stored in the PackageStates.php file * - * @var array + * @var array */ protected $packageStatesConfiguration = []; /** - * @var array + * @var array */ protected $settings; @@ -111,7 +118,7 @@ class PackageManager * Inject settings into the package manager. Has to be called explicitly on object initialization as * the package manager subpackage is excluded from proxy class building. * - * @param array $settings + * @param array $settings * @return void */ public function injectSettings(array $settings): void @@ -145,8 +152,6 @@ public function initialize(Bootstrap $bootstrap): void $this->bootstrap = $bootstrap; $this->packageStatesConfiguration = $this->getCurrentPackageStates(); $this->registerPackagesFromConfiguration($this->packageStatesConfiguration); - /** @var PackageInterface $package */ - foreach ($this->packages as $package) { if ($package instanceof FlowPackageInterface) { $this->flowPackages[$package->getPackageKey()] = $package; @@ -197,11 +202,11 @@ public function getPackagesBasePath(): string * Returns a PackageInterface object for the specified package. * * @param string $packageKey - * @return PackageInterface The requested package object + * @return PackageInterface&PackageKeyAwareInterface The requested package object * @throws Exception\UnknownPackageException if the specified package is not known * @api */ - public function getPackage($packageKey): PackageInterface + public function getPackage($packageKey): PackageInterface&PackageKeyAwareInterface { if (!$this->isPackageAvailable($packageKey)) { throw new Exception\UnknownPackageException('Package "' . $packageKey . '" is not available. Please check if the package exists and that the package key is correct (package keys are case sensitive).', 1166546734); @@ -214,7 +219,7 @@ public function getPackage($packageKey): PackageInterface * Returns an array of PackageInterface objects of all available packages. * A package is available, if the package directory contains valid meta information. * - * @return array + * @return array * @api */ public function getAvailablePackages(): array @@ -229,7 +234,7 @@ public function getAvailablePackages(): array * @param string $packageState defaults to available * @param string $packageType * - * @return array + * @return array * @throws Exception\InvalidPackageStateException * @api */ @@ -256,9 +261,9 @@ public function getFilteredPackages($packageState = 'available', $packageType = * Returns an array of PackageInterface objects in the given array of packages * that are of the specified package type. * - * @param array $packages Array of PackageInterface objects to be filtered + * @param array $packages Array of PackageInterface objects to be filtered * @param string $packageType Filter out anything that's not of this packageType - * @return array + * @return array */ protected function filterPackagesByType($packages, $packageType): array { @@ -276,7 +281,7 @@ protected function filterPackagesByType($packages, $packageType): array * Create a package, given the package key * * @param string $packageKey The package key of the new package - * @param array $manifest A composer manifest as associative array. + * @param array $manifest A composer manifest as associative array. * @param string $packagesPath If specified, the package will be created in this path, otherwise the default "Application" directory is used * @return PackageInterface The newly created package * @@ -371,7 +376,7 @@ public function createPackage($packageKey, array $manifest = [], $packagesPath = /** * Rescans available packages, order and write a new PackageStates file. * - * @return array The found and sorted package states. + * @return array The found and sorted package states. * @throws Exception * @throws InvalidConfigurationException * @throws FilesException @@ -389,7 +394,7 @@ public function rescanPackages(): array * initialises a package scan if the file was not found or the configuration format * was not current. * - * @return array + * @return array * @throws Exception * @throws InvalidConfigurationException * @throws FilesException @@ -417,7 +422,7 @@ protected function getCurrentPackageStates(): array /** * Load the current package states * - * @return array + * @return array */ protected function loadPackageStates(): array { @@ -428,7 +433,7 @@ protected function loadPackageStates(): array * Scans all directories in the packages directories for available packages. * For each package a Package object is created and stored in $this->packages. * - * @return array + * @return array * @throws Exception * @throws InvalidConfigurationException */ @@ -483,6 +488,8 @@ protected function scanAvailablePackages(): array /** * Traverses directories recursively from the given starting point and yields folder paths, who contain a composer.json. * When a composer.json was found, traversing into lower directories is stopped. + * + * @implements \Generator */ protected function findComposerPackagesInPath(string $startingDirectory): \Generator { @@ -508,6 +515,14 @@ protected function findComposerPackagesInPath(string $startingDirectory): \Gener /** * @throws Exception\CorruptPackageException * @throws Exception\InvalidPackagePathException + * @param ComposerManifest $composerManifest + * @return array{ + * packageKey: string, + * packagePath: string, + * composerName: string, + * autoloadConfiguration: array, + * packageClassInformation: array, + * } */ protected function preparePackageStateConfiguration(FlowPackageKey $packageKey, string $packagePath, array $composerManifest): array { @@ -525,7 +540,7 @@ protected function preparePackageStateConfiguration(FlowPackageKey $packageKey, /** * Requires and registers all packages which were defined in packageStatesConfiguration * - * @param array $packageStatesConfiguration + * @param array $packageStatesConfiguration * @throws Exception\CorruptPackageException */ protected function registerPackagesFromConfiguration($packageStatesConfiguration): void @@ -541,7 +556,7 @@ protected function registerPackagesFromConfiguration($packageStatesConfiguration * to all relevant data arrays. * * @param string $composerName - * @param array $packageStateConfiguration + * @param array $packageStateConfiguration * @return void * @throws Exception\CorruptPackageException */ @@ -558,8 +573,8 @@ protected function registerPackageFromStateConfiguration($composerName, $package * Takes the given packageStatesConfiguration, sorts it by dependencies, saves it and returns * the ordered list * - * @param array $packageStates - * @return array + * @param array $packageStates + * @return array * @throws Exception\PackageStatesFileNotWritableException * @throws FilesException */ @@ -574,7 +589,7 @@ protected function sortAndSavePackageStates(array $packageStates): array /** * Save the given (ordered) array of package states data * - * @param array $orderedPackageStates + * @param array $orderedPackageStates * @throws Exception\PackageStatesFileNotWritableException * @throws FilesException */ @@ -620,12 +635,15 @@ protected function savePackageStates(array $orderedPackageStates): void * and package configurations arrays holds all packages in the correct * initialization order. * - * @param array $packageStates The unordered package states - * @return array ordered package states. + * @param array $packageStates The unordered package states + * @return array ordered package states. */ protected function sortAvailablePackagesByDependencies(array $packageStates): array { - $packageOrderResolver = new PackageOrderResolver($packageStates['packages'], $this->collectPackageManifestData($packageStates)); + $packageOrderResolver = new PackageOrderResolver( + $packageStates['packages'], + $this->collectPackageManifestData($packageStates) + ); $packageStates['packages'] = $packageOrderResolver->sort(); return $packageStates; @@ -634,8 +652,8 @@ protected function sortAvailablePackagesByDependencies(array $packageStates): ar /** * Collects the manifest data for all packages in the given package states array * - * @param array $packageStates - * @return array + * @param array $packageStates + * @return array */ protected function collectPackageManifestData(array $packageStates): array { diff --git a/Neos.Flow/Classes/Package/PackageOrderResolver.php b/Neos.Flow/Classes/Package/PackageOrderResolver.php index 8fae9f6b99..c1f9235be8 100644 --- a/Neos.Flow/Classes/Package/PackageOrderResolver.php +++ b/Neos.Flow/Classes/Package/PackageOrderResolver.php @@ -3,21 +3,23 @@ /** * A simple package dependency order solver. Just sorts by simple dependencies, does no checking or versions. + * + * @phpstan-import-type ComposerManifest from PackageManager */ class PackageOrderResolver { /** - * @var array + * @var array */ protected $manifestData; /** - * @var array + * @var array> */ protected $packageStates; /** - * @var array + * @var array */ protected $sortedPackages; @@ -25,13 +27,13 @@ class PackageOrderResolver * Array with state information of still unsorted packages. * The key is a package key, the value is "-1" if it is on stack for cycle detection; otherwise it is the number of times it was attempted to sort it already. * - * @var array + * @var array */ protected $unsortedPackages; /** - * @param array $packages The array of package states to order by dependencies - * @param array $manifestData The manifests of all given packages for reading dependencies + * @param array> $packages The array of package states to order by dependencies + * @param array $manifestData The manifests of all given packages for reading dependencies */ public function __construct(array $packages, array $manifestData) { @@ -43,7 +45,7 @@ public function __construct(array $packages, array $manifestData) /** * Sorts the packages and returns the sorted packages array * - * @return array + * @return array */ public function sort() { @@ -100,7 +102,6 @@ protected function sortPackage($packageKey) $unresolvedDependencies += $this->sortListBefore($packageKey, $sortingConfiguration); } - /** @var array $packageState */ $packageState = $this->packageStates[$packageKey]; $this->unsortedPackages[$packageKey] = $iterationForPackage + 1; if ($unresolvedDependencies === 0) { diff --git a/Neos.Flow/Classes/Persistence/AbstractPersistenceManager.php b/Neos.Flow/Classes/Persistence/AbstractPersistenceManager.php index 2db99302da..c887f1fc75 100644 --- a/Neos.Flow/Classes/Persistence/AbstractPersistenceManager.php +++ b/Neos.Flow/Classes/Persistence/AbstractPersistenceManager.php @@ -22,12 +22,12 @@ abstract class AbstractPersistenceManager implements PersistenceManagerInterface { /** - * @var array + * @var array */ protected $settings = []; /** - * @var array + * @var array */ protected $newObjects = []; @@ -46,7 +46,7 @@ abstract class AbstractPersistenceManager implements PersistenceManagerInterface * Injects the Flow settings, the persistence part is kept * for further use. * - * @param array $settings + * @param array> $settings * @return void */ public function injectSettings(array $settings): void diff --git a/Neos.Flow/Classes/Persistence/AllowedObjectsContainer.php b/Neos.Flow/Classes/Persistence/AllowedObjectsContainer.php index a013cec08e..c877ce1960 100644 --- a/Neos.Flow/Classes/Persistence/AllowedObjectsContainer.php +++ b/Neos.Flow/Classes/Persistence/AllowedObjectsContainer.php @@ -18,6 +18,7 @@ /** * A container for the list of allowed objects to be persisted during this request. * + * @extends \SplObjectStorage * @Flow\Scope("singleton") */ final class AllowedObjectsContainer extends \SplObjectStorage diff --git a/Neos.Flow/Classes/Persistence/Aspect/EmbeddedValueObjectPointcutFilter.php b/Neos.Flow/Classes/Persistence/Aspect/EmbeddedValueObjectPointcutFilter.php index e4526c1003..359e31a0a2 100644 --- a/Neos.Flow/Classes/Persistence/Aspect/EmbeddedValueObjectPointcutFilter.php +++ b/Neos.Flow/Classes/Persistence/Aspect/EmbeddedValueObjectPointcutFilter.php @@ -39,7 +39,7 @@ public function injectReflectionService(\Neos\Flow\Reflection\ReflectionService /** * Checks if the specified class and method matches against the filter * - * @param string $className Name of the class to check against + * @param class-string $className Name of the class to check against * @param string $methodName Name of the method to check against * @param string $methodDeclaringClassName Name of the class the method was originally declared in * @param mixed $pointcutQueryIdentifier Some identifier for this query - must at least differ from a previous identifier. Used for circular reference detection. @@ -64,7 +64,7 @@ public function hasRuntimeEvaluationsDefinition() /** * Returns runtime evaluations for a previously matched pointcut * - * @return array Runtime evaluations + * @return array Runtime evaluations */ public function getRuntimeEvaluationsDefinition() { diff --git a/Neos.Flow/Classes/Persistence/Aspect/PersistenceMagicAspect.php b/Neos.Flow/Classes/Persistence/Aspect/PersistenceMagicAspect.php index 7daf1e95dc..46b8d4e506 100644 --- a/Neos.Flow/Classes/Persistence/Aspect/PersistenceMagicAspect.php +++ b/Neos.Flow/Classes/Persistence/Aspect/PersistenceMagicAspect.php @@ -49,21 +49,21 @@ class PersistenceMagicAspect /** * @Flow\Pointcut("classAnnotatedWith(Neos\Flow\Annotations\Entity) || classAnnotatedWith(Doctrine\ORM\Mapping\Entity)") */ - public function isEntity() + public function isEntity(): void { } /** * @Flow\Pointcut("classAnnotatedWith(Neos\Flow\Annotations\ValueObject) && !filter(Neos\Flow\Persistence\Aspect\EmbeddedValueObjectPointcutFilter)") */ - public function isNonEmbeddedValueObject() + public function isNonEmbeddedValueObject(): void { } /** * @Flow\Pointcut("Neos\Flow\Persistence\Aspect\PersistenceMagicAspect->isEntity || Neos\Flow\Persistence\Aspect\PersistenceMagicAspect->isNonEmbeddedValueObject") */ - public function isEntityOrValueObject() + public function isEntityOrValueObject(): void { } @@ -114,6 +114,9 @@ public function generateValueHash(JoinPointInterface $joinPoint) $hashSourceParts = []; $classSchema = $this->reflectionService->getClassSchema($proxyClassName); + if (!$classSchema) { + throw new \Exception('Missing class schema for proxy class ' . $proxyClassName); + } foreach ($classSchema->getProperties() as $property => $propertySchema) { // Currently, private properties are transient. Should this behaviour change, they need to be included // in the value hash generation @@ -138,6 +141,6 @@ public function generateValueHash(JoinPointInterface $joinPoint) $serializedSource = ($this->useIgBinary === true) ? igbinary_serialize($hashSourceParts) : serialize($hashSourceParts); $proxy = $joinPoint->getProxy(); - ObjectAccess::setProperty($proxy, 'Persistence_Object_Identifier', sha1($serializedSource), true); + ObjectAccess::setProperty($proxy, 'Persistence_Object_Identifier', sha1($serializedSource ?: ''), true); } } diff --git a/Neos.Flow/Classes/Persistence/Doctrine/ArrayTypeConverter.php b/Neos.Flow/Classes/Persistence/Doctrine/ArrayTypeConverter.php index 46bdd4ae3c..0a2de45843 100644 --- a/Neos.Flow/Classes/Persistence/Doctrine/ArrayTypeConverter.php +++ b/Neos.Flow/Classes/Persistence/Doctrine/ArrayTypeConverter.php @@ -63,16 +63,15 @@ class ArrayTypeConverter extends AbstractTypeConverter * * @param mixed $source * @param string $targetType - * @param array $convertedChildProperties - * @param PropertyMappingConfigurationInterface $configuration - * @return mixed|Error the target type, or an error object if a user-error occurred + * @param array $convertedChildProperties + * @return array the target type, or an error object if a user-error occurred * @throws TypeConverterException thrown in case a developer error occurred * @api */ public function convertFrom($source, $targetType, array $convertedChildProperties = [], ?PropertyMappingConfigurationInterface $configuration = null) { $result = []; - $convertElements = $configuration->getConfigurationValue(ArrayTypeConverter::class, self::CONFIGURATION_CONVERT_ELEMENTS); + $convertElements = $configuration?->getConfigurationValue(ArrayTypeConverter::class, self::CONFIGURATION_CONVERT_ELEMENTS) ?: true; foreach ($source as $element) { if ($convertElements === true) { $element = $this->propertyMapper->convert($element, 'array', $configuration); diff --git a/Neos.Flow/Classes/Persistence/Doctrine/CountWalker.php b/Neos.Flow/Classes/Persistence/Doctrine/CountWalker.php index c1695d1b3a..f98cfeb319 100644 --- a/Neos.Flow/Classes/Persistence/Doctrine/CountWalker.php +++ b/Neos.Flow/Classes/Persistence/Doctrine/CountWalker.php @@ -34,7 +34,7 @@ public function walkSelectStatement(SelectStatement $AST) $parent = null; $parentName = null; foreach ($this->getQueryComponents() as $dqlAlias => $qComp) { - if ($qComp['parent'] === null && $qComp['nestingLevel'] === 0) { + if (($qComp['parent'] ?? null) === null && $qComp['nestingLevel'] === 0) { $parent = $qComp; $parentName = $dqlAlias; break; @@ -45,10 +45,13 @@ public function walkSelectStatement(SelectStatement $AST) $AST->selectClause->isDistinct = true; } + if (!is_string($parentName)) { + throw new \Exception('Missing parent name', 1744138349); + } $pathExpression = new PathExpression( PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $parentName, - $parent['metadata']->getSingleIdentifierFieldName() + ($parent['metadata'] ?? null)?->getSingleIdentifierFieldName() ); $pathExpression->type = PathExpression::TYPE_STATE_FIELD; diff --git a/Neos.Flow/Classes/Persistence/Doctrine/DataTypes/JsonArrayType.php b/Neos.Flow/Classes/Persistence/Doctrine/DataTypes/JsonArrayType.php index 33f829684a..111c0c131a 100755 --- a/Neos.Flow/Classes/Persistence/Doctrine/DataTypes/JsonArrayType.php +++ b/Neos.Flow/Classes/Persistence/Doctrine/DataTypes/JsonArrayType.php @@ -60,7 +60,7 @@ public function getMappedDatabaseTypes(AbstractPlatform $platform): array * * @param mixed $value The value to convert. * @param AbstractPlatform $platform The currently used database platform. - * @return array|null The PHP representation of the value. + * @return array|null The PHP representation of the value. * @throws ConversionException * @throws TypeConverterException */ @@ -124,11 +124,15 @@ protected function initializeDependencies(): void * Traverses the $array and replaces known persisted objects (tuples of * type and identifier) with actual instances. * + * @param array $array * @throws TypeConverterException */ protected function decodeObjectReferences(array &$array): void { - assert($this->persistenceManager instanceof PersistenceManagerInterface); + $persistenceManager = $this->persistenceManager; + if (!$persistenceManager) { + throw new \Exception('PersistenceManager is missing', 1744138182); + } foreach ($array as &$value) { if (!is_array($value)) { @@ -138,7 +142,7 @@ protected function decodeObjectReferences(array &$array): void if (isset($value['__value_object_value'], $value['__value_object_type'])) { $value = self::deserializeValueObject($value); } elseif (isset($value['__flow_object_type'])) { - $value = $this->persistenceManager->getObjectByIdentifier($value['__identifier'], $value['__flow_object_type'], true); + $value = $persistenceManager->getObjectByIdentifier($value['__identifier'], $value['__flow_object_type'], true); } else { $this->decodeObjectReferences($value); } @@ -146,6 +150,7 @@ protected function decodeObjectReferences(array &$array): void } /** + * @param array $serializedValueObject * @throws \InvalidArgumentException * @throws TypeConverterException */ @@ -168,12 +173,16 @@ public static function deserializeValueObject(array $serializedValueObject): \Js * Traverses the $array and replaces known persisted objects with a tuple of * type and identifier. * + * @param array $array * @throws \RuntimeException * @throws \JsonException */ protected function encodeObjectReferences(array &$array): void { - assert($this->persistenceManager instanceof PersistenceManagerInterface); + $persistenceManager = $this->persistenceManager; + if (!$persistenceManager) { + throw new \Exception('PersistenceManager is missing', 1744138137); + } foreach ($array as &$value) { if (is_array($value)) { @@ -197,7 +206,7 @@ protected function encodeObjectReferences(array &$array): void throw new \RuntimeException('Collection in array properties is not supported', 1375196581); } elseif ($value instanceof \ArrayObject) { throw new \RuntimeException('ArrayObject in array properties is not supported', 1375196582); - } elseif ($this->persistenceManager->isNewObject($value) === false + } elseif ($persistenceManager->isNewObject($value) === false && ( $this->reflectionService->isClassAnnotatedWith($propertyClassName, Flow\Entity::class) || $this->reflectionService->isClassAnnotatedWith($propertyClassName, Flow\ValueObject::class) @@ -206,7 +215,7 @@ protected function encodeObjectReferences(array &$array): void ) { $value = [ '__flow_object_type' => $propertyClassName, - '__identifier' => $this->persistenceManager->getIdentifierByObject($value) + '__identifier' => $persistenceManager->getIdentifierByObject($value) ]; } elseif ($value instanceof \JsonSerializable && DenormalizingObjectConverter::isDenormalizable(get_class($value)) @@ -219,6 +228,7 @@ protected function encodeObjectReferences(array &$array): void /** * @throws \RuntimeException * @throws \JsonException + * @return array */ public static function serializeValueObject(\JsonSerializable $valueObject): array { diff --git a/Neos.Flow/Classes/Persistence/Doctrine/EntityManagerConfiguration.php b/Neos.Flow/Classes/Persistence/Doctrine/EntityManagerConfiguration.php index 93e19e6942..3225e80660 100644 --- a/Neos.Flow/Classes/Persistence/Doctrine/EntityManagerConfiguration.php +++ b/Neos.Flow/Classes/Persistence/Doctrine/EntityManagerConfiguration.php @@ -99,7 +99,7 @@ public function configureEntityManager(Connection $connection, Configuration $co } /** - * @param array $configuredSubscribers + * @param array $configuredSubscribers * @param EventManager $eventManager * @throws IllegalObjectTypeException */ @@ -115,7 +115,7 @@ protected function registerEventSubscribers(array $configuredSubscribers, EventM } /** - * @param array $configuredListeners + * @param array, events: string|array}> $configuredListeners * @param EventManager $eventManager */ protected function registerEventListeners(array $configuredListeners, EventManager $eventManager): void @@ -130,7 +130,7 @@ protected function registerEventListeners(array $configuredListeners, EventManag * Apply configured settings regarding DQL to the Doctrine Configuration. * At the moment, these are custom DQL functions. * - * @param array $configuredSettings + * @param array $configuredSettings * @param Configuration $doctrineConfiguration * @return void */ @@ -174,7 +174,7 @@ protected function applyCacheConfiguration(Configuration $config): void /** * Apply configured settings regarding Doctrine's second level cache. * - * @param array $configuredSettings + * @param array $configuredSettings * @param Configuration $doctrineConfiguration * @return void * @throws NoSuchCacheException diff --git a/Neos.Flow/Classes/Persistence/Doctrine/EntityManagerFactory.php b/Neos.Flow/Classes/Persistence/Doctrine/EntityManagerFactory.php index 6380139d5e..3fc9c34544 100644 --- a/Neos.Flow/Classes/Persistence/Doctrine/EntityManagerFactory.php +++ b/Neos.Flow/Classes/Persistence/Doctrine/EntityManagerFactory.php @@ -55,7 +55,7 @@ class EntityManagerFactory protected $environment; /** - * @var array + * @var array */ protected $settings = []; @@ -63,7 +63,7 @@ class EntityManagerFactory * Injects the Flow settings, the persistence part is kept * for further use. * - * @param array $settings + * @param array> $settings * @return void * @throws InvalidConfigurationException */ @@ -138,7 +138,7 @@ public function create() * @param EventManager $eventManager * @Flow\Signal */ - public function emitBeforeDoctrineEntityManagerCreation(Connection $connection, Configuration $config, EventManager $eventManager) + public function emitBeforeDoctrineEntityManagerCreation(Connection $connection, Configuration $config, EventManager $eventManager): void { } @@ -147,7 +147,7 @@ public function emitBeforeDoctrineEntityManagerCreation(Connection $connection, * @param EntityManager $entityManager * @Flow\Signal */ - public function emitAfterDoctrineEntityManagerCreation(Configuration $config, EntityManager $entityManager) + public function emitAfterDoctrineEntityManagerCreation(Configuration $config, EntityManager $entityManager): void { } diff --git a/Neos.Flow/Classes/Persistence/Doctrine/Mapping/ClassMetadata.php b/Neos.Flow/Classes/Persistence/Doctrine/Mapping/ClassMetadata.php index 4a9fc7c8dc..5e24fd0d14 100644 --- a/Neos.Flow/Classes/Persistence/Doctrine/Mapping/ClassMetadata.php +++ b/Neos.Flow/Classes/Persistence/Doctrine/Mapping/ClassMetadata.php @@ -17,6 +17,8 @@ /** * A ClassMetadata instance holds all the object-relational mapping metadata * of an entity and it's associations. + * + * @extends \Doctrine\ORM\Mapping\ClassMetadata */ class ClassMetadata extends \Doctrine\ORM\Mapping\ClassMetadata { diff --git a/Neos.Flow/Classes/Persistence/Doctrine/Mapping/ClassMetadataFactory.php b/Neos.Flow/Classes/Persistence/Doctrine/Mapping/ClassMetadataFactory.php index eecf79fab8..ee630624e4 100644 --- a/Neos.Flow/Classes/Persistence/Doctrine/Mapping/ClassMetadataFactory.php +++ b/Neos.Flow/Classes/Persistence/Doctrine/Mapping/ClassMetadataFactory.php @@ -21,7 +21,7 @@ class ClassMetadataFactory extends \Doctrine\ORM\Mapping\ClassMetadataFactory /** * Creates a new ClassMetadata instance for the given class name. * - * @param string $className + * @param class-string $className * @return ClassMetadata */ protected function newClassMetadataInstance($className) diff --git a/Neos.Flow/Classes/Persistence/Doctrine/Mapping/Driver/FlowAnnotationDriver.php b/Neos.Flow/Classes/Persistence/Doctrine/Mapping/Driver/FlowAnnotationDriver.php index 8539422da6..d047a535f5 100644 --- a/Neos.Flow/Classes/Persistence/Doctrine/Mapping/Driver/FlowAnnotationDriver.php +++ b/Neos.Flow/Classes/Persistence/Doctrine/Mapping/Driver/FlowAnnotationDriver.php @@ -14,6 +14,7 @@ use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Common\Annotations\IndexedReader; use Doctrine\Common\Annotations\Reader; +use Doctrine\ORM\Mapping\MappingAttribute; use Doctrine\Persistence\Mapping\ClassMetadata; use Doctrine\Persistence\Mapping\Driver\MappingDriver as DoctrineMappingDriverInterface; use Doctrine\ORM\EntityManagerInterface; @@ -70,7 +71,7 @@ class FlowAnnotationDriver implements DoctrineMappingDriverInterface, PointcutFi protected $entityManager; /** - * @var array + * @var ?array */ protected $classNames; @@ -126,7 +127,7 @@ protected function getClassSchema($className) /** * Check for $className being an aggregate root. * - * @param string $className + * @param class-string $className * @param string $propertySourceHint * @return boolean * @throws ClassSchemaNotFoundException @@ -146,7 +147,7 @@ protected function isAggregateRoot($className, $propertySourceHint) /** * Check for $className being a value object. * - * @param string $className + * @param class-string $className * @param string $propertySourceHint * @return boolean * @throws ClassSchemaNotFoundException @@ -166,8 +167,8 @@ protected function isValueObject($className, $propertySourceHint) /** * Loads the metadata for the specified class into the provided container. * - * @param string $className - * @param ClassMetadata $metadata + * @param class-string $className + * @param ClassMetadata $metadata * @return void * @throws ORM\MappingException * @throws \UnexpectedValueException @@ -178,9 +179,8 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) * This is the actual type we have at this point, but we cannot change the * signature due to inheritance. * - * @var ORM\ClassMetadata $metadata + * @var ORM\ClassMetadata $metadata */ - try { $class = $metadata->getReflectionClass(); $classSchema = $this->getClassSchema($class->getName()); @@ -191,24 +191,28 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) // Evaluate Entity annotation if (isset($classAnnotations[ORM\MappedSuperclass::class])) { + /** @var ORM\MappedSuperclass $mappedSuperclassAnnotation */ $mappedSuperclassAnnotation = $classAnnotations[ORM\MappedSuperclass::class]; if ($mappedSuperclassAnnotation->repositoryClass !== null) { $metadata->setCustomRepositoryClass($mappedSuperclassAnnotation->repositoryClass); } $metadata->isMappedSuperclass = true; } elseif (isset($classAnnotations[Flow\Entity::class]) || isset($classAnnotations[ORM\Entity::class])) { - $entityAnnotation = isset($classAnnotations[Flow\Entity::class]) ? $classAnnotations[Flow\Entity::class] : $classAnnotations[ORM\Entity::class]; - if ($entityAnnotation->repositoryClass !== null) { + /** @var Flow\Entity|ORM\Entity $entityAnnotation */ + $entityAnnotation = $classAnnotations[Flow\Entity::class] ?? $classAnnotations[ORM\Entity::class]; + if ($entityAnnotation->repositoryClass !== null && is_subclass_of($entityAnnotation->repositoryClass, EntityRepository::class)) { $metadata->setCustomRepositoryClass($entityAnnotation->repositoryClass); } elseif ($classSchema->getRepositoryClassName() !== null) { - if ($this->reflectionService->isClassImplementationOf($classSchema->getRepositoryClassName(), EntityRepository::class)) { - $metadata->setCustomRepositoryClass($classSchema->getRepositoryClassName()); + $repositoryClassName = $classSchema->getRepositoryClassName(); + if (is_subclass_of($repositoryClassName, EntityRepository::class)) { + $metadata->setCustomRepositoryClass($repositoryClassName); } } if ($entityAnnotation->readOnly) { $metadata->markReadOnly(); } } elseif (isset($classAnnotations[Flow\ValueObject::class])) { + /** @var Flow\ValueObject $valueObjectAnnotation */ $valueObjectAnnotation = $classAnnotations[Flow\ValueObject::class]; if ($valueObjectAnnotation->embedded === true) { $metadata->isEmbeddedClass = true; @@ -225,6 +229,7 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) // Evaluate Table annotation $primaryTable = []; if (isset($classAnnotations[ORM\Table::class])) { + /** @var ORM\Table $tableAnnotation */ $tableAnnotation = $classAnnotations[ORM\Table::class]; $primaryTable = [ 'name' => $tableAnnotation->name, @@ -276,6 +281,7 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) // Evaluate @Cache annotation if (isset($classAnnotations[ORM\Cache::class])) { + /** @var ORM\Cache $cacheAnnotation */ $cacheAnnotation = $classAnnotations[ORM\Cache::class]; $cacheMap = [ 'region' => $cacheAnnotation->region, @@ -287,6 +293,7 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) // Evaluate NamedNativeQueries annotation if (isset($classAnnotations[ORM\NamedNativeQueries::class])) { + /** @var ORM\NamedNativeQueries $namedNativeQueriesAnnotation */ $namedNativeQueriesAnnotation = $classAnnotations[ORM\NamedNativeQueries::class]; foreach ($namedNativeQueriesAnnotation->value as $namedNativeQuery) { @@ -301,6 +308,7 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) // Evaluate SqlResultSetMappings annotation if (isset($classAnnotations[ORM\SqlResultSetMappings::class])) { + /** @var ORM\SqlResultSetMappings $sqlResultSetMappingsAnnotation */ $sqlResultSetMappingsAnnotation = $classAnnotations[ORM\SqlResultSetMappings::class]; foreach ($sqlResultSetMappingsAnnotation->value as $resultSetMapping) { @@ -339,16 +347,10 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) // Evaluate NamedQueries annotation if (isset($classAnnotations[ORM\NamedQueries::class])) { + /** @var ORM\NamedQueries $namedQueriesAnnotation */ $namedQueriesAnnotation = $classAnnotations[ORM\NamedQueries::class]; - if (!is_array($namedQueriesAnnotation->value)) { - throw new \UnexpectedValueException('@ORM\NamedQueries should contain an array of @ORM\NamedQuery annotations.'); - } - foreach ($namedQueriesAnnotation->value as $namedQuery) { - if (!($namedQuery instanceof ORM\NamedQuery)) { - throw new \UnexpectedValueException('@ORM\NamedQueries should contain an array of @ORM\NamedQuery annotations.'); - } $metadata->addNamedQuery([ 'name' => $namedQuery->name, 'query' => $namedQuery->query @@ -358,17 +360,19 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) // Evaluate InheritanceType annotation if (isset($classAnnotations[ORM\InheritanceType::class])) { + /** @var ORM\InheritanceType $inheritanceTypeAnnotation */ $inheritanceTypeAnnotation = $classAnnotations[ORM\InheritanceType::class]; $inheritanceType = constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . strtoupper($inheritanceTypeAnnotation->value)); if ($inheritanceType !== ORM\ClassMetadata::INHERITANCE_TYPE_NONE) { // Evaluate DiscriminatorColumn annotation if (isset($classAnnotations[ORM\DiscriminatorColumn::class])) { + /** @var ORM\DiscriminatorColumn $discriminatorColumnAnnotation */ $discriminatorColumnAnnotation = $classAnnotations[ORM\DiscriminatorColumn::class]; $discriminatorColumn = [ 'name' => $discriminatorColumnAnnotation->name, - 'type' => $discriminatorColumnAnnotation->type, - 'length' => $discriminatorColumnAnnotation->length, + 'type' => $discriminatorColumnAnnotation->type ?: throw new \Exception('Cannot use untyped discriminator column', 1744137386), + 'length' => $discriminatorColumnAnnotation->length ?: throw new \Exception('Cannot use discriminator column without length', 1744137404), 'columnDefinition' => $discriminatorColumnAnnotation->columnDefinition ]; } else { @@ -379,6 +383,7 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) // Evaluate DiscriminatorMap annotation if (isset($classAnnotations[ORM\DiscriminatorMap::class])) { + /** @var ORM\DiscriminatorMap $discriminatorMapAnnotation */ $discriminatorMapAnnotation = $classAnnotations[ORM\DiscriminatorMap::class]; $discriminatorMap = $discriminatorMapAnnotation->value; } else { @@ -407,8 +412,11 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) // Evaluate DoctrineChangeTrackingPolicy annotation if (isset($classAnnotations[ORM\ChangeTrackingPolicy::class])) { + /** @var ORM\ChangeTrackingPolicy $changeTrackingAnnotation */ $changeTrackingAnnotation = $classAnnotations[ORM\ChangeTrackingPolicy::class]; - $metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' . strtoupper($changeTrackingAnnotation->value))); + $metadata->setChangeTrackingPolicy(constant( + 'Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' . strtoupper($changeTrackingAnnotation->value) + )); } else { $metadata->setChangeTrackingPolicy(ORM\ClassMetadata::CHANGETRACKING_DEFERRED_EXPLICIT); } @@ -425,7 +433,11 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) && !isset($primaryTable['uniqueConstraints'])) { $idProperties = array_keys($classSchema->getIdentityProperties()); if (array_diff($idProperties, $metadata->getIdentifierFieldNames()) !== []) { - $uniqueIndexName = $this->truncateIdentifier('flow_identity_' . $primaryTable['name']); + $primaryTableName = $primaryTable['name'] ?? null; + if (!$primaryTableName) { + throw new \Exception('Missing primary table name', 1744135393); + } + $uniqueIndexName = $this->truncateIdentifier('flow_identity_' . $primaryTableName); foreach ($idProperties as $idProperty) { $primaryTable['uniqueConstraints'][$uniqueIndexName]['columns'][] = $metadata->fieldMappings[$idProperty]['columnName'] ?? strtolower($idProperty); } @@ -534,11 +546,11 @@ protected function buildJoinTableColumnName($className) * Check if the referenced column name is set (and valid) and if not make sure * it is initialized properly. * - * @param array $joinColumns - * @param array $mapping + * @param array $joinColumns + * @param array $mapping * @param \ReflectionProperty $property * @param integer $direction regular or inverse mapping (use is to be coded) - * @return array + * @return array */ protected function buildJoinColumnsIfNeeded(array $joinColumns, array $mapping, \ReflectionProperty $property, $direction = self::MAPPING_REGULAR) { @@ -574,7 +586,7 @@ protected function buildJoinColumnsIfNeeded(array $joinColumns, array $mapping, /** * Evaluate the property annotations and amend the metadata accordingly. * - * @param ORM\ClassMetadataInfo $metadata + * @param ORM\ClassMetadataInfo $metadata * @return void * @throws ORM\MappingException */ @@ -601,7 +613,7 @@ protected function evaluatePropertyAnnotations(ORM\ClassMetadataInfo $metadata) $mapping['fieldName'] = $property->getName(); $mapping['columnName'] = strtolower($property->getName()); $mapping['targetEntity'] = $propertyMetaData['type']; - $mapping['nullable'] = $propertyMetaData['nullable'] ?? false; + $mapping['nullable'] = $propertyMetaData['nullable']; $joinColumns = $this->evaluateJoinColumnAnnotations($property); @@ -621,13 +633,17 @@ protected function evaluatePropertyAnnotations(ORM\ClassMetadataInfo $metadata) $mapping['inversedBy'] = $oneToOneAnnotation->inversedBy; if ($oneToOneAnnotation->cascade !== null) { $mapping['cascade'] = $oneToOneAnnotation->cascade; + /** @phpstan-ignore argument.type (this really is a class-string -.-) */ } elseif ($this->isValueObject($mapping['targetEntity'], $className)) { $mapping['cascade'] = ['persist']; + /** @phpstan-ignore argument.type (this really is a class-string -.-) */ } elseif ($this->isAggregateRoot($mapping['targetEntity'], $className) === false) { $mapping['cascade'] = ['all']; } // We need to apply our value for non-aggregate roots first, because Doctrine sets a default value for orphanRemoval (see #1127) + /** @phpstan-ignore argument.type (this really is a class-string -.-) */ if ($this->isAggregateRoot($mapping['targetEntity'], $className) === false && + /** @phpstan-ignore argument.type (this really is a class-string -.-) */ $this->isValueObject($mapping['targetEntity'], $className) === false) { $mapping['orphanRemoval'] = true; } elseif ($oneToOneAnnotation->orphanRemoval !== null) { @@ -644,14 +660,18 @@ protected function evaluatePropertyAnnotations(ORM\ClassMetadataInfo $metadata) } if ($oneToManyAnnotation->cascade !== null) { $mapping['cascade'] = $oneToManyAnnotation->cascade; + /** @phpstan-ignore argument.type (this really is a class-string -.-) */ } elseif ($this->isValueObject($mapping['targetEntity'], $className)) { $mapping['cascade'] = ['persist']; + /** @phpstan-ignore argument.type (this really is a class-string -.-) */ } elseif ($this->isAggregateRoot($mapping['targetEntity'], $className) === false) { $mapping['cascade'] = ['all']; } $mapping['indexBy'] = $oneToManyAnnotation->indexBy; // We need to apply our value for non-aggregate roots first, because Doctrine sets a default value for orphanRemoval (see #1127) + /** @phpstan-ignore argument.type (this really is a class-string -.-) */ if ($this->isAggregateRoot($mapping['targetEntity'], $className) === false && + /** @phpstan-ignore argument.type (this really is a class-string -.-) */ $this->isValueObject($mapping['targetEntity'], $className) === false) { $mapping['orphanRemoval'] = true; } elseif ($oneToManyAnnotation->orphanRemoval !== null) { @@ -712,8 +732,10 @@ protected function evaluatePropertyAnnotations(ORM\ClassMetadataInfo $metadata) $mapping['joinColumns'] = $this->buildJoinColumnsIfNeeded($joinColumns, $mapping, $property); if ($manyToOneAnnotation->cascade !== null) { $mapping['cascade'] = $manyToOneAnnotation->cascade; + /** @phpstan-ignore argument.type (this really is a class-string -.-) */ } elseif ($this->isValueObject($mapping['targetEntity'], $className)) { $mapping['cascade'] = ['persist']; + /** @phpstan-ignore argument.type (this really is a class-string -.-) */ } elseif ($this->isAggregateRoot($mapping['targetEntity'], $className) === false) { $mapping['cascade'] = ['all']; } @@ -749,8 +771,10 @@ protected function evaluatePropertyAnnotations(ORM\ClassMetadataInfo $metadata) $mapping['inversedBy'] = $manyToManyAnnotation->inversedBy; if ($manyToManyAnnotation->cascade !== null) { $mapping['cascade'] = $manyToManyAnnotation->cascade; + /** @phpstan-ignore argument.type (this really is a class-string -.-) */ } elseif ($this->isValueObject($mapping['targetEntity'], $className)) { $mapping['cascade'] = ['persist']; + /** @phpstan-ignore argument.type (this really is a class-string -.-) */ } elseif ($this->isAggregateRoot($mapping['targetEntity'], $className) === false) { $mapping['cascade'] = ['all']; } @@ -798,7 +822,7 @@ protected function evaluatePropertyAnnotations(ORM\ClassMetadataInfo $metadata) break; default: if (strpos($propertyMetaData['type'], '\\') !== false) { - if ($this->reflectionService->isClassAnnotatedWith($propertyMetaData['type'], Flow\ValueObject::class)) { + if (class_exists($propertyMetaData['type']) && $this->reflectionService->isClassAnnotatedWith($propertyMetaData['type'], Flow\ValueObject::class)) { $valueObjectAnnotation = $this->reflectionService->getClassAnnotation($propertyMetaData['type'], Flow\ValueObject::class); if ($valueObjectAnnotation && $valueObjectAnnotation->embedded === true) { $mapping['class'] = $propertyMetaData['type']; @@ -833,11 +857,11 @@ protected function evaluatePropertyAnnotations(ORM\ClassMetadataInfo $metadata) // Check for SequenceGenerator/TableGenerator definition if ($seqGeneratorAnnotation = $this->reader->getPropertyAnnotation($property, ORM\SequenceGenerator::class)) { - $metadata->setSequenceGeneratorDefinition([ + $metadata->setSequenceGeneratorDefinition(array_filter([ 'sequenceName' => $seqGeneratorAnnotation->sequenceName, 'allocationSize' => $seqGeneratorAnnotation->allocationSize, 'initialValue' => $seqGeneratorAnnotation->initialValue - ]); + ])); } elseif ($customGeneratorAnnotation = $this->reader->getPropertyAnnotation($property, ORM\CustomIdGenerator::class)) { $metadata->setCustomGeneratorDefinition([ 'class' => $customGeneratorAnnotation->class @@ -847,10 +871,10 @@ protected function evaluatePropertyAnnotations(ORM\ClassMetadataInfo $metadata) // Evaluate @Cache annotation if (($cacheAnnotation = $this->reader->getPropertyAnnotation($property, ORM\Cache::class)) !== null) { - $metadata->enableAssociationCache($mapping['fieldName'], [ - 'usage' => constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAnnotation->usage), - 'region' => $cacheAnnotation->region, - ]); + $metadata->enableAssociationCache($mapping['fieldName'], array_filter([ + 'usage' => constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAnnotation->usage), + 'region' => $cacheAnnotation->region, + ])); } } } @@ -861,8 +885,8 @@ protected function evaluatePropertyAnnotations(ORM\ClassMetadataInfo $metadata) * @param ORM\JoinTable $joinTableAnnotation * @param \ReflectionProperty $property * @param string $className - * @param array $mapping - * @return array + * @param array $mapping + * @return array{name: string, schema: ?string, joinColumns: array, inverseJoinColumns: array} */ protected function evaluateJoinTableAnnotation(ORM\JoinTable $joinTableAnnotation, \ReflectionProperty $property, $className, array $mapping) { @@ -908,12 +932,12 @@ protected function evaluateJoinTableAnnotation(ORM\JoinTable $joinTableAnnotatio } /** - * Check for and build JoinColummn/JoinColumns annotations. + * Check for and build JoinColumn/JoinColumns annotations. * * If no annotations are found, a default is returned. * * @param \ReflectionProperty $property - * @return array + * @return array> */ protected function evaluateJoinColumnAnnotations(\ReflectionProperty $property) { @@ -933,8 +957,8 @@ protected function evaluateJoinColumnAnnotations(\ReflectionProperty $property) /** * Evaluate the association overrides annotations and amend the metadata accordingly. * - * @param array $classAnnotations - * @param ORM\ClassMetadataInfo $metadata + * @param array $classAnnotations + * @param ORM\ClassMetadataInfo $metadata * @return void */ protected function evaluateOverridesAnnotations(array $classAnnotations, ORM\ClassMetadataInfo $metadata) @@ -992,9 +1016,9 @@ protected function evaluateOverridesAnnotations(array $classAnnotations, ORM\Cla /** * Evaluate the EntityListeners annotation and amend the metadata accordingly. * - * @param \ReflectionClass $class - * @param ORM\ClassMetadata $metadata - * @param array $classAnnotations + * @param \ReflectionClass $class + * @param ORM\ClassMetadata $metadata + * @param array $classAnnotations * @return void * @throws ORM\MappingException */ @@ -1007,7 +1031,7 @@ protected function evaluateEntityListenersAnnotation(\ReflectionClass $class, OR $listenerClassName = $metadata->fullyQualifiedClassName($item); if ($listenerClassName === null || !class_exists($listenerClassName)) { - throw ORM\MappingException::entityListenerClassNotFound($listenerClassName, $class->getName()); + throw ORM\MappingException::entityListenerClassNotFound($listenerClassName ?: 'null', $class->getName()); } $hasMapping = false; @@ -1034,8 +1058,8 @@ protected function evaluateEntityListenersAnnotation(\ReflectionClass $class, OR /** * Evaluate the lifecycle annotations and amend the metadata accordingly. * - * @param \ReflectionClass $class - * @param ORM\ClassMetadataInfo $metadata + * @param \ReflectionClass $class + * @param ORM\ClassMetadataInfo $metadata * @return void */ protected function evaluateLifeCycleAnnotations(\ReflectionClass $class, ORM\ClassMetadataInfo $metadata) @@ -1058,7 +1082,7 @@ protected function evaluateLifeCycleAnnotations(\ReflectionClass $class, ORM\Cla * Returns an array of callbacks for lifecycle annotations on the given method. * * @param \ReflectionMethod $method - * @return array + * @return array> */ protected function getMethodCallbacks(\ReflectionMethod $method) { @@ -1138,7 +1162,7 @@ public function isTransient($className) /** * Returns the names of all mapped (non-transient) classes known to this driver. * - * @return array + * @return array */ public function getAllClassNames() { @@ -1169,7 +1193,7 @@ function ($className) { * * @param ORM\JoinColumn $joinColumnAnnotation * @param string $propertyName - * @return array + * @return array{name: ?string, unique: bool, nullable: bool, onDelete: mixed, columnDefinition: ?string, referencedColumnName: string} */ protected function joinColumnToArray(ORM\JoinColumn $joinColumnAnnotation, $propertyName = null) { @@ -1187,9 +1211,9 @@ protected function joinColumnToArray(ORM\JoinColumn $joinColumnAnnotation, $prop * Parse the given Column into an array * * @param ORM\Column $columnAnnotation - * @param array $mapping + * @param array $mapping * @param string $fieldName - * @return array + * @return array */ protected function addColumnToMappingArray(ORM\Column $columnAnnotation, array $mapping = [], $fieldName = null) { @@ -1222,11 +1246,12 @@ protected function addColumnToMappingArray(ORM\Column $columnAnnotation, array $ /** * Returns the classname after stripping a potentially present Compiler::ORIGINAL_CLASSNAME_SUFFIX. * - * @param string $className - * @return string + * @param class-string $className + * @return class-string */ protected function getUnproxiedClassName($className) { + /** @var class-string $className */ $className = preg_replace('/' . Compiler::ORIGINAL_CLASSNAME_SUFFIX . '$/', '', $className); return $className; @@ -1253,7 +1278,7 @@ private function getFetchMode($className, $fetchMode) /** * Checks if the specified class has a property annotated with Id * - * @param string $className Name of the class to check against + * @param class-string $className Name of the class to check against * @param string $methodName Name of the method to check against * @param string $methodDeclaringClassName Name of the class the method was originally declared in * @param mixed $pointcutQueryIdentifier Some identifier for this query - must at least differ from a previous identifier. Used for circular reference detection. @@ -1284,7 +1309,7 @@ public function hasRuntimeEvaluationsDefinition() /** * Returns runtime evaluations for a previously matched pointcut * - * @return array Runtime evaluations + * @return array Runtime evaluations */ public function getRuntimeEvaluationsDefinition() { diff --git a/Neos.Flow/Classes/Persistence/Doctrine/MigrationFinder.php b/Neos.Flow/Classes/Persistence/Doctrine/MigrationFinder.php index 93555a066c..ced9438bae 100644 --- a/Neos.Flow/Classes/Persistence/Doctrine/MigrationFinder.php +++ b/Neos.Flow/Classes/Persistence/Doctrine/MigrationFinder.php @@ -45,7 +45,7 @@ public function findMigrations(string $directory, ?string $namespace = null): ar $this->databasePlatformName ]); if (is_dir($path)) { - $files[] = glob($path . '/Version*.php'); + $files[] = glob($path . '/Version*.php') ?: []; } } diff --git a/Neos.Flow/Classes/Persistence/Doctrine/ObjectValidationAndDeDuplicationListener.php b/Neos.Flow/Classes/Persistence/Doctrine/ObjectValidationAndDeDuplicationListener.php index 92881f78b5..6732468bea 100644 --- a/Neos.Flow/Classes/Persistence/Doctrine/ObjectValidationAndDeDuplicationListener.php +++ b/Neos.Flow/Classes/Persistence/Doctrine/ObjectValidationAndDeDuplicationListener.php @@ -143,7 +143,7 @@ private function deduplicateValueObjectInsertions() * Validates the given object and throws an exception if validation fails. * * @param object $object - * @param \SplObjectStorage $validatedInstancesContainer + * @param \SplObjectStorage $validatedInstancesContainer * @return void * @throws ObjectValidationFailedException */ @@ -151,21 +151,18 @@ private function validateObject($object, \SplObjectStorage $validatedInstancesCo { $className = $this->entityManager->getClassMetadata(get_class($object))->getName(); $validator = $this->validatorResolver->getBaseValidatorConjunction($className, ['Persistence', 'Default']); - if ($validator === null) { - return; - } $validator->setValidatedInstancesContainer($validatedInstancesContainer); $validationResult = $validator->validate($object); - if ($validationResult->hasErrors()) { + if ($validationResult?->hasErrors()) { $errorMessages = ''; $errorCount = 0; - $allErrors = $validationResult->getFlattenedErrors(); + $allErrors = $validationResult->getFlattenedErrors() ?: []; foreach ($allErrors as $path => $errors) { $errorMessages .= $path . ':' . PHP_EOL; foreach ($errors as $error) { $errorCount++; - $errorMessages .= (string)$error . PHP_EOL; + $errorMessages .= $error . PHP_EOL; } } throw new ObjectValidationFailedException('An instance of "' . get_class($object) . '" failed to pass validation with ' . $errorCount . ' error(s): ' . PHP_EOL . $errorMessages, 1322585164); diff --git a/Neos.Flow/Classes/Persistence/Doctrine/PersistenceManager.php b/Neos.Flow/Classes/Persistence/Doctrine/PersistenceManager.php index be1c1127c3..9be04cdcef 100644 --- a/Neos.Flow/Classes/Persistence/Doctrine/PersistenceManager.php +++ b/Neos.Flow/Classes/Persistence/Doctrine/PersistenceManager.php @@ -151,7 +151,7 @@ public function clearState(): void */ public function isNewObject($object): bool { - if (!$object instanceof PersistenceMagicInterface) { + if (!($object instanceof PersistenceMagicInterface)) { return true; } diff --git a/Neos.Flow/Classes/Persistence/Doctrine/Query.php b/Neos.Flow/Classes/Persistence/Doctrine/Query.php index 62ddd3ed8a..44592cb81d 100644 --- a/Neos.Flow/Classes/Persistence/Doctrine/Query.php +++ b/Neos.Flow/Classes/Persistence/Doctrine/Query.php @@ -64,7 +64,7 @@ class Query implements QueryInterface protected $entityManager; /** - * @var array + * @var array */ protected $settings; @@ -74,7 +74,7 @@ class Query implements QueryInterface protected $constraint; /** - * @var array + * @var array */ protected $orderings = []; @@ -104,7 +104,7 @@ class Query implements QueryInterface protected $parameters; /** - * @var array + * @var ?array */ protected $joins; @@ -140,10 +140,10 @@ public function injectEntityManager(EntityManagerInterface $entityManager) * Injects the Flow settings, the persistence part is kept * for further use. * - * @param array $settings + * @param array $settings * @return void */ - public function injectSettings(array $settings) + public function injectSettings(array $settings): void { $this->settings = $settings['persistence']; } @@ -151,7 +151,7 @@ public function injectSettings(array $settings) /** * @param LoggerInterface $logger */ - public function injectLogger(LoggerInterface $logger) + public function injectLogger(LoggerInterface $logger): void { $this->logger = $logger; } @@ -186,7 +186,7 @@ public function execute(bool $cacheResult = false): QueryResultInterface * Really executes the query on the database. * This should only ever be executed from the QueryResult class. * - * @return array result set + * @return mixed result set * @throws DatabaseException * @throws DatabaseConnectionException * @throws DatabaseStructureException @@ -283,7 +283,7 @@ public function count(): int * 'bar' => \Neos\Flow\Persistence\QueryInterface::ORDER_DESCENDING * ) * - * @param array $orderings The property names to order by + * @param array $orderings The property names to order by * @return QueryInterface * @api */ @@ -304,7 +304,7 @@ public function setOrderings(array $orderings): QueryInterface * 'bar' => \Neos\Flow\Persistence\QueryInterface::ORDER_DESCENDING * ) * - * @return array + * @return array * @api */ public function getOrderings(): array @@ -393,7 +393,7 @@ public function getOffset(): ?int * The constraint used to limit the result set. Returns $this to allow * for chaining (fluid interface) * - * @param object $constraint Some constraint, depending on the backend + * @param object|string $constraint Some constraint, depending on the backend * @return QueryInterface * @api */ @@ -456,7 +456,7 @@ public function logicalOr(mixed $constraint1, mixed ...$constraints) /** * Performs a logical negation of the given constraint * - * @param object $constraint Constraint to negate + * @param object|string $constraint Constraint to negate * @return object * @api */ @@ -618,7 +618,7 @@ public function greaterThanOrEqual(string $propertyName, $operand) /** * Add parameters to the query * - * @param array $parameters + * @param array $parameters * @return void */ public function addParameters($parameters) @@ -632,7 +632,7 @@ public function addParameters($parameters) /** * Gets all defined query parameters for the query being constructed. * - * @return ArrayCollection + * @return ArrayCollection */ public function getParameters() { @@ -717,7 +717,7 @@ public function __wakeup() $this->queryBuilder->where($this->constraint); } - if (is_array($this->orderings)) { + if ($this->orderings !== []) { $aliases = $this->queryBuilder->getRootAliases(); foreach ($this->orderings as $propertyName => $order) { $this->queryBuilder->addOrderBy($aliases[0] . '.' . $propertyName, $order); @@ -732,7 +732,7 @@ public function __wakeup() $this->queryBuilder->setMaxResults($this->limit); $this->queryBuilder->distinct($this->distinct); $this->queryBuilder->setParameters($this->parameters); - unset($this->parameters); + $this->parameters = new ArrayCollection(); } /** diff --git a/Neos.Flow/Classes/Persistence/Doctrine/QueryResult.php b/Neos.Flow/Classes/Persistence/Doctrine/QueryResult.php index e8004a88ab..a97d42307b 100644 --- a/Neos.Flow/Classes/Persistence/Doctrine/QueryResult.php +++ b/Neos.Flow/Classes/Persistence/Doctrine/QueryResult.php @@ -23,7 +23,7 @@ class QueryResult implements QueryResultInterface { /** - * @var array + * @var ?array * @Flow\Transient */ protected $rows; @@ -50,6 +50,7 @@ public function __construct(Query $query) /** * Loads the objects this QueryResult is supposed to hold * + * @phpstan-assert array $this->rows * @return void */ protected function initialize() @@ -110,7 +111,7 @@ public function count(): int /** * Returns an array with the objects in the result set * - * @return array + * @return array * @api */ public function toArray(): array @@ -209,6 +210,6 @@ public function rewind(): void public function valid(): bool { $this->initialize(); - return current($this->rows) !== false; + return current($this->rows ?: []) !== false; } } diff --git a/Neos.Flow/Classes/Persistence/Doctrine/Repository.php b/Neos.Flow/Classes/Persistence/Doctrine/Repository.php index 5749463bea..87017f174f 100644 --- a/Neos.Flow/Classes/Persistence/Doctrine/Repository.php +++ b/Neos.Flow/Classes/Persistence/Doctrine/Repository.php @@ -13,8 +13,8 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\QueryBuilder; -use Doctrine\Persistence\Mapping\ClassMetadata; use Neos\Flow\Annotations as Flow; use Neos\Flow\Persistence\Exception\IllegalObjectTypeException; use Neos\Flow\Persistence\Exception\UnknownObjectException; @@ -26,6 +26,7 @@ /** * The Flow default Repository, based on Doctrine 2 * + * @extends EntityRepository * @api */ abstract class Repository extends EntityRepository implements RepositoryInterface @@ -50,7 +51,7 @@ abstract class Repository extends EntityRepository implements RepositoryInterfac protected $objectType; /** - * @var array + * @var array */ protected $defaultOrderings = []; @@ -58,7 +59,7 @@ abstract class Repository extends EntityRepository implements RepositoryInterfac * Initializes a new Repository. * * @param EntityManagerInterface $entityManager The EntityManager to use. - * @param ClassMetadata|null $classMetadata The class descriptor. + * @param ClassMetadata|null $classMetadata The class descriptor. */ public function __construct(EntityManagerInterface $entityManager, ?ClassMetadata $classMetadata = null) { @@ -70,8 +71,12 @@ public function __construct(EntityManagerInterface $entityManager, ?ClassMetadat } /** @var class-string $objectType */ $this->objectType = $objectType; + /** @var ?ClassMetadata $classMetadata */ $classMetadata = $entityManager->getClassMetadata($this->objectType); } + if (!$classMetadata) { + throw new \Exception('Unable to resolve class metadata for ' . $this->objectType); + } parent::__construct($entityManager, $classMetadata); $this->entityManager = $this->_em; } @@ -97,9 +102,8 @@ public function getEntityClassName(): string */ public function add($object): void { - if (!is_object($object) || !($object instanceof $this->objectType)) { - $type = (is_object($object) ? get_class($object) : gettype($object)); - throw new IllegalObjectTypeException('The value given to add() was ' . $type . ' , however the ' . get_class($this) . ' can only store ' . $this->objectType . ' instances.', 1517408062); + if (!($object instanceof $this->objectType)) { + throw new IllegalObjectTypeException('The value given to add() was ' . get_class($object) . ' , however the ' . get_class($this) . ' can only store ' . $this->objectType . ' instances.', 1517408062); } $this->entityManager->persist($object); } @@ -114,9 +118,8 @@ public function add($object): void */ public function remove($object): void { - if (!is_object($object) || !($object instanceof $this->objectType)) { - $type = (is_object($object) ? get_class($object) : gettype($object)); - throw new IllegalObjectTypeException('The value given to remove() was ' . $type . ' , however the ' . get_class($this) . ' can only handle ' . $this->objectType . ' instances.', 1517408067); + if (!($object instanceof $this->objectType)) { + throw new IllegalObjectTypeException('The value given to remove() was ' . get_class($object) . ' , however the ' . get_class($this) . ' can only handle ' . $this->objectType . ' instances.', 1517408067); } $this->entityManager->remove($object); } @@ -124,9 +127,10 @@ public function remove($object): void /** * Finds all entities in the repository. * - * @phpstan-ignore-next-line we don't satisfy the contract of doctrines repository as we don't return a simple array. - * @return QueryResultInterface The query result + * @return QueryResultInterface The query result * @api + * (don't know how generics work here yet and ignoring method.childReturnType has no effect) + * @phpstan-ignore-next-line */ public function findAll(): QueryResultInterface { @@ -136,7 +140,7 @@ public function findAll(): QueryResultInterface /** * Find all objects and return an IterableResult * - * @return iterable + * @return iterable */ public function findAllIterator(): iterable { @@ -222,7 +226,7 @@ public function removeAll(): void * 'bar' => \Neos\Flow\Persistence\QueryInterface::ORDER_DESCENDING * ) * - * @param array $defaultOrderings The property names to order by by default + * @param array $defaultOrderings The property names to order by by default * @return void * @api */ @@ -257,7 +261,7 @@ public function update($object): void * - countBy($value, $caseSensitive = true) * * @param string $method Name of the method - * @param array $arguments The arguments + * @param array $arguments The arguments * @return mixed The result of the repository method * @api */ diff --git a/Neos.Flow/Classes/Persistence/Doctrine/Service.php b/Neos.Flow/Classes/Persistence/Doctrine/Service.php index 2c18e36a20..c3d3496588 100644 --- a/Neos.Flow/Classes/Persistence/Doctrine/Service.php +++ b/Neos.Flow/Classes/Persistence/Doctrine/Service.php @@ -42,6 +42,7 @@ use Doctrine\Migrations\Version\ExecutionResult; use Doctrine\Migrations\Version\Version; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\MappingException; use Doctrine\ORM\Tools\SchemaTool; use Doctrine\ORM\Tools\SchemaValidator; @@ -97,7 +98,7 @@ class Service * Validates the metadata mapping for Doctrine, using the SchemaValidator * of Doctrine. * - * @return array + * @return array> */ public function validateMapping(): array { @@ -167,7 +168,7 @@ public function compileProxies(): void * Returns information about which entities exist and possibly if their * mapping information contains errors or not. * - * @return array + * @return array> */ public function getEntityStatus(): array { @@ -192,12 +193,12 @@ public function getEntityStatus(): array * Run DQL and return the result as-is. * * @param string $dql - * @param integer $hydrationMode + * @param 1|2|3|4|5|6|string|null $hydrationMode * @param int|null $firstResult * @param int|null $maxResult * @return mixed */ - public function runDql(string $dql, int $hydrationMode = \Doctrine\ORM\Query::HYDRATE_OBJECT, ?int $firstResult = null, ?int $maxResult = null) + public function runDql(string $dql, int|string|null $hydrationMode = \Doctrine\ORM\Query::HYDRATE_OBJECT, ?int $firstResult = null, ?int $maxResult = null) { $query = $this->entityManager->createQuery($dql); if ($firstResult !== null) { @@ -489,7 +490,7 @@ public function markAsMigrated(string $version, bool $markAsMigrated, ?string $o if ($version === 'all') { if ($markAsMigrated === false) { foreach ($executedMigrations->getItems() as $availableMigration) { - $this->mark($output, $availableMigration->getVersion(), false, $executedMigrations, !$markAsMigrated, $overrideMigrationFolderName); + $this->mark($output, $availableMigration->getVersion(), false, $executedMigrations, true, $overrideMigrationFolderName); } } @@ -619,7 +620,7 @@ public function getMigrationStatus(?string $overrideMigrationFolderName = null): * @param boolean $diffAgainstCurrent * @param string|null $filterExpression * @param string|null $overrideMigrationFolderName - * @return array Path to the new file + * @return array Path to the new file * @throws DBALException * @throws FilesException */ @@ -694,10 +695,10 @@ public function getMigrationFolderName(): string * * @param Schema $schema * @param AbstractPlatform $platform - * @param array $tableNames + * @param array $tableNames * @param string $search * @param string $replace - * @return array + * @return array{drop: array, add: array} */ public static function getForeignKeyHandlingSql(Schema $schema, AbstractPlatform $platform, array $tableNames, string $search, string $replace): array { diff --git a/Neos.Flow/Classes/Persistence/EmptyQueryResult.php b/Neos.Flow/Classes/Persistence/EmptyQueryResult.php index ce291f11d6..3f1e5ed430 100644 --- a/Neos.Flow/Classes/Persistence/EmptyQueryResult.php +++ b/Neos.Flow/Classes/Persistence/EmptyQueryResult.php @@ -58,7 +58,7 @@ public function getFirst() /** * Returns an empty array * - * @return array + * @return array * @api */ public function toArray(): array @@ -68,6 +68,7 @@ public function toArray(): array public function current(): mixed { + /** @phpstan-ignore return.type (I don't see any "object" requirement here) */ return null; } diff --git a/Neos.Flow/Classes/Persistence/PersistenceManagerInterface.php b/Neos.Flow/Classes/Persistence/PersistenceManagerInterface.php index 5aff47fa89..80888fb1e6 100644 --- a/Neos.Flow/Classes/Persistence/PersistenceManagerInterface.php +++ b/Neos.Flow/Classes/Persistence/PersistenceManagerInterface.php @@ -22,7 +22,7 @@ interface PersistenceManagerInterface /** * Injects the Flow settings, called by Flow. * - * @param array $settings + * @param array $settings * @return void * @api */ diff --git a/Neos.Flow/Classes/Persistence/QueryInterface.php b/Neos.Flow/Classes/Persistence/QueryInterface.php index 910f37d9d8..f7d6334bae 100644 --- a/Neos.Flow/Classes/Persistence/QueryInterface.php +++ b/Neos.Flow/Classes/Persistence/QueryInterface.php @@ -129,7 +129,7 @@ public function count(): int; * 'bar' => \Neos\Flow\Persistence\QueryInterface::ORDER_DESCENDING * ) * - * @param array $orderings The property names to order by + * @param array $orderings The property names to order by * @return QueryInterface * @api */ @@ -142,7 +142,7 @@ public function setOrderings(array $orderings): QueryInterface; * 'bar' => \Neos\Flow\Persistence\QueryInterface::ORDER_DESCENDING * ) * - * @return array + * @return array * @api */ public function getOrderings(): array; diff --git a/Neos.Flow/Classes/Persistence/QueryResultInterface.php b/Neos.Flow/Classes/Persistence/QueryResultInterface.php index 001d247753..1c260a280f 100644 --- a/Neos.Flow/Classes/Persistence/QueryResultInterface.php +++ b/Neos.Flow/Classes/Persistence/QueryResultInterface.php @@ -14,6 +14,8 @@ /** * A lazy result list that is returned by Query::execute() * + * @extends \Iterator + * @extends \ArrayAccess * @api */ interface QueryResultInterface extends \Countable, \Iterator, \ArrayAccess @@ -37,7 +39,7 @@ public function getFirst(); /** * Returns an array with the objects in the result set * - * @return array + * @return array * @api */ public function toArray(): array; diff --git a/Neos.Flow/Classes/Persistence/Repository.php b/Neos.Flow/Classes/Persistence/Repository.php index cea6418226..3ace4da0cf 100644 --- a/Neos.Flow/Classes/Persistence/Repository.php +++ b/Neos.Flow/Classes/Persistence/Repository.php @@ -36,7 +36,7 @@ abstract class Repository implements RepositoryInterface protected $entityClassName; /** - * @var array + * @var array */ protected $defaultOrderings = []; @@ -78,9 +78,8 @@ public function getEntityClassName(): string */ public function add($object): void { - if (!is_object($object) || !($object instanceof $this->entityClassName)) { - $type = (is_object($object) ? get_class($object) : gettype($object)); - throw new IllegalObjectTypeException('The value given to add() was ' . $type . ' , however the ' . get_class($this) . ' can only store ' . $this->entityClassName . ' instances.', 1298403438); + if (!($object instanceof $this->entityClassName)) { + throw new IllegalObjectTypeException('The value given to add() was ' . get_class($object) . ' , however the ' . get_class($this) . ' can only store ' . $this->entityClassName . ' instances.', 1298403438); } $this->persistenceManager->add($object); } @@ -95,9 +94,8 @@ public function add($object): void */ public function remove($object): void { - if (!is_object($object) || !($object instanceof $this->entityClassName)) { - $type = (is_object($object) ? get_class($object) : gettype($object)); - throw new IllegalObjectTypeException('The value given to remove() was ' . $type . ' , however the ' . get_class($this) . ' can only handle ' . $this->entityClassName . ' instances.', 1298403442); + if (!($object instanceof $this->entityClassName)) { + throw new IllegalObjectTypeException('The value given to remove() was ' . get_class($object) . ' , however the ' . get_class($this) . ' can only handle ' . $this->entityClassName . ' instances.', 1298403442); } $this->persistenceManager->remove($object); } @@ -174,7 +172,7 @@ public function removeAll(): void * 'bar' => \Neos\Flow\Persistence\QueryInterface::ORDER_DESCENDING * ) * - * @param array $defaultOrderings The property names to order by by default + * @param array $defaultOrderings The property names to order by by default * @return void * @api */ @@ -192,9 +190,8 @@ public function setDefaultOrderings(array $defaultOrderings): void */ public function update($object): void { - if (!is_object($object) || !($object instanceof $this->entityClassName)) { - $type = (is_object($object) ? get_class($object) : gettype($object)); - throw new IllegalObjectTypeException('The value given to update() was ' . $type . ' , however the ' . get_class($this) . ' can only store ' . $this->entityClassName . ' instances.', 1249479625); + if (!($object instanceof $this->entityClassName)) { + throw new IllegalObjectTypeException('The value given to update() was ' . get_class($object) . ' , however the ' . get_class($this) . ' can only store ' . $this->entityClassName . ' instances.', 1249479625); } $this->persistenceManager->update($object); @@ -209,7 +206,7 @@ public function update($object): void * - countBy($value, $caseSensitive = true) * * @param string $method Name of the method - * @param array $arguments The arguments + * @param array $arguments The arguments * @return mixed The result of the repository method * @api */ diff --git a/Neos.Flow/Classes/Persistence/RepositoryInterface.php b/Neos.Flow/Classes/Persistence/RepositoryInterface.php index 12dcf762dd..23f4e0c6af 100644 --- a/Neos.Flow/Classes/Persistence/RepositoryInterface.php +++ b/Neos.Flow/Classes/Persistence/RepositoryInterface.php @@ -93,7 +93,7 @@ public function removeAll(): void; * 'bar' => \Neos\Flow\Persistence\QueryInterface::ORDER_DESCENDING * ) * - * @param array $defaultOrderings The property names to order by by default + * @param array $defaultOrderings The property names to order by by default * @return void * @api */ @@ -117,7 +117,7 @@ public function update($object): void; * - countBy($value, $caseSensitive = true) * * @param string $method Name of the method - * @param array $arguments The arguments + * @param array $arguments The arguments * @return mixed The result of the repository method * @api */ diff --git a/Neos.Flow/Classes/Property/PropertyMapper.php b/Neos.Flow/Classes/Property/PropertyMapper.php index 8344fc0d59..516bee0e21 100644 --- a/Neos.Flow/Classes/Property/PropertyMapper.php +++ b/Neos.Flow/Classes/Property/PropertyMapper.php @@ -53,7 +53,7 @@ class PropertyMapper * 3. Dimension: Priority * Value: Type Converter instance * - * @var array + * @var array>>> */ protected $typeConverters = []; @@ -85,7 +85,7 @@ public function initializeObject() * Returns all class names implementing the TypeConverterInterface. * * @param ObjectManagerInterface $objectManager - * @return array Array of type converter implementations + * @return array> Array of type converter implementations * @Flow\CompileStatic */ public static function getTypeConverterImplementationClassNames($objectManager) @@ -123,9 +123,10 @@ public function convert($source, $targetType, ?PropertyMappingConfigurationInter } return $result; - } catch (SecurityException $exception) { + } catch (SecurityException $exception) { /** @phpstan-ignore catch.neverThrown (AOP I guess) */ throw $exception; } catch (\Exception $exception) { + /** @phpstan-ignore greater.alwaysFalse (Not sure about this tbh) */ throw new PropertyException('Could not convert target type "' . $targetType . '"' . (count($currentPropertyPath) > 0 ? ', at property path "' . implode('.', $currentPropertyPath) . '"' : '') . ': ' . $exception->getMessage(), 1297759968, $exception); } } @@ -147,7 +148,7 @@ public function getMessages() * @param mixed $source the source data to map. MUST be a simple type, NO object allowed! * @param string $targetType The type of the target; can be either a class name or a simple type. * @param PropertyMappingConfigurationInterface $configuration Configuration for the property mapping. - * @param array $currentPropertyPath The property path currently being mapped; used for knowing the context in case an exception is thrown. + * @param array $currentPropertyPath The property path currently being mapped; used for knowing the context in case an exception is thrown. * @return mixed an instance of $targetType * @throws Exception\TypeConverterException * @throws Exception\InvalidPropertyMappingConfigurationException @@ -175,10 +176,6 @@ protected function doMapping($source, $targetType, PropertyMappingConfigurationI $typeConverter = $this->findTypeConverter($source, $targetType, $configuration); $targetType = $typeConverter->getTargetTypeForSource($source, $targetType, $configuration); - if (!is_object($typeConverter) || !($typeConverter instanceof TypeConverterInterface)) { - throw new Exception\TypeConverterException('Type converter for "' . $source . '" -> "' . $targetType . '" not found.'); - } - $convertedChildProperties = []; foreach ($typeConverter->getSourceChildPropertiesToBeConverted($source) as $sourcePropertyName => $sourcePropertyValue) { $targetPropertyName = $configuration->getTargetPropertyName($sourcePropertyName); @@ -232,9 +229,6 @@ protected function findTypeConverter($source, $targetType, PropertyMappingConfig return $configuration->getTypeConverter(); } - if (!is_string($targetType)) { - throw new Exception\InvalidTargetException('The target type was no string, but of type "' . gettype($targetType) . '"', 1297941727); - } $normalizedTargetType = TypeHandling::normalizeType($targetType); $truncatedTargetType = TypeHandling::truncateElementType($normalizedTargetType); $converter = null; @@ -296,7 +290,7 @@ protected function findFirstEligibleTypeConverterInObjectHierarchy($source, $sou } } - $converters = $this->getConvertersForInterfaces($convertersForSource, class_implements($targetClass)); + $converters = $this->getConvertersForInterfaces($convertersForSource, class_implements($targetClass) ?: []); $converter = $this->findEligibleConverterWithHighestPriority($converters, $source, $targetType); if ($converter !== null) { @@ -341,9 +335,9 @@ protected function findEligibleConverterWithHighestPriority($converters, $source } /** - * @param array $convertersForSource - * @param array $interfaceNames - * @return array + * @param array>> $convertersForSource + * @param array $interfaceNames + * @return array> * @throws DuplicateTypeConverterException */ protected function getConvertersForInterfaces(array $convertersForSource, array $interfaceNames) @@ -353,7 +347,7 @@ protected function getConvertersForInterfaces(array $convertersForSource, array if (isset($convertersForSource[$implementedInterface])) { foreach ($convertersForSource[$implementedInterface] as $priority => $converter) { if (isset($convertersForInterface[$priority])) { - throw new DuplicateTypeConverterException('There exist at least two converters which handle the conversion to an interface with priority "' . $priority . '". ' . get_class($convertersForInterface[$priority]) . ' and ' . get_class($converter), 1297951338); + throw new DuplicateTypeConverterException('There exist at least two converters which handle the conversion to an interface with priority "' . $priority . '". ' . $convertersForInterface[$priority] . ' and ' . $converter, 1297951338); } $convertersForInterface[$priority] = $converter; } @@ -367,7 +361,7 @@ protected function getConvertersForInterfaces(array $convertersForSource, array * Determine the type of the source data, or throw an exception if source was an unsupported format. * * @param mixed $source - * @return array Possible source types (single value for simple typed source, multiple values for object source) + * @return array Possible source types (single value for simple typed source, multiple values for object source) * @throws Exception\InvalidSourceException */ protected function determineSourceTypes($source) @@ -396,7 +390,7 @@ protected function determineSourceTypes($source) /** * Collects all TypeConverter implementations in a multi-dimensional array with source and target types. * - * @return array + * @return array>>> * @throws Exception\DuplicateTypeConverterException * @see getTypeConverters */ @@ -405,7 +399,6 @@ protected function prepareTypeConverterMap() $typeConverterMap = []; $typeConverterClassNames = static::getTypeConverterImplementationClassNames($this->objectManager); foreach ($typeConverterClassNames as $typeConverterClassName) { - /** @var TypeConverterInterface $typeConverter */ $typeConverter = $this->objectManager->get($typeConverterClassName); foreach ($typeConverter->getSupportedSourceTypes() as $supportedSourceType) { $normalizedSourceType = TypeHandling::normalizeType($supportedSourceType); @@ -430,7 +423,7 @@ protected function prepareTypeConverterMap() * 3. Dimension: Priority * Value: Type Converter instance * - * @return array + * @return array>>> */ public function getTypeConverters() { diff --git a/Neos.Flow/Classes/Property/PropertyMappingConfiguration.php b/Neos.Flow/Classes/Property/PropertyMappingConfiguration.php index dc6021c1cb..c92713b00f 100644 --- a/Neos.Flow/Classes/Property/PropertyMappingConfiguration.php +++ b/Neos.Flow/Classes/Property/PropertyMappingConfiguration.php @@ -30,21 +30,21 @@ class PropertyMappingConfiguration implements PropertyMappingConfigurationInterf * 2. Dimension: Configuration Key * Value: Configuration Value * - * @var array + * @var array,array> */ protected $configuration; /** * Stores the configuration for specific child properties. * - * @var array + * @var array */ protected $subConfigurationForProperty = []; /** * Keys which should be renamed * - * @var array + * @var array */ protected $mapping = []; @@ -56,21 +56,21 @@ class PropertyMappingConfiguration implements PropertyMappingConfigurationInterf /** * List of allowed property names to be converted * - * @var array + * @var array */ protected $propertiesToBeMapped = []; /** * List of property names to be skipped during property mapping * - * @var array + * @var array */ protected $propertiesToSkip = []; /** * List of disallowed property names which will be ignored while property mapping * - * @var array + * @var array */ protected $propertiesNotToBeMapped = []; @@ -260,7 +260,7 @@ public function getTargetPropertyName($sourcePropertyName) /** * @param string $typeConverterClassName - * @param string $key + * @param string|int $key * @return mixed configuration value for the specific $typeConverterClassName. Can be used by Type Converters to fetch converter-specific configuration. * @api */ @@ -290,8 +290,8 @@ public function setMapping($sourcePropertyName, $targetPropertyName) /** * Set all options for the given $typeConverter. * - * @param string $typeConverter class name of type converter - * @param array $options + * @param class-string $typeConverter class name of type converter + * @param array $options * @return PropertyMappingConfiguration this * @api */ @@ -306,7 +306,7 @@ public function setTypeConverterOptions($typeConverter, array $options) /** * Set a single option (denoted by $optionKey) for the given $typeConverter. * - * @param string $typeConverter class name of type converter + * @param class-string $typeConverter class name of type converter * @param int|string $optionKey * @param mixed $optionValue * @return PropertyMappingConfiguration this @@ -326,13 +326,18 @@ public function setTypeConverterOption($typeConverter, $optionKey, $optionValue) * When setting an option on a subclassed type converter, this option must also be set on * all its parent type converters. * - * @param string $typeConverter The type converter class - * @return array Class names of type converters + * @param class-string $typeConverter The type converter class + * @return array> Class names of type converters */ protected function getTypeConvertersWithParentClasses($typeConverter) { $typeConverterClasses = class_parents($typeConverter); - $typeConverterClasses = $typeConverterClasses === false ? [] : $typeConverterClasses; + $typeConverterClasses = $typeConverterClasses === false + ? [] + : array_filter( + $typeConverterClasses, + fn (string $parentClassName): bool => is_subclass_of($parentClassName, TypeConverterInterface::class) + ); $typeConverterClasses[] = $typeConverter; return $typeConverterClasses; } @@ -355,7 +360,7 @@ public function forProperty($propertyPath) /** * Traverse the property configuration. Only used by forProperty(). * - * @param array $splittedPropertyPath + * @param array $splittedPropertyPath * @return PropertyMappingConfiguration (or a subclass thereof) */ public function traverseProperties(array $splittedPropertyPath) diff --git a/Neos.Flow/Classes/Property/PropertyMappingConfigurationInterface.php b/Neos.Flow/Classes/Property/PropertyMappingConfigurationInterface.php index 786e08b003..55622e78c7 100644 --- a/Neos.Flow/Classes/Property/PropertyMappingConfigurationInterface.php +++ b/Neos.Flow/Classes/Property/PropertyMappingConfigurationInterface.php @@ -70,7 +70,7 @@ public function getTargetPropertyName($sourcePropertyName); /** * @param string $typeConverterClassName - * @param string $key + * @param string|int $key * @return mixed configuration value for the specific $typeConverterClassName. Can be used by Type Converters to fetch converter-specific configuration * @api */ diff --git a/Neos.Flow/Classes/Property/TypeConverter/ArrayConverter.php b/Neos.Flow/Classes/Property/TypeConverter/ArrayConverter.php index ca088c34cb..216f8b2bc8 100644 --- a/Neos.Flow/Classes/Property/TypeConverter/ArrayConverter.php +++ b/Neos.Flow/Classes/Property/TypeConverter/ArrayConverter.php @@ -112,9 +112,9 @@ class ArrayConverter extends AbstractTypeConverter * * @param mixed $source * @param string $targetType - * @param array $convertedChildProperties + * @param array $convertedChildProperties * @param PropertyMappingConfigurationInterface|null $configuration - * @return array + * @return array * @throws InvalidPropertyMappingConfigurationException * @throws InvalidSourceException * @throws TypeConverterException @@ -146,9 +146,13 @@ public function convertFrom($source, $targetType, array $convertedChildPropertie $exportType = $this->getResourceExportType($configuration); switch ($exportType) { case self::RESOURCE_EXPORT_TYPE_BASE64: + $fileContent = file_get_contents('resource://' . $source->getSha1()); + if ($fileContent === false) { + throw new \Exception('Failed to fetch content for resource ' . $source->getSha1(), 1744060115); + } return [ 'filename' => $source->getFilename(), - 'data' => base64_encode(file_get_contents('resource://' . $source->getSha1())), + 'data' => base64_encode($fileContent), 'collectionName' => $source->getCollectionName(), 'relativePublicationPath' => $source->getRelativePublicationPath(), 'mediaType' => $source->getMediaType(), @@ -159,7 +163,13 @@ public function convertFrom($source, $targetType, array $convertedChildPropertie if ($sourceStream === false) { throw new InvalidSourceException(sprintf('Could not get stream of resource "%s" (%s). This might be caused by a broken resource object and can be fixed by running the "resource:clean" command.', $source->getFilename(), $source->getSha1()), 1435842312); } + if (!$configuration) { + throw new \Exception('Cannot resolve target path without a property mapping configuration', 1744060031); + } $targetStream = fopen($configuration->getConfigurationValue(ArrayConverter::class, self::CONFIGURATION_RESOURCE_SAVE_PATH) . '/' . $source->getSha1(), 'w'); + if ($targetStream === false) { + throw new \Exception('Could not open target stream', 1744060053); + } stream_copy_to_stream($sourceStream, $targetStream); fclose($targetStream); fclose($sourceStream); @@ -181,7 +191,7 @@ public function convertFrom($source, $targetType, array $convertedChildPropertie /** * @param PropertyMappingConfigurationInterface|null $configuration - * @return string + * @return non-empty-string * @throws InvalidPropertyMappingConfigurationException */ protected function getStringDelimiter(?PropertyMappingConfigurationInterface $configuration = null) @@ -195,6 +205,8 @@ protected function getStringDelimiter(?PropertyMappingConfigurationInterface $co return self::DEFAULT_STRING_DELIMITER; } elseif (!is_string($stringDelimiter)) { throw new InvalidPropertyMappingConfigurationException(sprintf('CONFIGURATION_STRING_DELIMITER must be of type string, "%s" given', (is_object($stringDelimiter) ? get_class($stringDelimiter) : gettype($stringDelimiter))), 1368433339); + } elseif ($stringDelimiter === '') { + throw new InvalidPropertyMappingConfigurationException('CONFIGURATION_STRING_DELIMITER must not be empty', 1744060278); } return $stringDelimiter; diff --git a/Neos.Flow/Classes/Property/TypeConverter/ArrayFromObjectConverter.php b/Neos.Flow/Classes/Property/TypeConverter/ArrayFromObjectConverter.php index 0c5d63e1d6..772798dbac 100644 --- a/Neos.Flow/Classes/Property/TypeConverter/ArrayFromObjectConverter.php +++ b/Neos.Flow/Classes/Property/TypeConverter/ArrayFromObjectConverter.php @@ -50,7 +50,7 @@ class ArrayFromObjectConverter extends AbstractTypeConverter * Convert all properties in the source array * * @param mixed $source - * @return array + * @return array */ public function getSourceChildPropertiesToBeConverted($source) { @@ -90,7 +90,7 @@ public function getTypeOfChildProperty($targetType, $propertyName, PropertyMappi * * @param mixed $source * @param string $targetType - * @param array $convertedChildProperties + * @param array $convertedChildProperties * @param PropertyMappingConfigurationInterface|null $configuration * @return mixed|Error the target type, or an error object if a user-error occurred * @api diff --git a/Neos.Flow/Classes/Property/TypeConverter/ArrayObjectConverter.php b/Neos.Flow/Classes/Property/TypeConverter/ArrayObjectConverter.php index ea519af032..24bfc8c087 100644 --- a/Neos.Flow/Classes/Property/TypeConverter/ArrayObjectConverter.php +++ b/Neos.Flow/Classes/Property/TypeConverter/ArrayObjectConverter.php @@ -45,9 +45,9 @@ class ArrayObjectConverter extends AbstractTypeConverter * * @param mixed $source * @param string $targetType - * @param array $convertedChildProperties + * @param array $convertedChildProperties * @param PropertyMappingConfigurationInterface|null $configuration - * @return array + * @return array * @throws InvalidSourceException * @api */ diff --git a/Neos.Flow/Classes/Property/TypeConverter/BooleanConverter.php b/Neos.Flow/Classes/Property/TypeConverter/BooleanConverter.php index 460f1b3541..135b17ab15 100644 --- a/Neos.Flow/Classes/Property/TypeConverter/BooleanConverter.php +++ b/Neos.Flow/Classes/Property/TypeConverter/BooleanConverter.php @@ -46,7 +46,7 @@ class BooleanConverter extends AbstractTypeConverter * * @param mixed $source * @param string $targetType - * @param array $convertedChildProperties + * @param array $convertedChildProperties * @param PropertyMappingConfigurationInterface|null $configuration * @return boolean * @api diff --git a/Neos.Flow/Classes/Property/TypeConverter/CollectionConverter.php b/Neos.Flow/Classes/Property/TypeConverter/CollectionConverter.php index 91755c7d9a..6226dc8fcf 100644 --- a/Neos.Flow/Classes/Property/TypeConverter/CollectionConverter.php +++ b/Neos.Flow/Classes/Property/TypeConverter/CollectionConverter.php @@ -49,9 +49,9 @@ class CollectionConverter extends AbstractTypeConverter * * @param mixed $source * @param string $targetType - * @param array $convertedChildProperties + * @param array $convertedChildProperties * @param PropertyMappingConfigurationInterface|null $configuration - * @return ArrayCollection + * @return ArrayCollection * @api */ public function convertFrom($source, $targetType, array $convertedChildProperties = [], ?PropertyMappingConfigurationInterface $configuration = null) @@ -63,7 +63,7 @@ public function convertFrom($source, $targetType, array $convertedChildPropertie * Returns the source, if it is an array, otherwise an empty array. * * @param mixed $source - * @return array + * @return array * @api */ public function getSourceChildPropertiesToBeConverted($source) diff --git a/Neos.Flow/Classes/Property/TypeConverter/DateTimeConverter.php b/Neos.Flow/Classes/Property/TypeConverter/DateTimeConverter.php index 8a68a92286..339b3dce97 100644 --- a/Neos.Flow/Classes/Property/TypeConverter/DateTimeConverter.php +++ b/Neos.Flow/Classes/Property/TypeConverter/DateTimeConverter.php @@ -92,7 +92,7 @@ class DateTimeConverter extends AbstractTypeConverter /** * If conversion is possible. * - * @param string|int|array $source + * @param string|int|array $source * @param string $targetType * @return boolean */ @@ -101,21 +101,16 @@ public function canConvertFrom($source, $targetType) if (!is_callable([$targetType, 'createFromFormat'])) { return false; } - if (is_array($source)) { - return true; - } - if (is_integer($source)) { - return true; - } - return is_string($source); + + return true; } /** * Converts $source to a \DateTime using the configured dateFormat * - * @param string|integer|array $source the string to be converted to a \DateTime object + * @param string|integer|array $source the string to be converted to a \DateTime object * @param string $targetType must be "DateTime" - * @param array $convertedChildProperties not used currently + * @param array $convertedChildProperties not used currently * @param PropertyMappingConfigurationInterface|null $configuration * @return \DateTimeInterface|null|Error * @throws InvalidPropertyMappingConfigurationException @@ -171,7 +166,7 @@ public function convertFrom($source, $targetType, array $convertedChildPropertie if ($date === false) { return new Error('The date "%s" was not recognized (for format "%s").', 1307719788, [$dateAsString, $dateFormat]); } - if (isset($source['hour'], $source['minute'], $source['second']) && is_array($source)) { + if (isset($source['hour'], $source['minute'], $source['second'])) { $date = $this->overrideTime($date, $source); } return $date; @@ -179,7 +174,7 @@ public function convertFrom($source, $targetType, array $convertedChildPropertie /** * Returns whether date information (day, month, year) are present as keys in $source. - * @param array $source + * @param array $source * @return bool */ protected function isDatePartKeysProvided(array $source) @@ -215,7 +210,7 @@ protected function getDefaultDateFormat(?PropertyMappingConfigurationInterface $ * Overrides hour, minute & second of the given date with the values in the $source array * * @param \DateTimeInterface $date - * @param array $source + * @param array $source * @return \DateTimeInterface */ protected function overrideTime(\DateTimeInterface $date, array $source) @@ -223,7 +218,7 @@ protected function overrideTime(\DateTimeInterface $date, array $source) $hour = isset($source['hour']) ? (integer)$source['hour'] : 0; $minute = isset($source['minute']) ? (integer)$source['minute'] : 0; $second = isset($source['second']) ? (integer)$source['second'] : 0; - if ($date instanceof \DateTime || $date instanceof \DateTimeImmutable) { + if (is_callable([$date, 'setTime'])) { $date = $date->setTime($hour, $minute, $second); } return $date; diff --git a/Neos.Flow/Classes/Property/TypeConverter/FloatConverter.php b/Neos.Flow/Classes/Property/TypeConverter/FloatConverter.php index 5cd49c1183..d8919f9e9e 100644 --- a/Neos.Flow/Classes/Property/TypeConverter/FloatConverter.php +++ b/Neos.Flow/Classes/Property/TypeConverter/FloatConverter.php @@ -124,7 +124,7 @@ class FloatConverter extends AbstractTypeConverter * * @param mixed $source * @param string $targetType - * @param array $convertedChildProperties + * @param array $convertedChildProperties * @param PropertyMappingConfigurationInterface|null $configuration * @return float|Error|null * @throws InvalidPropertyMappingConfigurationException @@ -172,7 +172,7 @@ protected function parseUsingLocaleIfConfigured($source, PropertyMappingConfigur } if (!$locale instanceof Locale) { - $exceptionMessage = 'Determined locale is not of type "\Neos\Flow\I18n\Locale", but of type "' . (is_object($locale) ? get_class($locale) : gettype($locale)) . '".'; + $exceptionMessage = 'Determined locale is not of type "\Neos\Flow\I18n\Locale", but of type "' . gettype($locale) . '".'; throw new InvalidPropertyMappingConfigurationException($exceptionMessage, 1334837413); } @@ -215,8 +215,8 @@ protected function parseUsingLocaleIfConfigured($source, PropertyMappingConfigur * Helper method to collect configuration for this class. * * @param PropertyMappingConfigurationInterface $configuration - * @param array $configurationKeys - * @return array + * @param array $configurationKeys + * @return array */ protected function getConfigurationKeysAndValues(PropertyMappingConfigurationInterface $configuration, array $configurationKeys) { diff --git a/Neos.Flow/Classes/Property/TypeConverter/IntegerConverter.php b/Neos.Flow/Classes/Property/TypeConverter/IntegerConverter.php index 6ccf96a562..edecebe380 100644 --- a/Neos.Flow/Classes/Property/TypeConverter/IntegerConverter.php +++ b/Neos.Flow/Classes/Property/TypeConverter/IntegerConverter.php @@ -47,7 +47,7 @@ class IntegerConverter extends AbstractTypeConverter * * @param int|string|\DateTimeInterface|null $source * @param string $targetType - * @param array $convertedChildProperties + * @param array $convertedChildProperties * @param PropertyMappingConfigurationInterface|null $configuration * @return integer|null|Error * @api diff --git a/Neos.Flow/Classes/Property/TypeConverter/MediaTypeConverter.php b/Neos.Flow/Classes/Property/TypeConverter/MediaTypeConverter.php index f78b1cd663..96d4f58d31 100644 --- a/Neos.Flow/Classes/Property/TypeConverter/MediaTypeConverter.php +++ b/Neos.Flow/Classes/Property/TypeConverter/MediaTypeConverter.php @@ -48,9 +48,9 @@ class MediaTypeConverter extends AbstractTypeConverter implements MediaTypeConve * * @param string $source the raw request body * @param string $targetType must be "array" - * @param array $convertedChildProperties + * @param array $convertedChildProperties * @param PropertyMappingConfigurationInterface|null $configuration - * @return array|string|integer Note that this TypeConverter may return a non-array in case of JSON media type, even though he declares to only convert to array + * @return array|string|integer Note that this TypeConverter may return a non-array in case of JSON media type, even though he declares to only convert to array * @api */ public function convertFrom($source, $targetType, array $convertedChildProperties = [], ?PropertyMappingConfigurationInterface $configuration = null) @@ -71,13 +71,13 @@ public function convertFrom($source, $targetType, array $convertedChildPropertie * * @param string $requestBody the raw request body * @param string $mediaType the configured media type (for example "application/json") - * @return array|string|integer + * @return array|string|integer * @api */ protected function convertMediaType($requestBody, $mediaType) { $mediaTypeParts = MediaTypes::parseMediaType($mediaType); - if (!isset($mediaTypeParts['subtype']) || $mediaTypeParts['subtype'] === '') { + if ($mediaTypeParts['subtype'] === '') { return []; } $result = []; diff --git a/Neos.Flow/Classes/Property/TypeConverter/ObjectConverter.php b/Neos.Flow/Classes/Property/TypeConverter/ObjectConverter.php index 62f6599243..29cd9fd4e5 100644 --- a/Neos.Flow/Classes/Property/TypeConverter/ObjectConverter.php +++ b/Neos.Flow/Classes/Property/TypeConverter/ObjectConverter.php @@ -79,7 +79,18 @@ class ObjectConverter extends AbstractTypeConverter /** * As it is very likely that the constructor arguments are needed twice we should cache them for the request. * - * @var array + * @var array>, + * }>> */ protected $constructorReflectionFirstLevelCache = []; @@ -116,7 +127,7 @@ public function getSourceChildPropertiesToBeConverted($source) /** * The type of a property is determined by the reflection service. * - * @param string $targetType + * @param class-string $targetType * @param string $propertyName * @param PropertyMappingConfigurationInterface $configuration * @return string|null @@ -149,6 +160,9 @@ public function getTypeOfChildProperty($targetType, $propertyName, PropertyMappi // would not find a property mapper. It is needed because the ObjectConverter doesn't use class schemata, // but reads the annotations directly. $declaredType = strtok(trim(current($varTagValues), " \n\t"), " \n\t"); + if ($declaredType === false) { + throw new \Exception('Invalid declared type', 1744058940); + } try { $parsedType = TypeHandling::parseType($declaredType); } catch (InvalidTypeException $exception) { @@ -173,7 +187,7 @@ public function getTypeOfChildProperty($targetType, $propertyName, PropertyMappi * * @param mixed $source * @param string $targetType - * @param array $convertedChildProperties + * @param array $convertedChildProperties * @param PropertyMappingConfigurationInterface|null $configuration * @return object the target type * @throws InvalidTargetException @@ -185,6 +199,7 @@ public function convertFrom($source, $targetType, array $convertedChildPropertie $object = $this->buildObject($convertedChildProperties, $targetType); foreach ($convertedChildProperties as $propertyName => $propertyValue) { $result = ObjectAccess::setProperty($object, $propertyName, $propertyValue); + /** @var object $object */ if ($result === false) { $exceptionMessage = sprintf( 'Property "%s" having a value of type "%s" could not be set in target object of type "%s". Make sure that the property is accessible properly, for example via an appropriate setter method.', @@ -214,7 +229,7 @@ public function getTargetTypeForSource($source, $originalTargetType, ?PropertyMa { $targetType = $originalTargetType; - if (is_array($source) && array_key_exists('__type', $source)) { + if (is_array($source) && array_key_exists('__type', $source) && is_string($source['__type'])) { $targetType = $source['__type']; if ($configuration === null) { @@ -224,6 +239,7 @@ public function getTargetTypeForSource($source, $originalTargetType, ?PropertyMa throw new InvalidPropertyMappingConfigurationException('Override of target type not allowed. To enable this, you need to set the PropertyMappingConfiguration Value "CONFIGURATION_OVERRIDE_TARGET_TYPE_ALLOWED" to true.', 1317050430); } + /** @todo is_a is not recursive! replace with is_subclass_of or similar */ if ($targetType !== $originalTargetType && is_a($targetType, $originalTargetType, true) === false) { throw new InvalidDataTypeException('The given type "' . $targetType . '" is not a subtype of "' . $originalTargetType . '".', 1317048056); } @@ -239,7 +255,7 @@ public function getTargetTypeForSource($source, $originalTargetType, ?PropertyMa * * Furthermore, the constructor arguments are removed from $possibleConstructorArgumentValues * - * @param array &$possibleConstructorArgumentValues + * @param array &$possibleConstructorArgumentValues * @param string $objectType * @return object The created instance * @throws InvalidTargetException if a required constructor argument is missing @@ -248,6 +264,9 @@ protected function buildObject(array &$possibleConstructorArgumentValues, $objec { $constructorArguments = []; $className = $this->objectManager->getClassNameByObjectName($objectType); + if ($className === false) { + throw new \Exception('Invalid object type '. $objectType, 1744058576); + } $constructorSignature = $this->getConstructorArgumentsForClass($className); if (count($constructorSignature)) { foreach ($constructorSignature as $constructorArgumentName => $constructorArgumentReflection) { @@ -256,7 +275,7 @@ protected function buildObject(array &$possibleConstructorArgumentValues, $objec unset($possibleConstructorArgumentValues[$constructorArgumentName]); } elseif ($constructorArgumentReflection['optional'] === true) { $constructorArguments[] = $constructorArgumentReflection['defaultValue']; - } elseif ($this->objectManager->isRegistered($constructorArgumentReflection['type']) && $this->objectManager->getScope($constructorArgumentReflection['type']) === Configuration::SCOPE_SINGLETON) { + } elseif ($constructorArgumentReflection['type'] && $this->objectManager->isRegistered($constructorArgumentReflection['type']) && $this->objectManager->getScope($constructorArgumentReflection['type']) === Configuration::SCOPE_SINGLETON) { $constructorArguments[] = $this->objectManager->get($constructorArgumentReflection['type']); } else { throw new InvalidTargetException('Missing constructor argument "' . $constructorArgumentName . '" for object of type "' . $objectType . '".', 1268734872); @@ -272,8 +291,19 @@ protected function buildObject(array &$possibleConstructorArgumentValues, $objec /** * Get the constructor argument reflection for the given object type. * - * @param string $className - * @return array + * @param class-string $className + * @return array>, + * }> */ protected function getConstructorArgumentsForClass($className) { diff --git a/Neos.Flow/Classes/Property/TypeConverter/PersistentObjectConverter.php b/Neos.Flow/Classes/Property/TypeConverter/PersistentObjectConverter.php index 9ce89473ea..028eef44ea 100644 --- a/Neos.Flow/Classes/Property/TypeConverter/PersistentObjectConverter.php +++ b/Neos.Flow/Classes/Property/TypeConverter/PersistentObjectConverter.php @@ -114,7 +114,7 @@ public function getSourceChildPropertiesToBeConverted($source) /** * The type of a property is determined by the reflection service. * - * @param string $targetType + * @param class-string $targetType * @param string $propertyName * @param PropertyMappingConfigurationInterface $configuration * @return string @@ -128,6 +128,9 @@ public function getTypeOfChildProperty($targetType, $propertyName, PropertyMappi } $schema = $this->reflectionService->getClassSchema($targetType); + if (!$schema) { + throw new InvalidTargetException('Schema for target object of type "' . $targetType . '" not found.', 1744057928); + } $setterMethodName = ObjectAccess::buildSetterMethodName($propertyName); $constructorParameters = $this->reflectionService->getMethodParameters($targetType, '__construct'); @@ -154,7 +157,7 @@ public function getTypeOfChildProperty($targetType, $propertyName, PropertyMappi * * @param mixed $source * @param string $targetType - * @param array $convertedChildProperties + * @param array $convertedChildProperties * @param PropertyMappingConfigurationInterface $configuration * @return object|TargetNotFoundError|null the converted entity/value object or an instance of TargetNotFoundError if the object could not be resolved * @throws \InvalidArgumentException|InvalidTargetException @@ -191,6 +194,7 @@ public function convertFrom($source, $targetType, array $convertedChildPropertie foreach ($convertedChildProperties as $propertyName => $propertyValue) { // We need to check for "immutable" constructor arguments that have no setter and remove them. + /** @var object $object */ if (isset($objectConstructorArguments[$propertyName]) && !ObjectAccess::isPropertySettable($object, $propertyName)) { $currentPropertyValue = ObjectAccess::getProperty($object, $propertyName); if ($currentPropertyValue === $propertyValue) { @@ -217,15 +221,16 @@ public function convertFrom($source, $targetType, array $convertedChildPropertie } } + /** @var object $object */ return $object; } /** * Handle the case if $source is an array. * - * @param array $source + * @param array $source * @param class-string $targetType - * @param array $convertedChildProperties + * @param array &$convertedChildProperties * @param PropertyMappingConfigurationInterface|null $configuration * @return object|TargetNotFoundError * @throws InvalidPropertyMappingConfigurationException @@ -267,7 +272,7 @@ protected function handleArrayData(array $source, $targetType, array &$converted * Set the given $identity on the created $object. * * @param object $object - * @param string|array $identity + * @param string|array $identity * @return void * @todo set identity properly if it is composite or custom property */ @@ -300,7 +305,7 @@ protected function fetchObjectFromPersistence($identity, $targetType) /** * Finds an object from the repository by searching for its identity properties. * - * @param array $identityProperties Property names and values to search for + * @param array $identityProperties Property names and values to search for * @param string $type The object type to look for * @return object|null Either the object matching the identity or NULL if no object was found * @throws DuplicateObjectException if more than one object was found @@ -311,7 +316,7 @@ protected function findObjectByIdentityProperties(array $identityProperties, $ty $classSchema = $this->reflectionService->getClassSchema($type); $equals = []; - foreach ($classSchema->getIdentityProperties() as $propertyName => $propertyType) { + foreach ($classSchema?->getIdentityProperties() ?: [] as $propertyName => $propertyType) { if (isset($identityProperties[$propertyName])) { if ($propertyType === 'string') { $equals[] = $query->equals($propertyName, $identityProperties[$propertyName], false); diff --git a/Neos.Flow/Classes/Property/TypeConverter/PersistentObjectSerializer.php b/Neos.Flow/Classes/Property/TypeConverter/PersistentObjectSerializer.php index 5231260403..0ee57cbfde 100644 --- a/Neos.Flow/Classes/Property/TypeConverter/PersistentObjectSerializer.php +++ b/Neos.Flow/Classes/Property/TypeConverter/PersistentObjectSerializer.php @@ -52,7 +52,7 @@ class PersistentObjectSerializer extends AbstractTypeConverter * * @param object $source * @param string $targetType - * @param array $convertedChildProperties + * @param array $convertedChildProperties * @param PropertyMappingConfigurationInterface|null $configuration * @return mixed The identifier for the object if it is known, or NULL */ diff --git a/Neos.Flow/Classes/Property/TypeConverter/ScalarTypeToObjectConverter.php b/Neos.Flow/Classes/Property/TypeConverter/ScalarTypeToObjectConverter.php index 988e0a5ad4..4656aa035f 100644 --- a/Neos.Flow/Classes/Property/TypeConverter/ScalarTypeToObjectConverter.php +++ b/Neos.Flow/Classes/Property/TypeConverter/ScalarTypeToObjectConverter.php @@ -58,6 +58,9 @@ class ScalarTypeToObjectConverter extends AbstractTypeConverter */ public function canConvertFrom($source, $targetType) { + if (!class_exists($targetType)) { + return false; + } if (( $this->reflectionService->isClassAnnotatedWith($targetType, Flow\Entity::class) || $this->reflectionService->isClassAnnotatedWith($targetType, Flow\ValueObject::class) || @@ -71,7 +74,8 @@ public function canConvertFrom($source, $targetType) return false; } $methodParameter = array_shift($methodParameters); - return TypeHandling::normalizeType($methodParameter['type']) === TypeHandling::normalizeType(gettype($source)); + $methodParameterType = $methodParameter['type'] ?? null; + return ($methodParameterType ? TypeHandling::normalizeType($methodParameterType) : null) === TypeHandling::normalizeType(gettype($source)); } /** @@ -79,7 +83,7 @@ public function canConvertFrom($source, $targetType) * * @param string|float|integer|bool $source * @param string $targetType - * @param array $convertedChildProperties + * @param array $convertedChildProperties * @param PropertyMappingConfigurationInterface|null $configuration * @return object */ diff --git a/Neos.Flow/Classes/Property/TypeConverter/SessionConverter.php b/Neos.Flow/Classes/Property/TypeConverter/SessionConverter.php index 3729434ed9..72ab19178b 100644 --- a/Neos.Flow/Classes/Property/TypeConverter/SessionConverter.php +++ b/Neos.Flow/Classes/Property/TypeConverter/SessionConverter.php @@ -14,6 +14,7 @@ use Neos\Flow\Annotations as Flow; use Neos\Flow\Property\PropertyMappingConfigurationInterface; use Neos\Flow\Session\Session; +use Neos\Flow\Session\SessionInterface; use Neos\Flow\Session\SessionManagerInterface; /** @@ -70,9 +71,9 @@ public function canConvertFrom($source, $targetType) * * @param string $source * @param string $targetType - * @param array $convertedChildProperties + * @param array $convertedChildProperties * @param PropertyMappingConfigurationInterface|null $configuration - * @return object the target type + * @return ?SessionInterface the target type */ public function convertFrom($source, $targetType, array $convertedChildProperties = [], ?PropertyMappingConfigurationInterface $configuration = null) { diff --git a/Neos.Flow/Classes/Property/TypeConverter/StringConverter.php b/Neos.Flow/Classes/Property/TypeConverter/StringConverter.php index dce5764954..ee58bc43ab 100644 --- a/Neos.Flow/Classes/Property/TypeConverter/StringConverter.php +++ b/Neos.Flow/Classes/Property/TypeConverter/StringConverter.php @@ -94,7 +94,7 @@ class StringConverter extends AbstractTypeConverter * * @param mixed $source * @param string $targetType - * @param array $convertedChildProperties + * @param array $convertedChildProperties * @param PropertyMappingConfigurationInterface|null $configuration * @return string * @throws InvalidPropertyMappingConfigurationException @@ -113,7 +113,7 @@ public function convertFrom($source, $targetType, array $convertedChildPropertie case self::ARRAY_FORMAT_CSV: return implode($this->getCsvDelimiter($configuration), $source); case self::ARRAY_FORMAT_JSON: - return json_encode($source); + return json_encode($source, JSON_THROW_ON_ERROR); default: throw new InvalidPropertyMappingConfigurationException(sprintf('Invalid array export format "%s" given', $this->getArrayFormat($configuration)), 1404317220); } diff --git a/Neos.Flow/Classes/Property/TypeConverter/TypedArrayConverter.php b/Neos.Flow/Classes/Property/TypeConverter/TypedArrayConverter.php index f183ca93cf..5550c4b281 100644 --- a/Neos.Flow/Classes/Property/TypeConverter/TypedArrayConverter.php +++ b/Neos.Flow/Classes/Property/TypeConverter/TypedArrayConverter.php @@ -56,11 +56,11 @@ public function canConvertFrom($source, $targetType) } /** - * @param array $source An array of objects/simple types + * @param array $source An array of objects/simple types * @param string $targetType - * @param array $convertedChildProperties + * @param array $convertedChildProperties * @param PropertyMappingConfigurationInterface|null $configuration - * @return array + * @return array * @api */ public function convertFrom($source, $targetType, array $convertedChildProperties = [], ?PropertyMappingConfigurationInterface $configuration = null) @@ -72,7 +72,7 @@ public function convertFrom($source, $targetType, array $convertedChildPropertie * Returns the source, if it is an array, otherwise an empty array. * * @param mixed $source - * @return array + * @return array */ public function getSourceChildPropertiesToBeConverted($source) { @@ -85,7 +85,7 @@ public function getSourceChildPropertiesToBeConverted($source) * @param string $targetType * @param string $propertyName * @param PropertyMappingConfigurationInterface $configuration - * @return string + * @return ?string */ public function getTypeOfChildProperty($targetType, $propertyName, PropertyMappingConfigurationInterface $configuration) { diff --git a/Neos.Flow/Classes/Property/TypeConverter/UriTypeConverter.php b/Neos.Flow/Classes/Property/TypeConverter/UriTypeConverter.php index f8556886df..3a6299e3c2 100644 --- a/Neos.Flow/Classes/Property/TypeConverter/UriTypeConverter.php +++ b/Neos.Flow/Classes/Property/TypeConverter/UriTypeConverter.php @@ -46,7 +46,7 @@ class UriTypeConverter extends AbstractTypeConverter * * @param string $source The URI to be converted * @param string $targetType - * @param array $convertedChildProperties + * @param array $convertedChildProperties * @param PropertyMappingConfigurationInterface|null $configuration * @return Uri|Error if the input format is not supported or could not be converted for other reasons */ diff --git a/Neos.Flow/Classes/Property/TypeConverterInterface.php b/Neos.Flow/Classes/Property/TypeConverterInterface.php index d9bb5bbc8e..47c0e0b561 100644 --- a/Neos.Flow/Classes/Property/TypeConverterInterface.php +++ b/Neos.Flow/Classes/Property/TypeConverterInterface.php @@ -104,7 +104,7 @@ public function getTypeOfChildProperty($targetType, $propertyName, PropertyMappi * * @param mixed $source * @param string $targetType - * @param array $convertedChildProperties + * @param array $convertedChildProperties * @param PropertyMappingConfigurationInterface|null $configuration * @return mixed|Error the target type, or an error object if a user-error occurred * @throws Exception\TypeConverterException thrown in case a developer error occurred diff --git a/Neos.Flow/Classes/Reflection/ClassReflection.php b/Neos.Flow/Classes/Reflection/ClassReflection.php index 34664fab6c..6ecc334ca4 100644 --- a/Neos.Flow/Classes/Reflection/ClassReflection.php +++ b/Neos.Flow/Classes/Reflection/ClassReflection.php @@ -16,6 +16,7 @@ /** * Extended version of the ReflectionClass * + * @extends \ReflectionClass * @Flow\Proxy(false) */ class ClassReflection extends \ReflectionClass @@ -39,7 +40,7 @@ static function ($className) use ($classNameOrObject) { } /** - * @var DocCommentParser Holds an instance of the doc comment parser for this class + * @var ?DocCommentParser Holds an instance of the doc comment parser for this class */ protected $docCommentParser; @@ -48,12 +49,14 @@ static function ($className) use ($classNameOrObject) { * that MethodReflection objects are returned instead of the * original ReflectionMethod instances. * - * @return MethodReflection Method reflection object of the constructor method + * @return ?MethodReflection Method reflection object of the constructor method */ - public function getConstructor(): MethodReflection + public function getConstructor(): ?MethodReflection { $parentConstructor = parent::getConstructor(); - return (!is_object($parentConstructor)) ? $parentConstructor : new MethodReflection($this->getName(), $parentConstructor->getName()); + return $parentConstructor === null + ? $parentConstructor + : new MethodReflection($this->getName(), $parentConstructor->getName()); } /** @@ -165,7 +168,7 @@ public function isTaggedWith($tag) /** * Returns an array of tags and their values * - * @return array Tags and values + * @return array> Tags and values */ public function getTagsValues() { @@ -175,7 +178,7 @@ public function getTagsValues() /** * Returns the values of the specified tag * @param string $tag - * @return array Values of the given tag + * @return array Values of the given tag */ public function getTagValues($tag) { @@ -205,7 +208,7 @@ public function newInstanceWithoutConstructor(): object { $instance = parent::newInstanceWithoutConstructor(); - if (method_exists($instance, '__wakeup') && is_callable([$instance, '__wakeup'])) { + if (method_exists($instance, '__wakeup')) { $instance->__wakeup(); } @@ -221,8 +224,11 @@ public function newInstanceWithoutConstructor(): object protected function getDocCommentParser() { if (!is_object($this->docCommentParser)) { - $this->docCommentParser = new DocCommentParser; - $this->docCommentParser->parseDocComment($this->getDocComment()); + $this->docCommentParser = new DocCommentParser(); + $docComment = $this->getDocComment(); + if ($docComment) { + $this->docCommentParser->parseDocComment($docComment); + } } return $this->docCommentParser; } diff --git a/Neos.Flow/Classes/Reflection/ClassSchema.php b/Neos.Flow/Classes/Reflection/ClassSchema.php index df24913f7a..98bd7312aa 100644 --- a/Neos.Flow/Classes/Reflection/ClassSchema.php +++ b/Neos.Flow/Classes/Reflection/ClassSchema.php @@ -11,6 +11,7 @@ * source code. */ +use Neos\Flow\Persistence\RepositoryInterface; use Neos\Utility\Exception\InvalidTypeException; use Neos\Utility\TypeHandling; @@ -47,21 +48,27 @@ class ClassSchema protected $lazyLoadable = false; /** - * @var string|null + * @var class-string|null */ protected $repositoryClassName; /** * Properties of the class which need to be persisted * - * @var array + * @var array */ protected $properties = []; /** * The properties forming the identity of an object * - * @var array + * @var array */ protected $identityProperties = []; @@ -137,7 +144,13 @@ public function addProperty($name, $type, $lazy = false, $transient = false) * hasProperty($propertyName) before! * * @param string $propertyName - * @return array + * @return array{ + * type: string, + * elementType: ?string, + * nullable: bool, + * lazy: bool, + * transient: bool, + * } */ public function getProperty($propertyName) { @@ -147,7 +160,13 @@ public function getProperty($propertyName) /** * Returns all properties defined in this schema * - * @return array + * @return array */ public function getProperties() { @@ -198,7 +217,7 @@ public function getModelType() /** * Set the class name of the repository managing an entity. * - * @param string $repositoryClassName + * @param ?class-string $repositoryClassName * @return void * @throws Exception\ClassSchemaConstraintViolationException */ @@ -211,7 +230,7 @@ public function setRepositoryClassName($repositoryClassName) } /** - * @return string + * @return ?class-string */ public function getRepositoryClassName() { @@ -300,7 +319,7 @@ public function markAsIdentityProperty($propertyName) /** * Gets the properties (names and types) forming the identity of an object. * - * @return array + * @return array * @see markAsIdentityProperty() */ public function getIdentityProperties() diff --git a/Neos.Flow/Classes/Reflection/DocCommentParser.php b/Neos.Flow/Classes/Reflection/DocCommentParser.php index 514a976456..3eca9e1db4 100644 --- a/Neos.Flow/Classes/Reflection/DocCommentParser.php +++ b/Neos.Flow/Classes/Reflection/DocCommentParser.php @@ -28,7 +28,7 @@ class DocCommentParser /** * An array of tag names and their values (multiple values are possible) - * @var array + * @var array> */ protected $tags = []; @@ -47,9 +47,9 @@ public function parseDocComment($docComment) $lines = explode(chr(10), $docComment); foreach ($lines as $line) { - $line = trim(preg_replace('/\*\/$/', '', $line)); + $line = trim(preg_replace('/\*\/$/', '', $line) ?: ''); if ($line !== '' && strpos($line, '* @') !== false) { - $this->parseTag(substr($line, strpos($line, '@'))); + $this->parseTag(substr($line, strpos($line, '@') ?: 0)); } elseif (count($this->tags) === 0) { $this->description .= preg_replace('/\s*\\/?[\\\\*]*\s?(.*)$/', '$1', $line) . chr(10); } @@ -60,7 +60,7 @@ public function parseDocComment($docComment) /** * Returns the tags which have been previously parsed * - * @return array Array of tag names and their (multiple) values + * @return array> Array of tag names and their (multiple) values */ public function getTagsValues() { @@ -73,7 +73,7 @@ public function getTagsValues() * available. * * @param string $tagName The tag name to retrieve the values for - * @return array The tag's values + * @return array The tag's values * @throws Exception */ public function getTagValues($tagName) @@ -116,7 +116,7 @@ protected function parseTag($line) { $tagAndValue = []; if (preg_match('/@[A-Za-z0-9\\\\]+\\\\([A-Za-z0-9]+)(?:\\((.*)\\))?$/', $line, $tagAndValue) === 0) { - $tagAndValue = preg_split('/\s/', $line, 2); + $tagAndValue = preg_split('/\s/', $line, 2) ?: []; } else { array_shift($tagAndValue); } diff --git a/Neos.Flow/Classes/Reflection/MethodReflection.php b/Neos.Flow/Classes/Reflection/MethodReflection.php index b5e8a0e1e7..37b4317881 100644 --- a/Neos.Flow/Classes/Reflection/MethodReflection.php +++ b/Neos.Flow/Classes/Reflection/MethodReflection.php @@ -21,7 +21,7 @@ class MethodReflection extends \ReflectionMethod { /** - * @var DocCommentParser An instance of the doc comment parser + * @var ?DocCommentParser An instance of the doc comment parser */ protected $docCommentParser; @@ -66,7 +66,7 @@ public function isTaggedWith($tag) /** * Returns an array of tags and their values * - * @return array Tags and values + * @return array> Tags and values */ public function getTagsValues() { @@ -77,7 +77,7 @@ public function getTagsValues() * Returns the values of the specified tag * * @param string $tag Tag name to check for - * @return array Values of the given tag + * @return array Values of the given tag */ public function getTagValues($tag) { @@ -99,9 +99,6 @@ public function getDescription() */ public function getDeclaredReturnType() { - if (!is_callable([$this, 'getReturnType'])) { - return null; - } $type = $this->getReturnType(); return $type !== null ? ltrim((string)$type, '?') : null; } @@ -111,9 +108,6 @@ public function getDeclaredReturnType() */ public function isDeclaredReturnTypeNullable() { - if (!is_callable([$this, 'getReturnType'])) { - return false; - } $type = $this->getReturnType(); return $type !== null && $type->allowsNull(); } @@ -127,8 +121,11 @@ public function isDeclaredReturnTypeNullable() protected function getDocCommentParser() { if (!is_object($this->docCommentParser)) { - $this->docCommentParser = new DocCommentParser; - $this->docCommentParser->parseDocComment($this->getDocComment()); + $this->docCommentParser = new DocCommentParser(); + $docComment = $this->getDocComment(); + if ($docComment) { + $this->docCommentParser->parseDocComment($docComment); + } } return $this->docCommentParser; } diff --git a/Neos.Flow/Classes/Reflection/ParameterReflection.php b/Neos.Flow/Classes/Reflection/ParameterReflection.php index 94c560aac0..11d4831436 100644 --- a/Neos.Flow/Classes/Reflection/ParameterReflection.php +++ b/Neos.Flow/Classes/Reflection/ParameterReflection.php @@ -28,11 +28,12 @@ class ParameterReflection extends \ReflectionParameter /** * Returns the declaring class * - * @return ClassReflection The declaring class + * @return ?ClassReflection The declaring class */ - public function getDeclaringClass(): ClassReflection + public function getDeclaringClass(): ?ClassReflection { - return new ClassReflection(parent::getDeclaringClass()->getName()); + $reflectionClass = parent::getDeclaringClass(); + return $reflectionClass ? new ClassReflection($reflectionClass->getName()) : null; } /** diff --git a/Neos.Flow/Classes/Reflection/PropertyReflection.php b/Neos.Flow/Classes/Reflection/PropertyReflection.php index 1b59ba4c4e..4afc83c6f8 100644 --- a/Neos.Flow/Classes/Reflection/PropertyReflection.php +++ b/Neos.Flow/Classes/Reflection/PropertyReflection.php @@ -21,7 +21,7 @@ class PropertyReflection extends \ReflectionProperty { /** - * @var DocCommentParser An instance of the doc comment parser + * @var ?DocCommentParser An instance of the doc comment parser */ protected $docCommentParser; @@ -58,7 +58,7 @@ public function getDeclaringClass(): ClassReflection /** * Returns an array of tags and their values * - * @return array Tags and values + * @return array> Tags and values */ public function getTagsValues() { @@ -69,7 +69,7 @@ public function getTagsValues() * Returns the values of the specified tag * * @param string $tag - * @return array Values of the given tag + * @return array Values of the given tag */ public function getTagValues($tag) { @@ -137,8 +137,11 @@ public function setValue($object = null, $value = null): void protected function getDocCommentParser() { if (!is_object($this->docCommentParser)) { - $this->docCommentParser = new DocCommentParser; - $this->docCommentParser->parseDocComment($this->getDocComment()); + $this->docCommentParser = new DocCommentParser(); + $docComment = $this->getDocComment(); + if ($docComment) { + $this->docCommentParser->parseDocComment($docComment); + } } return $this->docCommentParser; } diff --git a/Neos.Flow/Classes/Reflection/ReflectionService.php b/Neos.Flow/Classes/Reflection/ReflectionService.php index 5c720b5db0..e2281a39c1 100644 --- a/Neos.Flow/Classes/Reflection/ReflectionService.php +++ b/Neos.Flow/Classes/Reflection/ReflectionService.php @@ -92,6 +92,9 @@ class ReflectionService protected const DATA_PARAMETER_ANNOTATIONS = 25; protected Reader $annotationReader; + /** + * @var array> class names per package (key) + */ protected array $availableClassNames = []; protected VariableFrontend $reflectionDataRuntimeCache; protected VariableFrontend $classSchemataRuntimeCache; @@ -107,40 +110,87 @@ class ReflectionService /** * a cache which stores the use statements reflected for a particular class * (only relevant for un-expanded "var" and "param" annotations) + * + * @var array> */ protected array $useStatementsForClassCache; + /** + * @var array + */ protected array $settings = []; /** * Array of annotation classnames and the names of classes which are annotated with them + * + * @var array> */ protected array $annotatedClasses = []; /** * Array of method annotations and the classes and methods which are annotated with them + * + * @var array>> */ protected array $classesByMethodAnnotations = []; /** * Schemata of all classes which can be persisted * - * @var array + * @var array */ protected array $classSchemata = []; /** * An array of class names which are currently being forgotten by forgetClass(). Acts as a safeguard against infinite loops. + * + * @var array */ protected array $classesCurrentlyBeingForgotten = []; /** * Array with reflection information indexed by class name + * + * @var array, + * 3?: array, + * 5?: array, + * 6?: true, + * 7?: true, + * 8?: array> + * }>, + * 25: ?string + * }>, + * 9?: array>, + * 15?: array>, + * 24?: int, + * 26?: string, + * 28?: true, + * }>, + * 27?: true, + * }> */ protected array $classReflectionData = []; /** * Array with updated reflection information (e.g. in Development context after classes have changed) + * + * @var array */ protected array $updatedReflectionData = []; @@ -148,6 +198,8 @@ class ReflectionService /** * A runtime cache for reflected method annotations to speed up repeating checks. + * + * @var array>> */ protected array $methodAnnotationsRuntimeCache = []; @@ -162,6 +214,9 @@ public function setClassSchemataRuntimeCache(VariableFrontend $cache): void $this->classSchemataRuntimeCache = $cache; } + /** + * @param array $settings + */ public function injectSettings(array $settings): void { $this->settings = $settings['reflection']; @@ -211,7 +266,7 @@ protected function initialize(): void * This method is called by the Compile Time Object Manager which also determines * the list of classes to consider for reflection. * - * @param array $availableClassNames + * @param array> $availableClassNames * @throws ClassLoadingForReflectionFailedException * @throws ClassSchemaConstraintViolationException * @throws Exception @@ -234,7 +289,7 @@ public function buildReflectionData(array $availableClassNames): void * Tells if the specified class is known to this reflection service and * reflection information is available. * - * @param class-string $className + * @param string $className * * @api */ @@ -245,12 +300,13 @@ public function isClassReflected(string $className): bool } $className = $this->cleanClassName($className); - return isset($this->classReflectionData[$className]) && is_array($this->classReflectionData[$className]); + return array_key_exists($className, $this->classReflectionData); } /** * Returns the names of all classes known to this reflection service. * + * @return array * @api */ public function getAllClassNames(): array @@ -318,6 +374,7 @@ public function getAllImplementationClassNamesForInterface(string $interfaceName } $interfaceName = $this->prepareClassReflectionForUsage($interfaceName); + /** @phpstan-ignore return.type (classic subtype dilemma; don't know how to solve it yet) */ return array_keys($this->classReflectionData[$interfaceName][self::DATA_INTERFACE_IMPLEMENTATIONS] ?? []); } @@ -325,8 +382,9 @@ public function getAllImplementationClassNamesForInterface(string $interfaceName * Searches for and returns all names of classes inheriting the specified class. * If no class inheriting the given class was found, an empty array is returned. * - * @param class-string $className - * @return array + * @template T of object + * @param class-string $className + * @return array> * @throws ClassLoadingForReflectionFailedException * @throws InvalidClassException * @throws \ReflectionException @@ -338,12 +396,15 @@ public function getAllSubClassNamesForClass(string $className): array throw new \InvalidArgumentException('"' . $className . '" does not exist or is not the name of a class.', 1257168042); } $className = $this->prepareClassReflectionForUsage($className); + /** @phpstan-ignore return.type (classic subtype dilemma; don't know how to solve it yet) */ return array_keys($this->classReflectionData[$className][self::DATA_CLASS_SUBCLASSES] ?? []); } /** * Searches for and returns all names of classes which are tagged by the specified * annotation. If no classes were found, an empty array is returned. + * + * @return array */ public function getClassNamesByAnnotation(string $annotationClassName): array { @@ -375,9 +436,10 @@ public function isClassAnnotatedWith(string $className, string $annotationClassN /** * Returns the specified class annotations or an empty array * + * @template T * @param class-string $className - * @param null|class-string $annotationClassName - * @return array + * @param null|class-string $annotationClassName + * @return ($annotationClassName is null ? array : array) * @throws ClassLoadingForReflectionFailedException * @throws InvalidClassException * @throws \ReflectionException @@ -410,9 +472,10 @@ public function getClassAnnotations(string $className, string|null $annotationCl * If multiple annotations are set on the target you will * get the first instance of them. * + * @template T of object * @param class-string $className - * @param class-string $annotationClassName - * @return object|null + * @param class-string $annotationClassName + * @return T|null * @throws ClassLoadingForReflectionFailedException * @throws InvalidClassException * @throws \ReflectionException @@ -424,7 +487,7 @@ public function getClassAnnotation(string $className, string $annotationClassNam } $annotations = $this->getClassAnnotations($className, $annotationClassName); - return $annotations === [] ? null : reset($annotations); + return $annotations[0] ?? null; } /** @@ -505,6 +568,7 @@ public function isClassUnconfigurable(string $className): bool * Returns all class names of classes containing at least one method annotated * with the given annotation class * + * @return array * @api */ public function getClassesContainingMethodsAnnotatedWith(string $annotationClassName): array @@ -519,6 +583,7 @@ public function getClassesContainingMethodsAnnotatedWith(string $annotationClass /** * Returns all names of methods of the given class that are annotated with the given annotation class * + * @return array * @api */ public function getMethodsAnnotatedWith(string $className, string $annotationClassName): array @@ -549,7 +614,7 @@ public function isMethodFinal(string $className, string $methodName): bool /** * Tells if the specified method is declared as static or not * - * @param class-string $className + * @param string $className * @throws ClassLoadingForReflectionFailedException * @throws InvalidClassException * @throws \ReflectionException @@ -588,7 +653,6 @@ public function isMethodProtected(string $className, string $methodName): bool } /** - * @param class-string $className * @throws ClassLoadingForReflectionFailedException * @throws InvalidClassException * @throws \ReflectionException @@ -643,9 +707,10 @@ public function isMethodAnnotatedWith(string $className, string $methodName, str /** * Returns the specified method annotations or an empty array * + * @template T * @param class-string $className - * @param class-string|null $annotationClassName - * @return array + * @param class-string|null $annotationClassName + * @return ($annotationClassName is null ? array : array) * * @throws \ReflectionException * @api @@ -692,6 +757,10 @@ public function getMethodAnnotations(string $className, string $methodName, stri * If multiple annotations are set on the target you will * get the first instance of them. * + * @template T of object + * @param class-string $className + * @param class-string $annotationClassName + * * @throws \ReflectionException */ public function getMethodAnnotation(string $className, string $methodName, string $annotationClassName): ?object @@ -740,6 +809,7 @@ public function hasMethod(string $className, string $methodName): bool * * @throws \ReflectionException * @deprecated since 8.4 + * @return array> */ public function getMethodTagsValues(string $className, string $methodName): array { @@ -756,7 +826,18 @@ public function getMethodTagsValues(string $className, string $methodName): arra * additional information about the parameter position, type hint etc. * * @param class-string $className - * @return array An array of parameter names and additional information or an empty array of no parameters were found + * @return array>, + * }> An array of parameter names and additional information or an empty array of no parameters were found * @throws ClassLoadingForReflectionFailedException * @throws InvalidClassException * @throws \ReflectionException @@ -819,7 +900,7 @@ public function getPropertyNamesByTag(string $className, string $tag): array * * @param class-string $className * @param string $propertyName - * @return array + * @return array> * @throws ClassLoadingForReflectionFailedException * @throws InvalidClassException * @throws \ReflectionException @@ -837,7 +918,7 @@ public function getPropertyTagsValues(string $className, string $propertyName): * @param class-string $className * @param string $propertyName * @param string $tag - * @return array + * @return array * @throws ClassLoadingForReflectionFailedException * @throws InvalidClassException * @throws \ReflectionException @@ -958,10 +1039,11 @@ public function getPropertyNamesByAnnotation(string $className, string $annotati /** * Returns the specified property annotations or an empty array * + * @template Annotation of object * @param class-string $className * @param string $propertyName - * @param class-string|null $annotationClassName - * @return array + * @param class-string|null $annotationClassName + * @return ($annotationClassName is null ? array> : array) * @throws ClassLoadingForReflectionFailedException * @throws InvalidClassException * @throws \ReflectionException @@ -978,6 +1060,9 @@ public function getPropertyAnnotations(string $className, string $propertyName, return $this->classReflectionData[$className][self::DATA_CLASS_PROPERTIES][$propertyName][self::DATA_PROPERTY_ANNOTATIONS]; } + /** @phpstan-ignore return.type + * (in case of an annotation class name, this is really the annotation instance; The array iss just mixed with arbitrary-string-indexed other objects) + */ return $this->classReflectionData[$className][self::DATA_CLASS_PROPERTIES][$propertyName][self::DATA_PROPERTY_ANNOTATIONS][$annotationClassName] ?? []; } @@ -987,10 +1072,11 @@ public function getPropertyAnnotations(string $className, string $propertyName, * If multiple annotations are set on the target you will * get the first instance of them. * - * @param string $className + * @template Annotation of object + * @param class-string $className * @param string $propertyName - * @param string $annotationClassName - * @return object|null + * @param class-string $annotationClassName + * @return Annotation|null * @throws ClassLoadingForReflectionFailedException * @throws InvalidClassException * @throws \ReflectionException @@ -1001,13 +1087,13 @@ public function getPropertyAnnotation(string $className, string $propertyName, s $this->initialize(); } $annotations = $this->getPropertyAnnotations($className, $propertyName, $annotationClassName); - return $annotations === [] ? null : reset($annotations); + return reset($annotations) ?: null; } /** * Returns the class schema for the given class * - * @param class-string|object $classNameOrObject + * @param string|object $classNameOrObject * @return ClassSchema|null */ public function getClassSchema(string|object $classNameOrObject): ?ClassSchema @@ -1017,8 +1103,8 @@ public function getClassSchema(string|object $classNameOrObject): ?ClassSchema } $className = $classNameOrObject; - if (is_object($classNameOrObject)) { - $className = TypeHandling::getTypeForValue($classNameOrObject); + if (is_object($className)) { + $className = TypeHandling::getTypeForValue($className); } $className = $this->cleanClassName($className); @@ -1033,8 +1119,8 @@ public function getClassSchema(string|object $classNameOrObject): ?ClassSchema /** * Initializes the ReflectionService, cleans the given class name and finally reflects the class if necessary. * - * @param class-string $className - * @return string + * @param string $className + * @return class-string * @throws ClassLoadingForReflectionFailedException * @throws InvalidClassException * @throws \ReflectionException @@ -1123,7 +1209,7 @@ protected function isTagIgnored(string $tagName): bool /** * Reflects the given class and stores the results in this service's properties. * - * @param class-string $className + * @param string $className * @throws ClassLoadingForReflectionFailedException * @throws InvalidClassException * @throws \ReflectionException @@ -1138,7 +1224,7 @@ protected function reflectClass(string $className): void } $class = new ClassReflection($className); - if (!isset($this->classReflectionData[$className]) || !is_array($this->classReflectionData[$className])) { + if (!array_key_exists($className, $this->classReflectionData)) { $this->classReflectionData[$className] = []; } @@ -1194,6 +1280,7 @@ protected function reflectClass(string $className): void } /** + * @param class-string $className * @return int visibility */ public function reflectClassProperty(string $className, PropertyReflection $property): int @@ -1242,6 +1329,10 @@ public function reflectClassProperty(string $className, PropertyReflection $prop return $visibility; } + /** + * @param array $tagValues + * @return array|null + */ protected function reflectPropertyTag(string $className, PropertyReflection $property, string $tagName, array $tagValues): ?array { if ($this->isTagIgnored($tagName)) { @@ -1263,12 +1354,14 @@ protected function reflectPropertyTag(string $className, PropertyReflection $pro } /** + * @param class-string $className * @throws InvalidClassException * @throws ClassLoadingForReflectionFailedException * @throws \ReflectionException */ protected function addParentClass(string $className, ClassReflection $parentClass): void { + /** @var class-string $parentClassName */ $parentClassName = $parentClass->getName(); if (!$this->isClassReflected($parentClassName)) { $this->loadOrReflectClassIfNecessary($parentClassName); @@ -1277,6 +1370,7 @@ protected function addParentClass(string $className, ClassReflection $parentClas } /** + * @param class-string $className * @throws ClassLoadingForReflectionFailedException * @throws InvalidClassException * @throws \ReflectionException @@ -1447,6 +1541,7 @@ protected function expandType(ClassReflection $class, string $type): string /** * Finds all parent classes of the given class * + * @param array $parentClasses * @return array */ protected function getParentClasses(ClassReflection $class, array $parentClasses = []): array @@ -1514,7 +1609,11 @@ protected function buildClassSchema(string $className): ClassSchema } $possibleRepositoryClassName = str_replace('\\Model\\', '\\Repository\\', $className) . 'Repository'; - if ($this->isClassReflected($possibleRepositoryClassName) === true) { + if ( + class_exists($possibleRepositoryClassName) + && is_subclass_of($possibleRepositoryClassName, RepositoryInterface::class) + && $this->isClassReflected($possibleRepositoryClassName) === true + ) { $classSchema->setRepositoryClassName($possibleRepositoryClassName); } @@ -1540,7 +1639,6 @@ protected function addPropertiesToClassSchema(ClassSchema $classSchema): void $className = $classSchema->getClassName(); $skipArtificialIdentity = false; - /* @var $valueObjectAnnotation Flow\ValueObject */ $valueObjectAnnotation = $this->getClassAnnotation($className, Flow\ValueObject::class); if ($valueObjectAnnotation !== null && $valueObjectAnnotation->embedded === true) { $skipArtificialIdentity = true; @@ -1594,6 +1692,9 @@ protected function evaluateClassPropertyAnnotationsForSchema(ClassSchema $classS } $declaredType = strtok(trim(current($varTagValues), " \n\t"), " \n\t"); + if ($declaredType === false) { + throw new \Exception('Invalid type for ' . $className . '::$' . $propertyName, 1744046737); + } if ($this->isPropertyAnnotatedWith($className, $propertyName, ORM\Id::class)) { $skipArtificialIdentity = true; @@ -1644,7 +1745,7 @@ protected function completeRepositoryAssignments(): void } foreach ($this->classSchemata as $classSchema) { - if ($classSchema instanceof ClassSchema && class_exists($classSchema->getClassName()) && $classSchema->isAggregateRoot()) { + if (class_exists($classSchema->getClassName()) && $classSchema->isAggregateRoot()) { $this->makeChildClassesAggregateRoot($classSchema); } } @@ -1684,7 +1785,7 @@ protected function makeChildClassesAggregateRoot(ClassSchema $classSchema): void protected function ensureAggregateRootInheritanceChainConsistency(): void { foreach ($this->classSchemata as $className => $classSchema) { - if (!class_exists($className) || ($classSchema instanceof ClassSchema && $classSchema->isAggregateRoot() === false)) { + if (!class_exists($className) || ($classSchema->isAggregateRoot() === false)) { continue; } @@ -1726,8 +1827,30 @@ protected function checkValueObjectRequirements(string $className): void * Converts the internal, optimized data structure of parameter information into * a human-friendly array with speaking indexes. * - * @param array $parametersInformation Raw, internal parameter information - * @return array Developer friendly version + * @param array>, + * }> $parametersInformation Raw, internal parameter information + * @return array>, + * }> Developer friendly version */ protected function convertParameterDataToArray(array $parametersInformation): array { @@ -1736,7 +1859,7 @@ protected function convertParameterDataToArray(array $parametersInformation): ar $parameters[$parameterName] = [ 'position' => $parameterData[self::DATA_PARAMETER_POSITION], 'optional' => isset($parameterData[self::DATA_PARAMETER_OPTIONAL]), - 'type' => $parameterData[self::DATA_PARAMETER_TYPE], + 'type' => $parameterData[self::DATA_PARAMETER_TYPE] ?? null, 'class' => $parameterData[self::DATA_PARAMETER_CLASS] ?? null, 'array' => isset($parameterData[self::DATA_PARAMETER_ARRAY]), 'byReference' => isset($parameterData[self::DATA_PARAMETER_BY_REFERENCE]), @@ -1753,7 +1876,18 @@ protected function convertParameterDataToArray(array $parametersInformation): ar /** * Converts the given parameter reflection into an information array * - * @return array Parameter information array + * @return array{ + * 16: int, + * 17?: true, + * 18?: string, + * 19?: true, + * 20?: string, + * 21?: true, + * 22?: mixed, + * 23?: true, + * 24?: true, + * 25?: array> + * } Parameter information array */ protected function convertParameterReflectionToArray(ParameterReflection $parameter, MethodReflection $method): array { @@ -1820,7 +1954,7 @@ protected function convertParameterReflectionToArray(ParameterReflection $parame protected function forgetChangedClasses(): void { foreach ($this->classReflectionData as $className => $_) { - if (is_string($className) && !$this->reflectionDataRuntimeCache->has($this->produceCacheIdentifierFromClassName($className))) { + if (!$this->reflectionDataRuntimeCache->has($this->produceCacheIdentifierFromClassName($className))) { $this->forgetClass($className); } } @@ -1953,10 +2087,16 @@ public function saveToCache(): void /** * Clean a given class name from possibly prefixed backslash + * @return class-string */ protected function cleanClassName(string $className): string { - return ltrim($className, '\\'); + $className = ltrim($className, '\\'); + if (!class_exists($className)) { + throw new \Exception('Invalid class ' . $className, 1744047892); + } + + return $className; } /** @@ -1969,6 +2109,7 @@ protected function produceCacheIdentifierFromClassName(string $className): strin /** * Writes the given message along with the additional information into the log. + * @param array $additionalData */ protected function log(string $message, string $severity = LogLevel::INFO, array $additionalData = []): void { @@ -1992,7 +2133,7 @@ function (\ReflectionNamedType|\ReflectionIntersectionType $type) { $parameterType->getTypes() )), $parameterType instanceof \ReflectionIntersectionType => implode('&', array_map( - function (\ReflectionNamedType $type) { + function (\ReflectionType $type) { return $this->renderParameterType($type); }, $parameterType->getTypes() diff --git a/Neos.Flow/Classes/Reflection/ReflectionServiceFactory.php b/Neos.Flow/Classes/Reflection/ReflectionServiceFactory.php index e3e5ed1429..61ef500d0f 100644 --- a/Neos.Flow/Classes/Reflection/ReflectionServiceFactory.php +++ b/Neos.Flow/Classes/Reflection/ReflectionServiceFactory.php @@ -11,6 +11,7 @@ * source code. */ +use Neos\Cache\Frontend\VariableFrontend; use Neos\Flow\Annotations as Flow; use Neos\Flow\Cache\CacheManager; use Neos\Flow\Configuration\ConfigurationManager; @@ -62,8 +63,16 @@ public function create(): ReflectionService $reflectionService = new ReflectionService(); $reflectionService->injectLogger($this->bootstrap->getEarlyInstance(PsrLoggerFactoryInterface::class)->get('systemLogger')); $reflectionService->injectSettings($settings); - $reflectionService->setReflectionDataRuntimeCache($cacheManager->getCache('Flow_Reflection_RuntimeData')); - $reflectionService->setClassSchemataRuntimeCache($cacheManager->getCache('Flow_Reflection_RuntimeClassSchemata')); + $runtimeDataCache = $cacheManager->getCache('Flow_Reflection_RuntimeData'); + if (!$runtimeDataCache instanceof VariableFrontend) { + throw new \Exception('Cache Flow_Reflection_RuntimeData must have a VariableFrontend', 1743972055); + } + $reflectionService->setReflectionDataRuntimeCache($runtimeDataCache); + $runtimeClassSchemataCache = $cacheManager->getCache('Flow_Reflection_RuntimeClassSchemata'); + if (!$runtimeClassSchemataCache instanceof VariableFrontend) { + throw new \Exception('Cache Flow_Reflection_RuntimeClassSchemata must have a VariableFrontend', 1743972058); + } + $reflectionService->setClassSchemataRuntimeCache($runtimeClassSchemataCache); $this->reflectionService = $reflectionService; return $reflectionService; diff --git a/Neos.Flow/Classes/ResourceManagement/Collection.php b/Neos.Flow/Classes/ResourceManagement/Collection.php index 6b07307bb0..275e85cf4c 100644 --- a/Neos.Flow/Classes/ResourceManagement/Collection.php +++ b/Neos.Flow/Classes/ResourceManagement/Collection.php @@ -40,7 +40,7 @@ class Collection implements CollectionInterface protected $target; /** - * @var array + * @var array */ protected $pathPatterns; @@ -56,7 +56,7 @@ class Collection implements CollectionInterface * @param string $name User-space name of this collection, as specified in the settings * @param StorageInterface $storage The storage for data used in this collection * @param TargetInterface $target The publication target for this collection - * @param array $pathPatterns Glob patterns for paths to consider – only supported by specific storages + * @param array $pathPatterns Glob patterns for paths to consider – only supported by specific storages */ public function __construct($name, StorageInterface $storage, TargetInterface $target, array $pathPatterns) { diff --git a/Neos.Flow/Classes/ResourceManagement/PersistentResource.php b/Neos.Flow/Classes/ResourceManagement/PersistentResource.php index f431792dde..4e47d9f839 100644 --- a/Neos.Flow/Classes/ResourceManagement/PersistentResource.php +++ b/Neos.Flow/Classes/ResourceManagement/PersistentResource.php @@ -179,7 +179,7 @@ public function setFilename($filename) $this->throwExceptionIfProtected(); $pathInfo = UnicodeFunctions::pathinfo($filename); - $extension = (isset($pathInfo['extension']) ? '.' . strtolower($pathInfo['extension']) : ''); + $extension = '.' . strtolower($pathInfo['extension']); $this->filename = $pathInfo['filename'] . $extension; $this->mediaType = Utility\MediaTypes::getMediaTypeFromFilename($this->filename); } @@ -298,10 +298,10 @@ public function getSha1() * @return void * @api */ - public function setSha1($sha1) + public function setSha1(string $sha1) { $this->throwExceptionIfProtected(); - if (!is_string($sha1) || preg_match('/[A-Fa-f0-9]{40}/', $sha1) !== 1) { + if (preg_match('/[A-Fa-f0-9]{40}/', $sha1) !== 1) { throw new \InvalidArgumentException('Specified invalid hash to setSha1()', 1362564220); } $this->sha1 = strtolower($sha1); @@ -330,9 +330,9 @@ public function createTemporaryLocalCopy() $temporaryPathAndFilename .= '-' . microtime(true); if (function_exists('posix_getpid')) { - $temporaryPathAndFilename .= '-' . str_pad(posix_getpid(), 10); + $temporaryPathAndFilename .= '-' . str_pad((string)posix_getpid(), 10); } else { - $temporaryPathAndFilename .= '-' . (string) getmypid(); + $temporaryPathAndFilename .= '-' . getmypid(); } $temporaryPathAndFilename = trim($temporaryPathAndFilename); @@ -364,7 +364,9 @@ public function postPersist() { if ($this->lifecycleEventsActive) { $collection = $this->resourceManager->getCollection($this->collectionName); - $collection->getTarget()->publishResource($this, $collection); + if ($collection) { + $collection->getTarget()->publishResource($this, $collection); + } } } diff --git a/Neos.Flow/Classes/ResourceManagement/Publishing/MessageCollector.php b/Neos.Flow/Classes/ResourceManagement/Publishing/MessageCollector.php index ffba74d24a..b5b6ecefc0 100644 --- a/Neos.Flow/Classes/ResourceManagement/Publishing/MessageCollector.php +++ b/Neos.Flow/Classes/ResourceManagement/Publishing/MessageCollector.php @@ -35,7 +35,7 @@ class MessageCollector ]; /** - * @var \SplObjectStorage + * @var \SplObjectStorage */ protected $messages; diff --git a/Neos.Flow/Classes/ResourceManagement/ResourceManager.php b/Neos.Flow/Classes/ResourceManagement/ResourceManager.php index 8956aa1770..c513b5bb7a 100644 --- a/Neos.Flow/Classes/ResourceManagement/ResourceManager.php +++ b/Neos.Flow/Classes/ResourceManagement/ResourceManager.php @@ -65,7 +65,7 @@ class ResourceManager protected $persistenceManager; /** - * @var array + * @var array */ protected $settings; @@ -98,7 +98,7 @@ class ResourceManager /** * Injects the settings of this package * - * @param array $settings + * @param array $settings * @return void */ public function injectSettings(array $settings) @@ -156,7 +156,6 @@ public function importResource($source, $collectionName = ResourceManager::DEFAU throw new Exception(sprintf('Tried to import a file into the resource collection "%s" but no such collection exists. Please check your settings and the code which triggered the import.', $collectionName), 1375196643); } - /* @var CollectionInterface $collection */ $collection = $this->collections[$collectionName]; try { @@ -165,15 +164,17 @@ public function importResource($source, $collectionName = ResourceManager::DEFAU ObjectAccess::setProperty($resource, 'Persistence_Object_Identifier', $forcedPersistenceObjectIdentifier, true); } if (!is_resource($source)) { + /** @var string $source */ $pathInfo = UnicodeFunctions::pathinfo($source); + /** @var PersistentResource $resource */ $resource->setFilename($pathInfo['basename']); } } catch (Exception $exception) { throw new Exception(sprintf('Importing a file into the resource collection "%s" failed: %s', $collectionName, $exception->getMessage()), 1375197120, $exception); } - + /** @var PersistentResource $resource */ $this->resourceRepository->add($resource); - $this->logger->debug(sprintf('Successfully imported file "%s" into the resource collection "%s" (storage: %s, a %s. SHA1: %s)', $source, $collectionName, $collection->getStorage()->getName(), get_class($collection), $resource->getSha1())); + $this->logger->debug(sprintf('Successfully imported file into the resource collection "%s" (storage: %s, a %s. SHA1: %s)', $collectionName, $collection->getStorage()->getName(), get_class($collection), $resource->getSha1())); return $resource; } @@ -195,18 +196,14 @@ public function importResource($source, $collectionName = ResourceManager::DEFAU * @throws Exception * @api */ - public function importResourceFromContent($content, $filename, $collectionName = ResourceManager::DEFAULT_PERSISTENT_COLLECTION_NAME, $forcedPersistenceObjectIdentifier = null) + public function importResourceFromContent(string $content, $filename, $collectionName = ResourceManager::DEFAULT_PERSISTENT_COLLECTION_NAME, $forcedPersistenceObjectIdentifier = null) { - if (!is_string($content)) { - throw new Exception(sprintf('Tried to import content into the resource collection "%s" but the given content was a %s instead of a string.', $collectionName, gettype($content)), 1380878115); - } $this->initialize(); if (!isset($this->collections[$collectionName])) { throw new Exception(sprintf('Tried to import a file into the resource collection "%s" but no such collection exists. Please check your settings and the code which triggered the import.', $collectionName), 1380878131); } - /* @var CollectionInterface $collection */ $collection = $this->collections[$collectionName]; try { @@ -219,6 +216,7 @@ public function importResourceFromContent($content, $filename, $collectionName = throw new Exception(sprintf('Importing content into the resource collection "%s" failed: %s', $collectionName, $exception->getMessage()), 1381156155, $exception); } + /** @var PersistentResource $resource */ $this->resourceRepository->add($resource); $this->logger->debug(sprintf('Successfully imported content into the resource collection "%s" (storage: %s, a %s. SHA1: %s)', $collectionName, $collection->getStorage()->getName(), get_class($collection->getStorage()), $resource->getSha1())); @@ -232,7 +230,7 @@ public function importResourceFromContent($content, $filename, $collectionName = * On a successful import this method returns a PersistentResource object representing * the newly imported persistent resource. * - * @param array $uploadInfo An array detailing the resource to import (expected keys: name, tmp_name) + * @param array $uploadInfo An array detailing the resource to import (expected keys: name, tmp_name) * @param string $collectionName Name of the collection this uploaded resource should be added to * @return PersistentResource A resource object representing the imported resource * @throws Exception @@ -303,7 +301,7 @@ public function getStreamByResource(PersistentResource $resource) * $resource2 => array('originalFilename' => 'Bar.txt'), * ... * - * @return \SplObjectStorage + * @return \SplObjectStorage * @api */ public function getImportedResources() @@ -449,7 +447,7 @@ public function getPublicPackageResourceUriByPath($path) * Return the package key and the relative path and filename from the given resource path * * @param string $path The ressource path, like resource://Your.Package/Public/Image/Dummy.png - * @return array The array contains two value, first the packageKey followed by the relativePathAndFilename + * @return array{0: string, 1: string} The array contains two value, first the packageKey followed by the relativePathAndFilename * @throws Exception * @api */ @@ -554,7 +552,10 @@ protected function initializeStorages() if (!class_exists($storageDefinition['storage'])) { throw new Exception(sprintf('The configuration for the resource storage "%s" defined in your settings has not defined a valid "storage" option. Please check the configuration syntax and make sure that the specified class "%s" really exists.', $storageName, $storageDefinition['storage']), 1361467212); } - $options = (isset($storageDefinition['storageOptions']) ? $storageDefinition['storageOptions'] : []); + if (!is_subclass_of($storageDefinition['storage'], StorageInterface::class)) { + throw new Exception('Configured storage ' . $storageDefinition['storage'] . ' does not implement the required ' . StorageInterface::class, 1743970047); + } + $options = ($storageDefinition['storageOptions'] ?? []); $this->storages[$storageName] = new $storageDefinition['storage']($storageName, $options); } } @@ -574,7 +575,10 @@ protected function initializeTargets() if (!class_exists($targetDefinition['target'])) { throw new Exception(sprintf('The configuration for the resource target "%s" defined in your settings has not defined a valid "target" option. Please check the configuration syntax and make sure that the specified class "%s" really exists.', $targetName, $targetDefinition['target']), 1361467839); } - $options = (isset($targetDefinition['targetOptions']) ? $targetDefinition['targetOptions'] : []); + if (!is_subclass_of($targetDefinition['target'], TargetInterface::class)) { + throw new Exception('Configured resource target ' . $targetDefinition['target'] . ' does not implement the required ' . TargetInterface::class, 1743969958); + } + $options = $targetDefinition['targetOptions'] ?? []; $this->targets[$targetName] = new $targetDefinition['target']($targetName, $options); } } @@ -612,21 +616,24 @@ protected function initializeCollections() * Prepare an uploaded file to be imported as resource object. Will check the validity of the file, * move it outside of upload folder if open_basedir is enabled and check the filename. * - * @param array $uploadInfo - * @return array Array of string with the two keys "filepath" (the path to get the filecontent from) and "filename" the filename of the originally uploaded file. + * @param array $uploadInfo + * @return array{filepath: string, filename: string} Array of string with the two keys "filepath" (the path to get the filecontent from) and "filename" the filename of the originally uploaded file. * @throws Exception */ protected function prepareUploadedFileForImport(array $uploadInfo) { $openBasedirEnabled = (boolean)ini_get('open_basedir'); - $temporaryTargetPathAndFilename = $uploadInfo['tmp_name']; + $temporaryTargetPathAndFilename = $uploadInfo['tmp_name'] ?? null; + if (!is_string($temporaryTargetPathAndFilename)) { + throw new \Exception('String tmp_name required', 1743969722); + } $pathInfo = UnicodeFunctions::pathinfo($uploadInfo['name']); if (!is_uploaded_file($temporaryTargetPathAndFilename)) { throw new Exception('The given upload file "' . strip_tags($pathInfo['basename']) . '" was not uploaded through PHP. As it could pose a security risk it cannot be imported.', 1422461503); } - if (isset($pathInfo['extension']) && array_key_exists(strtolower($pathInfo['extension']), $this->settings['extensionsBlockedFromUpload']) && $this->settings['extensionsBlockedFromUpload'][strtolower($pathInfo['extension'])] === true) { + if (array_key_exists(strtolower($pathInfo['extension']), $this->settings['extensionsBlockedFromUpload']) && $this->settings['extensionsBlockedFromUpload'][strtolower($pathInfo['extension'])] === true) { throw new Exception('The extension of the given upload file "' . strip_tags($pathInfo['basename']) . '" is excluded. As it could pose a security risk it cannot be imported.', 1447148472); } @@ -645,7 +652,7 @@ protected function prepareUploadedFileForImport(array $uploadInfo) return [ 'filepath' => $temporaryTargetPathAndFilename, - 'filename' => $pathInfo['basename'] + 'filename' => $pathInfo['basename'], ]; } } diff --git a/Neos.Flow/Classes/ResourceManagement/ResourceMetaDataInterface.php b/Neos.Flow/Classes/ResourceManagement/ResourceMetaDataInterface.php index 8cd9b10a9d..89556eac2e 100644 --- a/Neos.Flow/Classes/ResourceManagement/ResourceMetaDataInterface.php +++ b/Neos.Flow/Classes/ResourceManagement/ResourceMetaDataInterface.php @@ -80,5 +80,5 @@ public function getSha1(); * @param string $sha1 The sha1 hash * @return void */ - public function setSha1($sha1); + public function setSha1(string $sha1); } diff --git a/Neos.Flow/Classes/ResourceManagement/ResourceRepository.php b/Neos.Flow/Classes/ResourceManagement/ResourceRepository.php index 82bb9dcf7a..605ba63894 100644 --- a/Neos.Flow/Classes/ResourceManagement/ResourceRepository.php +++ b/Neos.Flow/Classes/ResourceManagement/ResourceRepository.php @@ -48,12 +48,12 @@ class ResourceRepository extends Repository protected $persistenceManager; /** - * @var \SplObjectStorage|PersistentResource[] + * @var \SplObjectStorage */ protected $removedResources; /** - * @var \SplObjectStorage|PersistentResource[] + * @var \SplObjectStorage */ protected $addedResources; @@ -73,6 +73,9 @@ public function __construct() */ public function add($object): void { + if (!($object instanceof PersistentResource)) { + throw new \Exception('Can only add objects of type PersistentResource', 1743969457); + } $this->persistenceManager->allowObject($object); if ($this->removedResources->contains($object)) { $this->removedResources->detach($object); @@ -91,6 +94,9 @@ public function add($object): void */ public function remove($object): void { + if (!($object instanceof PersistentResource)) { + throw new \Exception('Can only remove objects of type PersistentResource', 1743969438); + } // Intercept a second call for the same PersistentResource object because it might cause an endless loop caused by // the ResourceManager's deleteResource() method which also calls this remove() function: if (!$this->removedResources->contains($object)) { @@ -188,7 +194,7 @@ public function findSimilarResources(PersistentResource $resource) * Find all resources with the same SHA1 hash * * @param string $sha1Hash - * @return array + * @return array */ public function findBySha1($sha1Hash) { @@ -209,7 +215,7 @@ public function findBySha1($sha1Hash) * * @param string $sha1Hash * @param string $collectionName - * @return array + * @return array */ public function findBySha1AndCollectionName($sha1Hash, $collectionName) { @@ -261,16 +267,15 @@ public function countBySha1AndCollectionName(string $sha1Hash, string $collectio * Find one resource by SHA1 * * @param string $sha1Hash - * @return PersistentResource + * @return ?PersistentResource */ public function findOneBySha1($sha1Hash) { $query = $this->createQuery(); $query->matching($query->equals('sha1', $sha1Hash))->setLimit(1); - /** @var PersistentResource $resource */ + /** @var ?PersistentResource $resource */ $resource = $query->execute()->getFirst(); if ($resource === null) { - /** @var PersistentResource $importedResource */ foreach ($this->addedResources as $importedResource) { if ($importedResource->getSha1() === $sha1Hash) { return $importedResource; @@ -282,7 +287,7 @@ public function findOneBySha1($sha1Hash) } /** - * @return \SplObjectStorage + * @return \SplObjectStorage */ public function getAddedResources() { diff --git a/Neos.Flow/Classes/ResourceManagement/ResourceTypeConverter.php b/Neos.Flow/Classes/ResourceManagement/ResourceTypeConverter.php index fc81836e5b..cb806ee1d2 100644 --- a/Neos.Flow/Classes/ResourceManagement/ResourceTypeConverter.php +++ b/Neos.Flow/Classes/ResourceManagement/ResourceTypeConverter.php @@ -119,7 +119,7 @@ class ResourceTypeConverter extends AbstractTypeConverter protected $logger; /** - * @var array + * @var array */ protected $convertedResources = []; @@ -144,9 +144,9 @@ public function injectLogger(LoggerInterface $logger) * Note that $source['error'] will also be present if a file was successfully * uploaded. In that case its value will be \UPLOAD_ERR_OK. * - * @param array|string|UploadedFileInterface $source The upload info (expected keys: error, name, tmp_name), the hash or an UploadedFile + * @param array|string|UploadedFileInterface $source The upload info (expected keys: error, name, tmp_name), the hash or an UploadedFile * @param string $targetType - * @param array $convertedChildProperties + * @param array $convertedChildProperties * @param PropertyMappingConfigurationInterface|null $configuration * @return PersistentResource|null|FlowError if the input format is not supported or could not be converted for other reasons * @throws Exception @@ -177,7 +177,7 @@ public function convertFrom($source, $targetType, array $convertedChildPropertie } /** - * @param array $source + * @param array $source * @param PropertyMappingConfigurationInterface|null $configuration * @return PersistentResource|null|FlowError */ @@ -219,7 +219,7 @@ protected function handleFileUploads(array $source, ?PropertyMappingConfiguratio } /** - * @param array $source + * @param array $source * @param PropertyMappingConfigurationInterface|null $configuration * @return PersistentResource|FlowError * @throws Exception @@ -256,12 +256,13 @@ protected function handleHashAndData(array $source, ?PropertyMappingConfiguratio if (isset($source['data']) && isset($source['filename'])) { $resource = $this->resourceManager->importResourceFromContent(base64_decode($source['data']), $source['filename'], $collectionName, $givenResourceIdentity); } elseif ($hash !== null) { - $resource = $this->resourceManager->importResource($configuration->getConfigurationValue(ResourceTypeConverter::class, self::CONFIGURATION_RESOURCE_LOAD_PATH) . '/' . $hash, $collectionName, $givenResourceIdentity); - if (is_array($source) && isset($source['filename'])) { - $resource->setFilename($source['filename']); + $resource = $this->resourceManager->importResource($configuration?->getConfigurationValue(ResourceTypeConverter::class, self::CONFIGURATION_RESOURCE_LOAD_PATH) . '/' . $hash, $collectionName, $givenResourceIdentity); + $filename = $source['filename'] ?? null; + if (is_string($filename)) { + $resource->setFilename($filename); } } - if ($hash !== null && $resource->getSha1() !== $hash) { + if ($hash !== null && $resource?->getSha1() !== $hash) { throw new Exception\InvalidResourceDataException('The source SHA1 did not match the SHA1 of the imported resource.', 1482248149); } } @@ -297,7 +298,7 @@ protected function handleUploadedFile(UploadedFileInterface $source, ?PropertyMa case \UPLOAD_ERR_PARTIAL: return new FlowError(Files::getUploadErrorMessage($source->getError()), 1264440823); default: - $this->logger->error(sprintf('A server error occurred while converting an uploaded resource: "%s"', Files::getUploadErrorMessage($source['error'])), LogEnvironment::fromMethodName(__METHOD__)); + $this->logger->error(sprintf('A server error occurred while converting an uploaded resource: "%s"', Files::getUploadErrorMessage($source->getError())), LogEnvironment::fromMethodName(__METHOD__)); return new FlowError('An error occurred while uploading. Please try again or contact the administrator if the problem remains', 1340193849); } @@ -307,8 +308,15 @@ protected function handleUploadedFile(UploadedFileInterface $source, ?PropertyMa } try { - $resource = $this->resourceManager->importResource($source->getStream()->detach(), $this->getCollectionName($source, $configuration)); - $resource->setFilename($source->getClientFilename()); + $sourceForImport = $source->getStream()->detach(); + if ($sourceForImport === null) { + throw new \Exception('Failed to detach resource from stream', 1743967911); + } + $resource = $this->resourceManager->importResource($sourceForImport, $this->getCollectionName($source, $configuration)); + $filename = $source->getClientFilename(); + if ($filename !== null) { + $resource->setFilename($filename); + } $this->convertedResources[spl_object_hash($source)] = $resource; return $resource; } catch (\Exception $exception) { @@ -324,7 +332,7 @@ protected function handleUploadedFile(UploadedFileInterface $source, ?PropertyMa * The propertyMappingConfiguration CONFIGURATION_COLLECTION_NAME will directly override the default. Then if CONFIGURATION_ALLOW_COLLECTION_OVERRIDE is true * and __collectionName is in the $source this will finally be the value. * - * @param array|UploadedFileInterface $source + * @param array|UploadedFileInterface $source * @param PropertyMappingConfigurationInterface|null $configuration * @return string * @throws InvalidPropertyMappingConfigurationException diff --git a/Neos.Flow/Classes/ResourceManagement/Storage/FileSystemStorage.php b/Neos.Flow/Classes/ResourceManagement/Storage/FileSystemStorage.php index 6e4f0536cf..ad0ee6580c 100644 --- a/Neos.Flow/Classes/ResourceManagement/Storage/FileSystemStorage.php +++ b/Neos.Flow/Classes/ResourceManagement/Storage/FileSystemStorage.php @@ -59,7 +59,7 @@ class FileSystemStorage implements StorageInterface * Constructor * * @param string $name Name of this storage instance, according to the resource settings - * @param array $options Options for this storage + * @param array $options Options for this storage * @throws Exception */ public function __construct($name, array $options = []) diff --git a/Neos.Flow/Classes/ResourceManagement/Storage/PackageStorage.php b/Neos.Flow/Classes/ResourceManagement/Storage/PackageStorage.php index d0ff7285aa..23ead9adba 100644 --- a/Neos.Flow/Classes/ResourceManagement/Storage/PackageStorage.php +++ b/Neos.Flow/Classes/ResourceManagement/Storage/PackageStorage.php @@ -71,7 +71,10 @@ public function getObjectsByPathPattern($pattern) if ($directoryPattern === '*') { $directories[$packageKey][] = $package->getPackagePath(); } else { - $directories[$packageKey] = glob($package->getPackagePath() . $directoryPattern, GLOB_ONLYDIR); + $packageDirectories = glob($package->getPackagePath() . $directoryPattern, GLOB_ONLYDIR); + if ($packageDirectories !== false) { + $directories[$packageKey] = $packageDirectories; + } } } @@ -98,11 +101,15 @@ protected function createStorageObject($resourcePathAndFilename, FlowPackageInte $object = new StorageObject(); $object->setFilename($pathInfo['basename']); - $object->setSha1(sha1_file($resourcePathAndFilename)); - $object->setFileSize(filesize($resourcePathAndFilename)); - if (isset($pathInfo['dirname'])) { - $object->setRelativePublicationPath($this->prepareRelativePublicationPath($pathInfo['dirname'], $resourcePackage->getPackageKey(), $resourcePackage->getResourcesPath())); + $sha1 = sha1_file($resourcePathAndFilename); + if ($sha1 !== false) { + $object->setSha1($sha1); + } + $fileSize = filesize($resourcePathAndFilename); + if ($fileSize !== false) { + $object->setFileSize($fileSize); } + $object->setRelativePublicationPath($this->prepareRelativePublicationPath($pathInfo['dirname'], $resourcePackage->getPackageKey(), $resourcePackage->getResourcesPath())); $object->setStream(function () use ($resourcePathAndFilename) { return fopen($resourcePathAndFilename, 'r'); }); diff --git a/Neos.Flow/Classes/ResourceManagement/Storage/StorageObject.php b/Neos.Flow/Classes/ResourceManagement/Storage/StorageObject.php index 40dc365534..53d37a923e 100644 --- a/Neos.Flow/Classes/ResourceManagement/Storage/StorageObject.php +++ b/Neos.Flow/Classes/ResourceManagement/Storage/StorageObject.php @@ -98,7 +98,7 @@ public function getMediaType() public function setFilename($filename) { $pathInfo = UnicodeFunctions::pathinfo($filename); - $extension = (isset($pathInfo['extension']) ? '.' . strtolower($pathInfo['extension']) : ''); + $extension = '.' . strtolower($pathInfo['extension']); $this->filename = $pathInfo['filename'] . $extension; $this->mediaType = MediaTypes::getMediaTypeFromFilename($this->filename); } @@ -201,7 +201,11 @@ public function setStream($stream) public function getStream() { if ($this->stream instanceof \Closure) { - $this->stream = $this->stream->__invoke(); + $resource = $this->stream->__invoke(); + if (!is_resource($resource)) { + throw new \Exception('Failed to resolve resource from closure', 1743962813); + } + $this->stream = $resource; } if (is_resource($this->stream)) { $meta = stream_get_meta_data($this->stream); diff --git a/Neos.Flow/Classes/ResourceManagement/Storage/WritableFileSystemStorage.php b/Neos.Flow/Classes/ResourceManagement/Storage/WritableFileSystemStorage.php index 1974e75a6c..f841a53aa8 100644 --- a/Neos.Flow/Classes/ResourceManagement/Storage/WritableFileSystemStorage.php +++ b/Neos.Flow/Classes/ResourceManagement/Storage/WritableFileSystemStorage.php @@ -57,12 +57,16 @@ public function importResource($source, $collectionName) if (is_resource($source)) { try { $target = fopen($temporaryTargetPathAndFilename, 'wb'); + if ($target === false) { + throw new \Exception('Failed to open ' . $temporaryTargetPathAndFilename, 1743964877); + } stream_copy_to_stream($source, $target); fclose($target); } catch (\Exception $exception) { throw new StorageException(sprintf('Could import the content stream to temporary file "%s".', $temporaryTargetPathAndFilename), 1380880079); } } else { + /** @var string $source */ if (copy($source, $temporaryTargetPathAndFilename) === false) { throw new StorageException(sprintf('Could not copy the file from "%s" to temporary file "%s".', $source, $temporaryTargetPathAndFilename), 1375198876); } @@ -130,6 +134,9 @@ protected function importTemporaryFile($temporaryPathAndFileName, $collectionNam { $this->fixFilePermissions($temporaryPathAndFileName); $sha1Hash = sha1_file($temporaryPathAndFileName); + if ($sha1Hash === false) { + throw new \Exception('Failed to resolve sha1 hash for ' . $temporaryPathAndFileName); + } $targetPathAndFilename = $this->getStoragePathAndFilenameByHash($sha1Hash); if (!is_file($targetPathAndFilename)) { @@ -139,7 +146,10 @@ protected function importTemporaryFile($temporaryPathAndFileName, $collectionNam } $resource = new PersistentResource(); - $resource->setFileSize(filesize($targetPathAndFilename)); + $fileSize = filesize($targetPathAndFilename); + if ($fileSize !== false) { + $resource->setFileSize($fileSize); + } $resource->setCollectionName($collectionName); $resource->setSha1($sha1Hash); diff --git a/Neos.Flow/Classes/ResourceManagement/Streams/ResourceStreamWrapper.php b/Neos.Flow/Classes/ResourceManagement/Streams/ResourceStreamWrapper.php index 752ce47558..6117b75326 100644 --- a/Neos.Flow/Classes/ResourceManagement/Streams/ResourceStreamWrapper.php +++ b/Neos.Flow/Classes/ResourceManagement/Streams/ResourceStreamWrapper.php @@ -88,7 +88,7 @@ public function closeDirectory() * This method is called in response to opendir(). * * @param string $path Specifies the URL that was passed to opendir(). - * @param int $options Whether or not to enforce safe_mode (0x04). + * @param int $options Whether to enforce safe_mode (0x04). * @return boolean true on success or false on failure. */ public function openDirectory($path, $options) @@ -97,7 +97,7 @@ public function openDirectory($path, $options) if (!is_string($resourceUriOrStream)) { return false; } - $handle = ($resourceUriOrStream !== false) ? opendir($resourceUriOrStream) : false; + $handle = opendir($resourceUriOrStream); if ($handle !== false) { $this->handle = $handle; return true; @@ -110,7 +110,7 @@ public function openDirectory($path, $options) * * This method is called in response to readdir(). * - * @return string Should return string representing the next filename, or false if there is no next file. + * @return string|false Should return string representing the next filename, or false if there is no next file. */ public function readDirectory() { @@ -148,7 +148,7 @@ public function makeDirectory($path, $mode, $options) { $resourceUriOrStream = $this->evaluateResourcePath($path, false); if (is_string($resourceUriOrStream)) { - return mkdir($resourceUriOrStream, $mode, $options&STREAM_MKDIR_RECURSIVE); + return mkdir($resourceUriOrStream, $mode, (bool)($options&STREAM_MKDIR_RECURSIVE)); } return false; } @@ -309,10 +309,11 @@ public function open($path, $mode, $options, &$openedPathAndFilename) $this->handle = $resourceUriOrStream; return true; } - + /** @var string|false $resourceUriOrStream */ $handle = ($resourceUriOrStream !== false) ? fopen($resourceUriOrStream, $mode) : false; if ($handle !== false) { $this->handle = $handle; + /** @var string $resourceUriOrStream */ $openedPathAndFilename = $resourceUriOrStream; return true; } @@ -328,10 +329,13 @@ public function open($path, $mode, $options, &$openedPathAndFilename) * number of bytes that were successfully read). * * @param integer $count How many bytes of data from the current position should be returned. - * @return string If there are less than count bytes available, return as many as are available. If no more data is available, return either false or an empty string. + * @return string|false If there are less than count bytes available, return as many as are available. If no more data is available, return either false or an empty string. */ public function read($count) { + if ($count < 1) { + throw new \Exception('Cannot read less than one byte', 1743964166); + } return fread($this->handle, $count); } @@ -392,7 +396,7 @@ public function setOption($option, $argument1, $argument2) * * This method is called in response to ftell(). * - * @return int Should return the current position of the stream. + * @return int|false Should return the current position of the stream. */ public function tell() { @@ -411,7 +415,7 @@ public function tell() * bytes that were successfully written. * * @param string $data Should be stored into the underlying stream. - * @return int Should return the number of bytes that were successfully stored, or 0 if none could be stored. + * @return int|false Should return the number of bytes that were successfully stored, or 0 if none could be stored. */ public function write($data) { @@ -441,7 +445,7 @@ public function unlink($path) * * This method is called in response to fstat(). * - * @return array See http://php.net/stat + * @return array|false See http://php.net/stat */ public function resourceStat() { @@ -472,14 +476,18 @@ public function resourceStat() * * @param string $path The file path or URL to stat. Note that in the case of a URL, it must be a :// delimited URL. Other URL forms are not supported. * @param integer $flags Holds additional flags set by the streams API. - * @return array Should return as many elements as stat() does. Unknown or unavailable values should be set to a rational value (usually 0). + * @return array|false Should return as many elements as stat() does. Unknown or unavailable values should be set to a rational value (usually 0). */ public function pathStat($path, $flags) { $evaluatedResourcePath = $this->evaluateResourcePath($path); + if ($evaluatedResourcePath === false) { + return false; + } if (is_resource($evaluatedResourcePath)) { return @fstat($evaluatedResourcePath); } + /** @var string $evaluatedResourcePath */ return @stat($evaluatedResourcePath); } @@ -489,7 +497,7 @@ public function pathStat($path, $flags) * * @param string $requestedPath * @param boolean $checkForExistence Whether a (non-hash) path should be checked for existence before being returned - * @return mixed The full path and filename or false if the file doesn't exist + * @return string|resource|false The full path and filename or false if the file doesn't exist * @throws \InvalidArgumentException|ResourceException */ protected function evaluateResourcePath($requestedPath, $checkForExistence = true) @@ -507,7 +515,7 @@ protected function evaluateResourcePath($requestedPath, $checkForExistence = tru if (strpos($resourceUriWithoutScheme, '/') === false && preg_match('/^[0-9a-f]{40}$/i', $resourceUriWithoutScheme) === 1) { $resource = $this->resourceManager->getResourceBySha1($resourceUriWithoutScheme); - return $this->resourceManager->getStreamByResource($resource); + return $resource ? $this->resourceManager->getStreamByResource($resource) : false; } list($packageName, $path) = explode('/', $resourceUriWithoutScheme, 2); diff --git a/Neos.Flow/Classes/ResourceManagement/Streams/StreamWrapperAdapter.php b/Neos.Flow/Classes/ResourceManagement/Streams/StreamWrapperAdapter.php index adec60d7fc..a75a876278 100644 --- a/Neos.Flow/Classes/ResourceManagement/Streams/StreamWrapperAdapter.php +++ b/Neos.Flow/Classes/ResourceManagement/Streams/StreamWrapperAdapter.php @@ -28,7 +28,7 @@ class StreamWrapperAdapter { /** - * @var array + * @var array> indexed by scheme */ protected static $registeredStreamWrappers = []; @@ -51,7 +51,6 @@ class StreamWrapperAdapter public static function initializeStreamWrapper($objectManager) { $streamWrapperClassNames = static::getStreamWrapperImplementationClassNames($objectManager); - /** @var StreamWrapperInterface $streamWrapperClassName */ foreach ($streamWrapperClassNames as $streamWrapperClassName) { $scheme = $streamWrapperClassName::getScheme(); if (in_array($scheme, stream_get_wrappers())) { @@ -67,7 +66,7 @@ public static function initializeStreamWrapper($objectManager) * earlier ones without warning. * * @param string $scheme - * @param string $objectName + * @param class-string $objectName * @return void */ public static function registerStreamWrapper($scheme, $objectName) @@ -78,7 +77,7 @@ public static function registerStreamWrapper($scheme, $objectName) /** * Returns the stream wrappers registered with this class. * - * @return array + * @return array> indexed by scheme */ public static function getRegisteredStreamWrappers() { @@ -226,7 +225,7 @@ public function rmdir($path, $options) * This method is called in response to stream_select(). * * @param integer $cast_as Can be STREAM_CAST_FOR_SELECT when stream_select() is calling stream_cast() or STREAM_CAST_AS_STREAM when stream_cast() is called for other uses. - * @return resource Should return the underlying stream resource used by the wrapper, or false. + * @return resource|false Should return the underlying stream resource used by the wrapper, or false. */ public function stream_cast($cast_as) { @@ -401,7 +400,7 @@ public function stream_set_option($option, $arg1, $arg2) * * This method is called in response to fstat(). * - * @return array See http://php.net/stat + * @return array See http://php.net/stat */ public function stream_stat() { @@ -476,7 +475,7 @@ public function unlink($path) * * @param string $path The file path or URL to stat. Note that in the case of a URL, it must be a :// delimited URL. Other URL forms are not supported. * @param integer $flags Holds additional flags set by the streams API. - * @return array Should return as many elements as stat() does. Unknown or unavailable values should be set to a rational value (usually 0). + * @return array|false Should return as many elements as stat() does. Unknown or unavailable values should be set to a rational value (usually 0). */ public function url_stat($path, $flags) { @@ -488,7 +487,7 @@ public function url_stat($path, $flags) * Returns all class names implementing the StreamWrapperInterface. * * @param ObjectManagerInterface $objectManager - * @return array Array of stream wrapper implementations + * @return array> Array of stream wrapper implementations * @Flow\CompileStatic */ public static function getStreamWrapperImplementationClassNames(ObjectManagerInterface $objectManager) diff --git a/Neos.Flow/Classes/ResourceManagement/Streams/StreamWrapperInterface.php b/Neos.Flow/Classes/ResourceManagement/Streams/StreamWrapperInterface.php index 13365e92c6..11e91d18a1 100644 --- a/Neos.Flow/Classes/ResourceManagement/Streams/StreamWrapperInterface.php +++ b/Neos.Flow/Classes/ResourceManagement/Streams/StreamWrapperInterface.php @@ -335,7 +335,7 @@ public function unlink($path); * * This method is called in response to fstat(). * - * @return array See http://php.net/stat + * @return array See http://php.net/stat * @api */ public function resourceStat(); @@ -359,7 +359,7 @@ public function resourceStat(); * * @param string $path The file path or URL to stat. Note that in the case of a URL, it must be a :// delimited URL. Other URL forms are not supported. * @param integer $flags Holds additional flags set by the streams API. - * @return array Should return as many elements as stat() does. Unknown or unavailable values should be set to a rational value (usually 0). + * @return array|false Should return as many elements as stat() does. Unknown or unavailable values should be set to a rational value (usually 0). * @api */ public function pathStat($path, $flags); diff --git a/Neos.Flow/Classes/ResourceManagement/Target/FileSystemSymlinkTarget.php b/Neos.Flow/Classes/ResourceManagement/Target/FileSystemSymlinkTarget.php index f6e808cdbe..34a83aac88 100644 --- a/Neos.Flow/Classes/ResourceManagement/Target/FileSystemSymlinkTarget.php +++ b/Neos.Flow/Classes/ResourceManagement/Target/FileSystemSymlinkTarget.php @@ -62,7 +62,7 @@ protected function publishFile($sourceStream, $relativeTargetPathAndFilename) { $extension = strtolower(pathinfo($relativeTargetPathAndFilename, PATHINFO_EXTENSION)); if ($extension !== '' && array_key_exists($extension, $this->excludedExtensions) && $this->excludedExtensions[$extension] === true) { - throw new TargetException(sprintf('Could not publish "%s" into resource publishing target "%s" because the filename extension "%s" is excluded.', $sourceStream, $this->name, $extension), 1447152230); + throw new TargetException(sprintf('Could not publish resource into publishing target "%s" because the filename extension "%s" is excluded.', $this->name, $extension), 1447152230); } $streamMetaData = stream_get_meta_data($sourceStream); @@ -151,6 +151,9 @@ protected function setOption($key, $value) return parent::setOption($key, $value); } + /** + * @return array{0: bool, 1: ?\Exception} + */ private function publish(string $targetPathAndFilename, string $sourcePathAndFilename): array { $exception = null; diff --git a/Neos.Flow/Classes/ResourceManagement/Target/FileSystemTarget.php b/Neos.Flow/Classes/ResourceManagement/Target/FileSystemTarget.php index f30d54274b..3d6c8bf149 100644 --- a/Neos.Flow/Classes/ResourceManagement/Target/FileSystemTarget.php +++ b/Neos.Flow/Classes/ResourceManagement/Target/FileSystemTarget.php @@ -33,7 +33,7 @@ class FileSystemTarget implements TargetInterface { /** - * @var array + * @var array */ protected $options = []; @@ -83,7 +83,7 @@ class FileSystemTarget implements TargetInterface /** * A list of extensions that are excluded and must not be published by this target. * - * @var array + * @var array */ protected $excludedExtensions = []; @@ -115,7 +115,7 @@ class FileSystemTarget implements TargetInterface * Constructor * * @param string $name Name of this target instance, according to the resource settings - * @param array $options Options for this target + * @param array $options Options for this target */ public function __construct($name, array $options = []) { @@ -217,8 +217,9 @@ public function publishCollection(CollectionInterface $collection) $this->checkAndRemovePackageSymlinks($storage); $iteration = 0; foreach ($collection->getObjects() as $object) { - $sourceStream = $object->getStream(); - if ($sourceStream === false) { + try { + $sourceStream = $object->getStream(); + } catch (\Exception) { $this->handleMissingData($object, $collection); continue; } @@ -255,7 +256,7 @@ public function publishResource(PersistentResource $resource, CollectionInterfac * @param ResourceMetaDataInterface $resource * @param CollectionInterface $collection */ - protected function handleMissingData(ResourceMetaDataInterface $resource, CollectionInterface $collection) + protected function handleMissingData(ResourceMetaDataInterface $resource, CollectionInterface $collection): void { $message = sprintf('Could not publish resource %s with SHA1 hash %s of collection %s because there seems to be no corresponding data in the storage.', $resource->getFilename(), $resource->getSha1(), $collection->getName()); $this->messageCollector->append($message); @@ -323,16 +324,16 @@ protected function encodeRelativePathAndFilenameForUri($relativePathAndFilename) protected function publishFile($sourceStream, $relativeTargetPathAndFilename) { $pathInfo = UnicodeFunctions::pathinfo($relativeTargetPathAndFilename); - if (isset($pathInfo['extension']) && array_key_exists(strtolower($pathInfo['extension']), $this->excludedExtensions) && $this->excludedExtensions[strtolower($pathInfo['extension'])] === true) { - throw new TargetException(sprintf('Could not publish "%s" into resource publishing target "%s" because the filename extension "%s" is excluded.', $sourceStream, $this->name, strtolower($pathInfo['extension'])), 1447148472); + if (array_key_exists(strtolower($pathInfo['extension']), $this->excludedExtensions) && $this->excludedExtensions[strtolower($pathInfo['extension'])] === true) { + throw new TargetException(sprintf('Could not publish resource into publishing target "%s" because the filename extension "%s" is excluded.', $this->name, strtolower($pathInfo['extension'])), 1447148472); } $targetPathAndFilename = $this->path . $relativeTargetPathAndFilename; $streamMetaData = stream_get_meta_data($sourceStream); - $sourcePathAndFilename = $streamMetaData['uri'] ?? null; + $sourcePathAndFilename = $streamMetaData['uri']; if (@fstat($sourceStream) === false) { - throw new TargetException(sprintf('Could not publish "%s" into resource publishing target "%s" because the source file is not accessible (file stat failed).', $sourceStream, $this->name), 1375258499); + throw new TargetException(sprintf('Could not publish resource into publishing target "%s" because the source file is not accessible (file stat failed).', $this->name), 1375258499); } // If you switch from FileSystemSymlinkTarget than we need to remove the symlink before trying to write the file @@ -346,7 +347,7 @@ protected function publishFile($sourceStream, $relativeTargetPathAndFilename) } if (!is_writable(dirname($targetPathAndFilename))) { - throw new Exception(sprintf('Could not publish "%s" into resource publishing target "%s" because the target file "%s" is not writable.', $sourceStream, $this->name, $targetPathAndFilename), 1428917322); + throw new Exception(sprintf('Could not publish resource into publishing target "%s" because the target file "%s" is not writable.', $this->name, $targetPathAndFilename), 1428917322); } try { @@ -361,7 +362,7 @@ protected function publishFile($sourceStream, $relativeTargetPathAndFilename) $result = false; } if ($result === false) { - throw new TargetException(sprintf('Could not publish "%s" into resource publishing target "%s" because the source file could not be copied to the target location "%s".', $sourceStream, $this->name, $targetPathAndFilename), 1375258399, ($exception ?? null)); + throw new TargetException(sprintf('Could not publish resource into publishing target "%s" because the source file could not be copied to the target location "%s".', $this->name, $targetPathAndFilename), 1375258399, ($exception ?? null)); } $this->logger->debug(sprintf('FileSystemTarget: Published file. (target: %s, file: %s)', $this->name, $relativeTargetPathAndFilename)); diff --git a/Neos.Flow/Classes/Security/Account.php b/Neos.Flow/Classes/Security/Account.php index 59afe04d2f..edd33c6b55 100644 --- a/Neos.Flow/Classes/Security/Account.php +++ b/Neos.Flow/Classes/Security/Account.php @@ -55,7 +55,7 @@ class Account protected $creationDate; /** - * @var \DateTime + * @var \DateTime|null * @ORM\Column(nullable=true) */ protected $expirationDate; @@ -73,7 +73,7 @@ class Account protected $failedAuthenticationCount; /** - * @var array of strings + * @var array * @ORM\Column(type="simple_array", nullable=true) */ protected $roleIdentifiers = []; @@ -225,9 +225,6 @@ public function setRoles(array $roles) $this->roleIdentifiers = []; $this->roles = []; foreach ($roles as $role) { - if (!$role instanceof Role) { - throw new \InvalidArgumentException(sprintf('setRoles() only accepts an array of %s instances, given: "%s"', Role::class, gettype($role)), 1397125997); - } $this->addRole($role); } } @@ -310,7 +307,7 @@ public function getExpirationDate() /** * Sets the date on which this account will become inactive * - * @param \DateTime $expirationDate + * @param ?\DateTime $expirationDate * @return void * @api */ diff --git a/Neos.Flow/Classes/Security/AccountFactory.php b/Neos.Flow/Classes/Security/AccountFactory.php index e90acf5f0f..69a62229ff 100644 --- a/Neos.Flow/Classes/Security/AccountFactory.php +++ b/Neos.Flow/Classes/Security/AccountFactory.php @@ -37,7 +37,7 @@ class AccountFactory * * @param string $identifier Identifier of the account, must be unique * @param string $password The clear text password - * @param array $roleIdentifiers Optionally an array of role identifiers to assign to the new account + * @param array $roleIdentifiers Optionally an array of role identifiers to assign to the new account * @param string $authenticationProviderName Optional name of the authentication provider the account is affiliated with * @param string $passwordHashingStrategy Optional password hashing strategy to use for the password * @return Account A new account, not yet added to the account repository diff --git a/Neos.Flow/Classes/Security/AccountRepository.php b/Neos.Flow/Classes/Security/AccountRepository.php index b305695968..293274335e 100644 --- a/Neos.Flow/Classes/Security/AccountRepository.php +++ b/Neos.Flow/Classes/Security/AccountRepository.php @@ -30,7 +30,7 @@ class AccountRepository extends Repository const ENTITY_CLASSNAME = Account::class; /** - * @var array + * @var array */ protected $defaultOrderings = ['creationDate' => QueryInterface::ORDER_DESCENDING]; @@ -51,6 +51,9 @@ class AccountRepository extends Repository */ public function remove($object): void { + if (!($object instanceof Account)) { + throw new \InvalidArgumentException('Can only remove account objects.', 1743961393); + } parent::remove($object); // destroy the sessions for the account to be removed @@ -67,12 +70,14 @@ public function remove($object): void public function findByAccountIdentifierAndAuthenticationProviderName($accountIdentifier, $authenticationProviderName) { $query = $this->createQuery(); - return $query->matching( + $result = $query->matching( $query->logicalAnd( $query->equals('accountIdentifier', $accountIdentifier), $query->equals('authenticationProviderName', $authenticationProviderName) ) )->execute()->getFirst(); + + return $result instanceof Account ? $result : null; } /** @@ -85,7 +90,7 @@ public function findByAccountIdentifierAndAuthenticationProviderName($accountIde public function findActiveByAccountIdentifierAndAuthenticationProviderName($accountIdentifier, $authenticationProviderName) { $query = $this->createQuery(); - return $query->matching( + $result = $query->matching( $query->logicalAnd( $query->equals('accountIdentifier', $accountIdentifier), $query->equals('authenticationProviderName', $authenticationProviderName), @@ -95,5 +100,7 @@ public function findActiveByAccountIdentifierAndAuthenticationProviderName($acco ) ) )->execute()->getFirst(); + + return $result instanceof Account ? $result : null; } } diff --git a/Neos.Flow/Classes/Security/Aspect/LoggingAspect.php b/Neos.Flow/Classes/Security/Aspect/LoggingAspect.php index e85afc9ac1..ae4271ee8c 100644 --- a/Neos.Flow/Classes/Security/Aspect/LoggingAspect.php +++ b/Neos.Flow/Classes/Security/Aspect/LoggingAspect.php @@ -100,9 +100,9 @@ public function logManagerLogout(JoinPointInterface $joinPoint) } /** - * @param array $collectedIdentifiers + * @param array $collectedIdentifiers * @param TokenInterface $token - * @return array + * @return array */ protected function reduceTokenToAccountIdentifier(array $collectedIdentifiers, TokenInterface $token): array { @@ -170,7 +170,7 @@ public function logPrivilegeAccessDecisions(JoinPointInterface $joinPoint) /** * @param JoinPointInterface $joinPoint - * @return array + * @return array */ protected function getLogEnvironmentFromJoinPoint(JoinPointInterface $joinPoint): array { diff --git a/Neos.Flow/Classes/Security/Authentication/AuthenticationProviderInterface.php b/Neos.Flow/Classes/Security/Authentication/AuthenticationProviderInterface.php index 965c39a1d1..8f3dbf3b6a 100644 --- a/Neos.Flow/Classes/Security/Authentication/AuthenticationProviderInterface.php +++ b/Neos.Flow/Classes/Security/Authentication/AuthenticationProviderInterface.php @@ -21,7 +21,7 @@ interface AuthenticationProviderInterface * Constructs an instance with the given name and options. * * @param string $name - * @param array $options + * @param array $options * @return self */ public static function create(string $name, array $options); @@ -37,7 +37,7 @@ public function canAuthenticate(TokenInterface $token); /** * Returns the classnames of the tokens this provider is responsible for. * - * @return array The classname of the token this provider is responsible for + * @return array> The classname of the token this provider is responsible for */ public function getTokenClassNames(); diff --git a/Neos.Flow/Classes/Security/Authentication/AuthenticationProviderManager.php b/Neos.Flow/Classes/Security/Authentication/AuthenticationProviderManager.php index ed40991eab..5746d2c08f 100644 --- a/Neos.Flow/Classes/Security/Authentication/AuthenticationProviderManager.php +++ b/Neos.Flow/Classes/Security/Authentication/AuthenticationProviderManager.php @@ -51,7 +51,7 @@ class AuthenticationProviderManager implements AuthenticationManagerInterface * Injected configuration for providers. * Will be null'd again after building the object instances. * - * @var array|null + * @var array|null */ protected $providerConfigurations; @@ -81,7 +81,7 @@ public function __construct(TokenAndProviderFactoryInterface $tokenAndProviderFa /** * Inject the settings and does a fresh build of tokens based on the injected settings * - * @param array $settings The settings + * @param array $settings The settings * @return void * @throws Exception */ @@ -206,7 +206,10 @@ public function isAuthenticated(): bool } catch (AuthenticationRequiredException $exception) { } } - return $this->isAuthenticated; + + return $this->isAuthenticated !== null + ? $this->isAuthenticated + : false; } /** diff --git a/Neos.Flow/Classes/Security/Authentication/AuthenticationProviderResolver.php b/Neos.Flow/Classes/Security/Authentication/AuthenticationProviderResolver.php index d04def1887..30bf153f7c 100644 --- a/Neos.Flow/Classes/Security/Authentication/AuthenticationProviderResolver.php +++ b/Neos.Flow/Classes/Security/Authentication/AuthenticationProviderResolver.php @@ -47,12 +47,12 @@ public function __construct(ObjectManagerInterface $objectManager) public function resolveProviderClass($providerName) { $className = $this->objectManager->getClassNameByObjectName($providerName); - if ($className !== false) { + if ($className !== false && is_subclass_of($className, AuthenticationProviderInterface::class)) { return $className; } $className = $this->objectManager->getClassNameByObjectName('Neos\Flow\Security\Authentication\Provider\\' . $providerName); - if ($className !== false) { + if ($className !== false && is_subclass_of($className, AuthenticationProviderInterface::class)) { return $className; } diff --git a/Neos.Flow/Classes/Security/Authentication/Controller/AbstractAuthenticationController.php b/Neos.Flow/Classes/Security/Authentication/Controller/AbstractAuthenticationController.php index 0c5fe443a5..7ac7f4aae6 100644 --- a/Neos.Flow/Classes/Security/Authentication/Controller/AbstractAuthenticationController.php +++ b/Neos.Flow/Classes/Security/Authentication/Controller/AbstractAuthenticationController.php @@ -85,7 +85,11 @@ public function authenticateAction() if (!$this->authenticationManager->isAuthenticated()) { $this->onAuthenticationFailure($authenticationException); - return call_user_func([$this, $this->errorMethodName]); + $callable = [$this, $this->errorMethodName]; + if (!is_callable($callable)) { + throw new \Exception('Invalid error method ' . $this->errorMethodName, 1743955562); + } + return call_user_func($callable); } $storedRequest = $this->securityContext->getInterceptedRequest(); diff --git a/Neos.Flow/Classes/Security/Authentication/EntryPoint/AbstractEntryPoint.php b/Neos.Flow/Classes/Security/Authentication/EntryPoint/AbstractEntryPoint.php index f11c1d5e60..c609acbd56 100644 --- a/Neos.Flow/Classes/Security/Authentication/EntryPoint/AbstractEntryPoint.php +++ b/Neos.Flow/Classes/Security/Authentication/EntryPoint/AbstractEntryPoint.php @@ -21,14 +21,14 @@ abstract class AbstractEntryPoint implements EntryPointInterface /** * The configurations options * - * @var array + * @var array */ protected $options = []; /** * Sets the options array * - * @param array $options An array of configuration options + * @param array $options An array of configuration options * @return void */ public function setOptions(array $options) @@ -39,7 +39,7 @@ public function setOptions(array $options) /** * Returns the options array * - * @return array The configuration options of this entry point + * @return array The configuration options of this entry point */ public function getOptions() { diff --git a/Neos.Flow/Classes/Security/Authentication/EntryPoint/WebRedirect.php b/Neos.Flow/Classes/Security/Authentication/EntryPoint/WebRedirect.php index a37757f4b0..b0aacac10c 100644 --- a/Neos.Flow/Classes/Security/Authentication/EntryPoint/WebRedirect.php +++ b/Neos.Flow/Classes/Security/Authentication/EntryPoint/WebRedirect.php @@ -75,7 +75,7 @@ public function startAuthentication(ServerRequestInterface $request, ResponseInt } /** - * @param array $routeValues + * @param array $routeValues * @param ServerRequestInterface $request * @return string * @throws \Neos\Flow\Mvc\Routing\Exception\MissingActionNameException @@ -96,7 +96,7 @@ protected function generateUriFromRouteValues(array $routeValues, ServerRequestI * Returns the entry $key from the array $routeValues removing the original array item. * If $key does not exist, NULL is returned. * - * @param array $routeValues + * @param array $routeValues * @param string $key * @return mixed the specified route value or NULL if it is not set */ diff --git a/Neos.Flow/Classes/Security/Authentication/EntryPointInterface.php b/Neos.Flow/Classes/Security/Authentication/EntryPointInterface.php index 9fba7eb5ac..3db795974b 100644 --- a/Neos.Flow/Classes/Security/Authentication/EntryPointInterface.php +++ b/Neos.Flow/Classes/Security/Authentication/EntryPointInterface.php @@ -22,7 +22,7 @@ interface EntryPointInterface /** * Sets the options array * - * @param array $options An array of configuration options + * @param array $options An array of configuration options * @return void */ public function setOptions(array $options); @@ -30,7 +30,7 @@ public function setOptions(array $options); /** * Returns the options array * - * @return array An array of configuration options + * @return array An array of configuration options */ public function getOptions(); diff --git a/Neos.Flow/Classes/Security/Authentication/Provider/AbstractProvider.php b/Neos.Flow/Classes/Security/Authentication/Provider/AbstractProvider.php index 7ddcac499c..c518e7bd8c 100644 --- a/Neos.Flow/Classes/Security/Authentication/Provider/AbstractProvider.php +++ b/Neos.Flow/Classes/Security/Authentication/Provider/AbstractProvider.php @@ -26,7 +26,7 @@ abstract class AbstractProvider implements AuthenticationProviderInterface protected $name; /** - * @var array + * @var array */ protected $options = []; @@ -34,7 +34,7 @@ abstract class AbstractProvider implements AuthenticationProviderInterface * Factory method * * @param string $name - * @param array $options + * @param array $options * @return AuthenticationProviderInterface * @api */ @@ -47,7 +47,7 @@ public static function create(string $name, array $options) * Protected constructor, see create method * * @param string $name The name of this authentication provider - * @param array $options Additional configuration options + * @param array $options Additional configuration options * @see create */ protected function __construct($name, array $options = []) diff --git a/Neos.Flow/Classes/Security/Authentication/Provider/FileBasedSimpleKeyProvider.php b/Neos.Flow/Classes/Security/Authentication/Provider/FileBasedSimpleKeyProvider.php index 5f92aa376a..14fbfff42b 100644 --- a/Neos.Flow/Classes/Security/Authentication/Provider/FileBasedSimpleKeyProvider.php +++ b/Neos.Flow/Classes/Security/Authentication/Provider/FileBasedSimpleKeyProvider.php @@ -66,7 +66,7 @@ class FileBasedSimpleKeyProvider extends AbstractProvider /** * Returns the class names of the tokens this provider can authenticate. * - * @return array + * @return array> */ public function getTokenClassNames() { @@ -106,7 +106,8 @@ public function authenticate(TokenInterface $authenticationToken) */ protected function validateCredentials(PasswordTokenInterface $authenticationToken): void { - if (!$this->hashService->validatePassword($authenticationToken->getPassword(), $this->fileBasedSimpleKeyService->getKey($this->options['keyName']))) { + $key = $this->fileBasedSimpleKeyService->getKey($this->options['keyName']); + if (!$this->hashService->validatePassword($authenticationToken->getPassword(),$key)) { $authenticationToken->setAuthenticationStatus(TokenInterface::WRONG_CREDENTIALS); return; } diff --git a/Neos.Flow/Classes/Security/Authentication/Provider/PersistedUsernamePasswordProvider.php b/Neos.Flow/Classes/Security/Authentication/Provider/PersistedUsernamePasswordProvider.php index 4c14105b59..7861c532bc 100644 --- a/Neos.Flow/Classes/Security/Authentication/Provider/PersistedUsernamePasswordProvider.php +++ b/Neos.Flow/Classes/Security/Authentication/Provider/PersistedUsernamePasswordProvider.php @@ -62,7 +62,7 @@ class PersistedUsernamePasswordProvider extends AbstractProvider /** * Returns the class names of the tokens this provider can authenticate. * - * @return array + * @return array> */ public function getTokenClassNames() { diff --git a/Neos.Flow/Classes/Security/Authentication/Provider/TestingProvider.php b/Neos.Flow/Classes/Security/Authentication/Provider/TestingProvider.php index cff0310b51..7fc46ea159 100644 --- a/Neos.Flow/Classes/Security/Authentication/Provider/TestingProvider.php +++ b/Neos.Flow/Classes/Security/Authentication/Provider/TestingProvider.php @@ -34,7 +34,7 @@ class TestingProvider extends AbstractProvider /** * Returns the class names of the tokens this provider can authenticate. * - * @return array + * @return array> */ public function getTokenClassNames() { diff --git a/Neos.Flow/Classes/Security/Authentication/Token/AbstractToken.php b/Neos.Flow/Classes/Security/Authentication/Token/AbstractToken.php index 42900ce9f6..8b431a086f 100644 --- a/Neos.Flow/Classes/Security/Authentication/Token/AbstractToken.php +++ b/Neos.Flow/Classes/Security/Authentication/Token/AbstractToken.php @@ -36,18 +36,18 @@ abstract class AbstractToken implements TokenInterface /** * The credentials submitted by the client - * @var array + * @var array * @Flow\Transient */ protected $credentials = []; /** - * @var Account + * @var ?Account */ protected $account; /** - * @var array + * @var array */ protected $requestPatterns = []; @@ -59,14 +59,14 @@ abstract class AbstractToken implements TokenInterface /** * Token options - * @var array + * @var array */ protected $options; /** * Build an instance of this token, potentially passing it options that can be configured via `tokenOptions` * - * @param array|null $options + * @param array|null $options */ public function __construct(?array $options = null) { @@ -138,7 +138,7 @@ public function hasRequestPatterns() /** * Sets request patterns * - * @param array $requestPatterns Array of RequestPatternInterface to be set + * @param array $requestPatterns Array of RequestPatternInterface to be set * @return void * @throws \InvalidArgumentException */ @@ -166,7 +166,7 @@ public function getRequestPatterns() /** * Returns the credentials (username and password) of this token. * - * @return array $credentials The needed credentials to authenticate this token + * @return array $credentials The needed credentials to authenticate this token */ public function getCredentials() { @@ -176,7 +176,7 @@ public function getCredentials() /** * Returns the account if one is authenticated, NULL otherwise. * - * @return Account An account object + * @return ?Account An account object */ public function getAccount() { @@ -186,7 +186,7 @@ public function getAccount() /** * Set the (authenticated) account * - * @param Account $account An account object + * @param ?Account $account An account object * @return void */ public function setAccount(?Account $account = null) diff --git a/Neos.Flow/Classes/Security/Authentication/Token/BearerToken.php b/Neos.Flow/Classes/Security/Authentication/Token/BearerToken.php index e51e3960ef..f09688a55a 100644 --- a/Neos.Flow/Classes/Security/Authentication/Token/BearerToken.php +++ b/Neos.Flow/Classes/Security/Authentication/Token/BearerToken.php @@ -30,7 +30,7 @@ class BearerToken extends AbstractToken implements SessionlessTokenInterface { /** * The password credentials - * @var array + * @var array{bearer: string} * @Flow\Transient */ protected $credentials = ['bearer' => '']; diff --git a/Neos.Flow/Classes/Security/Authentication/Token/PasswordToken.php b/Neos.Flow/Classes/Security/Authentication/Token/PasswordToken.php index 90732a5ec1..2ecfaf9bbb 100644 --- a/Neos.Flow/Classes/Security/Authentication/Token/PasswordToken.php +++ b/Neos.Flow/Classes/Security/Authentication/Token/PasswordToken.php @@ -25,7 +25,7 @@ class PasswordToken extends AbstractToken implements PasswordTokenInterface /** * The password credentials - * @var array + * @var array{password: string,} * @Flow\Transient */ protected $credentials = ['password' => '']; @@ -60,7 +60,7 @@ public function updateCredentials(ActionRequest $actionRequest) */ public function getPassword(): string { - return $this->credentials['password'] ?? ''; + return $this->credentials['password']; } /** diff --git a/Neos.Flow/Classes/Security/Authentication/Token/UsernamePassword.php b/Neos.Flow/Classes/Security/Authentication/Token/UsernamePassword.php index 657ee6627d..5c93accc5e 100644 --- a/Neos.Flow/Classes/Security/Authentication/Token/UsernamePassword.php +++ b/Neos.Flow/Classes/Security/Authentication/Token/UsernamePassword.php @@ -26,7 +26,7 @@ class UsernamePassword extends AbstractToken implements UsernamePasswordTokenInt /** * The username/password credentials - * @var array + * @var array{username: string, password: string,} * @Flow\Transient */ protected $credentials = ['username' => '', 'password' => '']; @@ -78,7 +78,7 @@ public function updateCredentials(ActionRequest $actionRequest) */ public function getUsername(): string { - return $this->credentials['username'] ?? ''; + return $this->credentials['username']; } /** @@ -86,7 +86,7 @@ public function getUsername(): string */ public function getPassword(): string { - return $this->credentials['password'] ?? ''; + return $this->credentials['password']; } /** diff --git a/Neos.Flow/Classes/Security/Authentication/Token/UsernamePasswordHttpBasic.php b/Neos.Flow/Classes/Security/Authentication/Token/UsernamePasswordHttpBasic.php index f8095bae8c..5342185072 100644 --- a/Neos.Flow/Classes/Security/Authentication/Token/UsernamePasswordHttpBasic.php +++ b/Neos.Flow/Classes/Security/Authentication/Token/UsernamePasswordHttpBasic.php @@ -28,7 +28,7 @@ class UsernamePasswordHttpBasic extends UsernamePassword implements SessionlessT */ public function updateCredentials(ActionRequest $actionRequest) { - $this->credentials = ['username' => null, 'password' => null]; + $this->credentials = ['username' => '', 'password' => '']; $this->authenticationStatus = self::NO_CREDENTIALS_GIVEN; $authorizationHeader = $actionRequest->getHttpRequest()->getHeaderLine('Authorization'); diff --git a/Neos.Flow/Classes/Security/Authentication/TokenAndProviderFactory.php b/Neos.Flow/Classes/Security/Authentication/TokenAndProviderFactory.php index 4fe716176e..0314714fe5 100644 --- a/Neos.Flow/Classes/Security/Authentication/TokenAndProviderFactory.php +++ b/Neos.Flow/Classes/Security/Authentication/TokenAndProviderFactory.php @@ -40,7 +40,7 @@ class TokenAndProviderFactory implements TokenAndProviderFactoryInterface protected $tokens = []; /** - * @var array + * @var array> */ protected $providerConfigurations = []; @@ -113,7 +113,7 @@ public function getProviders(): array /** * Inject the settings and does a fresh build of tokens based on the injected settings * - * @param array $settings The settings + * @param array $settings The settings * @return void * @throws Exception */ @@ -151,14 +151,11 @@ protected function buildProvidersAndTokensFromConfiguration() continue; } - if (!is_array($providerConfiguration) || !isset($providerConfiguration['provider'])) { + if (!isset($providerConfiguration['provider'])) { throw new Exception\InvalidAuthenticationProviderException('The configured authentication provider "' . $providerName . '" needs a "provider" option!', 1248209521); } $providerObjectName = $this->providerResolver->resolveProviderClass((string)$providerConfiguration['provider']); - if ($providerObjectName === null) { - throw new Exception\InvalidAuthenticationProviderException('The configured authentication provider "' . $providerConfiguration['provider'] . '" could not be found!', 1237330453); - } $providerOptions = []; if (isset($providerConfiguration['providerOptions']) && is_array($providerConfiguration['providerOptions'])) { $providerOptions = $providerConfiguration['providerOptions']; @@ -175,7 +172,7 @@ protected function buildProvidersAndTokensFromConfiguration() } $tokenInstance = $this->objectManager->get($tokenClassName, $providerConfiguration['tokenOptions'] ?? []); if (!$tokenInstance instanceof TokenInterface) { - throw new Exception\InvalidAuthenticationProviderException(sprintf('The specified token is not an instance of %s but a %s. Please adjust the "token" configuration of the "%s" authentication provider', TokenInterface::class, is_object($tokenInstance) ? get_class($tokenInstance) : gettype($tokenInstance), $providerName), 1585921152); + throw new Exception\InvalidAuthenticationProviderException(sprintf('The specified token is not an instance of %s but a %s. Please adjust the "token" configuration of the "%s" authentication provider', TokenInterface::class, get_class($tokenInstance), $providerName), 1585921152); } $tokenInstance->setAuthenticationProviderName($providerName); $this->tokens[] = $tokenInstance; @@ -225,7 +222,7 @@ protected function buildProvidersAndTokensFromConfiguration() $entryPoint->setOptions($providerConfiguration['entryPointOptions']); } - $tokenInstance->setAuthenticationEntryPoint($entryPoint); + $tokenInstance?->setAuthenticationEntryPoint($entryPoint); } } diff --git a/Neos.Flow/Classes/Security/Authentication/TokenInterface.php b/Neos.Flow/Classes/Security/Authentication/TokenInterface.php index 13b6eb868c..da0f6c96f4 100644 --- a/Neos.Flow/Classes/Security/Authentication/TokenInterface.php +++ b/Neos.Flow/Classes/Security/Authentication/TokenInterface.php @@ -102,7 +102,7 @@ public function hasRequestPatterns(); /** * Sets request patterns * - * @param array $requestPatterns Array of \Neos\Flow\Security\RequestPatternInterface to be set + * @param array $requestPatterns Array of \Neos\Flow\Security\RequestPatternInterface to be set * @return void * @see hasRequestPattern() */ @@ -139,14 +139,14 @@ public function getCredentials(); /** * Returns the account if one is authenticated, NULL otherwise. * - * @return Account An account object + * @return ?Account An account object */ public function getAccount(); /** * Set the (authenticated) account * - * @param Account $account An account object + * @param ?Account $account An account object * @return void */ public function setAccount(?Account $account = null); diff --git a/Neos.Flow/Classes/Security/Authorization/FilterFirewall.php b/Neos.Flow/Classes/Security/Authorization/FilterFirewall.php index 1d2bc80d53..1798c1e927 100644 --- a/Neos.Flow/Classes/Security/Authorization/FilterFirewall.php +++ b/Neos.Flow/Classes/Security/Authorization/FilterFirewall.php @@ -72,7 +72,7 @@ public function __construct( /** * Injects the configuration settings * - * @param array $settings + * @param array $settings * @return void */ public function injectSettings(array $settings) @@ -101,7 +101,7 @@ public function blockIllegalRequests(ActionRequest $request) } /** - * @param array $filterConfiguration + * @param array $filterConfiguration * @return RequestFilter * @throws \Neos\Flow\Security\Exception\NoInterceptorFoundException * @throws \Neos\Flow\Security\Exception\NoRequestPatternFoundException diff --git a/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/ConjunctionGenerator.php b/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/ConjunctionGenerator.php index 6a4d46cdce..56bdbf7aa0 100644 --- a/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/ConjunctionGenerator.php +++ b/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/ConjunctionGenerator.php @@ -11,7 +11,7 @@ * source code. */ -use Doctrine\Persistence\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Query\Filter\SQLFilter as DoctrineSqlFilter; use Neos\Flow\Annotations as Flow; @@ -35,14 +35,13 @@ public function __construct(array $expressions) /** * @param DoctrineSqlFilter $sqlFilter - * @param ClassMetadata $targetEntity Metadata object for the target entity to create the constraint for + * @param ClassMetadata $targetEntity Metadata object for the target entity to create the constraint for * @param string $targetTableAlias The target table alias used in the current query * @return string */ public function getSql(DoctrineSqlFilter $sqlFilter, ClassMetadata $targetEntity, $targetTableAlias) { $sql = ''; - /** @var SqlGeneratorInterface $expression */ foreach ($this->expressions as $expression) { $sql .= ($sql !== '' ? ' AND ' : '') . $expression->getSql($sqlFilter, $targetEntity, $targetTableAlias); } diff --git a/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/DisjunctionGenerator.php b/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/DisjunctionGenerator.php index dafb1dfa59..c40043681e 100644 --- a/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/DisjunctionGenerator.php +++ b/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/DisjunctionGenerator.php @@ -11,7 +11,7 @@ * source code. */ -use Doctrine\Persistence\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Query\Filter\SQLFilter as DoctrineSqlFilter; use Neos\Flow\Annotations as Flow; @@ -35,14 +35,13 @@ public function __construct(array $expressions) /** * @param DoctrineSqlFilter $sqlFilter - * @param ClassMetaData $targetEntity Metadata object for the target entity to create the constraint for + * @param ClassMetaData $targetEntity Metadata object for the target entity to create the constraint for * @param string $targetTableAlias The target table alias used in the current query * @return string */ public function getSql(DoctrineSqlFilter $sqlFilter, ClassMetadata $targetEntity, $targetTableAlias) { $sql = ''; - /** @var SqlGeneratorInterface $expression */ foreach ($this->expressions as $expression) { $sql .= ($sql !== '' ? ' OR ' : '') . $expression->getSql($sqlFilter, $targetEntity, $targetTableAlias); } diff --git a/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/EntityPrivilege.php b/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/EntityPrivilege.php index 732aa8fade..ef39203a50 100644 --- a/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/EntityPrivilege.php +++ b/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/EntityPrivilege.php @@ -60,11 +60,11 @@ public function matchesEntityType($entityType) /** * Note: The result of this method cannot be cached, as the target table alias might change for different query scenarios * - * @param ClassMetadata $targetEntity + * @param \Doctrine\ORM\Mapping\ClassMetadata $targetEntity * @param string $targetTableAlias * @return string|null */ - public function getSqlConstraint(ClassMetadata $targetEntity, $targetTableAlias) + public function getSqlConstraint(\Doctrine\ORM\Mapping\ClassMetadata $targetEntity, $targetTableAlias) { $this->evaluateMatcher(); diff --git a/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/EntityPrivilegeExpressionParser.php b/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/EntityPrivilegeExpressionParser.php index 4e9bf53e01..b97fafc83e 100644 --- a/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/EntityPrivilegeExpressionParser.php +++ b/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/EntityPrivilegeExpressionParser.php @@ -23,10 +23,10 @@ class EntityPrivilegeExpressionParser extends CompilingEelParser { /** - * @param array $result - * @param array $sub + * @param array $result + * @param array $sub */ - public function NotExpression_exp(&$result, $sub) + public function NotExpression_exp(&$result, $sub): void { if (!isset($result['code'])) { $result['code'] = '$context'; @@ -35,29 +35,29 @@ public function NotExpression_exp(&$result, $sub) } /** - * @param array $result - * @param array $sub + * @param array $result + * @param array $sub */ - public function Disjunction_rgt(&$result, $sub) + public function Disjunction_rgt(&$result, $sub): void { $result['code'] = '$context->callAndWrap(\'disjunction\', array(' . $this->unwrapExpression($result['code']) . ', ' . $this->unwrapExpression($sub['code']) . '))'; } /** - * @param array $result - * @param array $sub + * @param array $result + * @param array $sub */ - public function Conjunction_rgt(&$result, $sub) + public function Conjunction_rgt(&$result, $sub): void { $result['code'] = '$context->callAndWrap(\'conjunction\', array(' . $this->unwrapExpression($result['code']) . ', ' . $this->unwrapExpression($sub['code']) . '))'; } /** - * @param array $result - * @param array $sub + * @param array $result + * @param array $sub * @throws ParserException */ - public function Comparison_rgt(&$result, $sub) + public function Comparison_rgt(&$result, $sub): void { $lval = $result['code']; $rval = $sub['code']; diff --git a/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/FalseConditionGenerator.php b/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/FalseConditionGenerator.php index 6fa1e62d70..abd63bf8ff 100644 --- a/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/FalseConditionGenerator.php +++ b/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/FalseConditionGenerator.php @@ -24,7 +24,7 @@ class FalseConditionGenerator implements SqlGeneratorInterface * Returns an SQL query part that is basically a no-op in order to match no entity * * @param DoctrineSqlFilter $sqlFilter - * @param ClassMetadata $targetEntity + * @param ClassMetadata $targetEntity * @param string $targetTableAlias * @return string */ diff --git a/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/NotExpressionGenerator.php b/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/NotExpressionGenerator.php index 5d52747678..5bbcc1921e 100644 --- a/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/NotExpressionGenerator.php +++ b/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/NotExpressionGenerator.php @@ -11,7 +11,7 @@ * source code. */ -use Doctrine\Persistence\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Query\Filter\SQLFilter as DoctrineSqlFilter; use Neos\Flow\Annotations as Flow; @@ -35,7 +35,7 @@ public function __construct(SqlGeneratorInterface $expression) /** * @param DoctrineSqlFilter $sqlFilter - * @param ClassMetaData $targetEntity Metadata object for the target entity to create the constraint for + * @param ClassMetaData $targetEntity Metadata object for the target entity to create the constraint for * @param string $targetTableAlias The target table alias used in the current query * @return string */ diff --git a/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/PropertyConditionGenerator.php b/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/PropertyConditionGenerator.php index 7df14fff9c..7d5de3f354 100644 --- a/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/PropertyConditionGenerator.php +++ b/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/PropertyConditionGenerator.php @@ -11,7 +11,7 @@ * source code. */ -use Doctrine\ORM\Mapping\ClassMetadataInfo; +use Doctrine\ORM\Mapping\ClassMetadata as ORMClassMetadata; use Doctrine\Persistence\Mapping\ClassMetadata; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\QuoteStrategy; @@ -45,7 +45,7 @@ class PropertyConditionGenerator implements SqlGeneratorInterface protected $operator; /** - * @var string|array + * @var string|array */ protected $operandDefinition; @@ -58,7 +58,7 @@ class PropertyConditionGenerator implements SqlGeneratorInterface * Array of registered global objects that can be accessed as operands * * @Flow\InjectConfiguration("aop.globalObjects") - * @var array + * @var array */ protected $globalObjects = []; @@ -95,7 +95,7 @@ class PropertyConditionGenerator implements SqlGeneratorInterface /** * Raw parameter values * - * @var array + * @var array */ protected $parameters = []; @@ -108,7 +108,7 @@ public function __construct($path) } /** - * @param string|array $operandDefinition + * @param string|array $operandDefinition * @return PropertyConditionGenerator the current instance to allow for method chaining */ public function equals($operandDefinition) @@ -120,7 +120,7 @@ public function equals($operandDefinition) } /** - * @param string|array $operandDefinition + * @param string|array $operandDefinition * @return PropertyConditionGenerator the current instance to allow for method chaining */ public function notEquals($operandDefinition) @@ -204,6 +204,9 @@ public function in($operandDefinition) if (is_array($this->operand) === false && ($this->operand instanceof \Traversable) === false) { throw new InvalidPolicyException(sprintf('The "in" operator needs an array as operand! Got: "%s"', $this->operand), 1416313526); } + if (!is_array($this->operandDefinition)) { + throw new \Exception('Cannot assign iterable operands to non-array operand definition', 1743959938); + } foreach ($this->operand as $iterator => $singleOperandValueDefinition) { $this->operandDefinition['inOperandValue' . $iterator] = $singleOperandValueDefinition; } @@ -230,15 +233,16 @@ public function contains($operandDefinition): PropertyConditionGenerator /** * @param DoctrineSqlFilter $sqlFilter - * @param ClassMetadata $targetEntity + * @param ORMClassMetadata $targetEntity * @param string $targetTableAlias * @return string * @throws InvalidQueryRewritingConstraintException * @throws \Exception */ - public function getSql(DoctrineSqlFilter $sqlFilter, ClassMetadata $targetEntity, $targetTableAlias) + public function getSql(DoctrineSqlFilter $sqlFilter, ORMClassMetadata $targetEntity, $targetTableAlias) { - $targetEntityPropertyName = (strpos($this->path, '.') ? substr($this->path, 0, strpos($this->path, '.')) : $this->path); + $pivot = strpos($this->path, '.'); + $targetEntityPropertyName = ($pivot ? substr($this->path, 0, $pivot) : $this->path); $quoteStrategy = $this->entityManager->getConfiguration()->getQuoteStrategy(); if ($targetEntity->hasAssociation($targetEntityPropertyName) === false) { @@ -248,7 +252,11 @@ public function getSql(DoctrineSqlFilter $sqlFilter, ClassMetadata $targetEntity } elseif ($targetEntity->isSingleValuedAssociation($targetEntityPropertyName) === true && $targetEntity->isAssociationInverseSide($targetEntityPropertyName) === false) { return $this->getSqlForManyToOneAndOneToOneRelationsWithPropertyPath($sqlFilter, $quoteStrategy, $targetEntity, $targetTableAlias, $targetEntityPropertyName); } elseif ($targetEntity->isSingleValuedAssociation($targetEntityPropertyName) === true && $targetEntity->isAssociationInverseSide($targetEntityPropertyName) === true) { - throw new InvalidQueryRewritingConstraintException('Single valued properties from the inverse side are not supported in a content security constraint path! Got: "' . $this->path . ' ' . $this->operator . ' ' . $this->operandDefinition . '"', 1416397754); + throw new InvalidQueryRewritingConstraintException( + 'Single valued properties from the inverse side are not supported in a content security constraint path! Got: "' + . $this->path . ' ' . $this->operator . ' ' . json_encode($this->operandDefinition) . '"' + , 1416397754 + ); } elseif ($targetEntity->isCollectionValuedAssociation($targetEntityPropertyName) === true) { return $this->getSqlForPropertyContains($sqlFilter, $quoteStrategy, $targetEntity, $targetTableAlias, $targetEntityPropertyName); } @@ -259,17 +267,21 @@ public function getSql(DoctrineSqlFilter $sqlFilter, ClassMetadata $targetEntity /** * @param DoctrineSqlFilter $sqlFilter * @param QuoteStrategy $quoteStrategy - * @param ClassMetadataInfo $targetEntity + * @param ORMClassMetadata $targetEntity * @param string $targetTableAlias * @param string $targetEntityPropertyName * @return string * @throws InvalidQueryRewritingConstraintException * @throws \Exception */ - protected function getSqlForPropertyContains(DoctrineSqlFilter $sqlFilter, QuoteStrategy $quoteStrategy, ClassMetadata $targetEntity, string $targetTableAlias, string $targetEntityPropertyName): string + protected function getSqlForPropertyContains(DoctrineSqlFilter $sqlFilter, QuoteStrategy $quoteStrategy, ORMClassMetadata $targetEntity, string $targetTableAlias, string $targetEntityPropertyName): string { if ($this->operator !== 'contains') { - throw new InvalidQueryRewritingConstraintException('Multivalued properties are not supported in a content security constraint path unless the "contains" operation is used! Got: "' . $this->path . ' ' . $this->operator . ' ' . $this->operandDefinition . '"', 1416397655); + throw new InvalidQueryRewritingConstraintException( + 'Multivalued properties are not supported in a content security constraint path unless the "contains" operation is used! Got: "' + . $this->path . ' ' . $this->operator . ' ' . json_encode($this->operandDefinition) . '"', + 1416397655 + ); } if (is_array($this->operandDefinition)) { @@ -303,9 +315,6 @@ protected function getSqlForPropertyContains(DoctrineSqlFilter $sqlFilter, Quote $subQuerySql = 'SELECT ' . $reverseColumn . ' FROM ' . $associationMapping['joinTable']['name'] . ' WHERE ' . $joinColumn . ' = ' . $parameterValue; } else { $associationTargetClass = $targetEntity->getAssociationTargetClass($targetEntityPropertyName); - if ($associationTargetClass === null) { - throw new \InvalidArgumentException("Association name expected, '" . $targetEntityPropertyName . "' is not an association.", 1629871077); - } $subselectQuery = new Query($associationTargetClass); $rootAliases = $subselectQuery->getQueryBuilder()->getRootAliases(); $primaryRootAlias = reset($rootAliases); @@ -314,6 +323,9 @@ protected function getSqlForPropertyContains(DoctrineSqlFilter $sqlFilter, Quote $subselectQuery->getQueryBuilder()->select('IDENTITY(' . $primaryRootAlias . '.' . $associationMapping['mappedBy'] . ')'); $subQuerySql = $subselectQuery->getSql(); } + if (is_array($subQuerySql)) { + throw new \Exception('invalid sub query, must not be an array', 1743931303); + } return $targetTableAlias . '.' . $identityColumnName . ' IN (' . $subQuerySql . ')'; } @@ -321,14 +333,14 @@ protected function getSqlForPropertyContains(DoctrineSqlFilter $sqlFilter, Quote /** * @param DoctrineSqlFilter $sqlFilter * @param QuoteStrategy $quoteStrategy - * @param ClassMetadata $targetEntity + * @param ORMClassMetadata $targetEntity * @param string $targetTableAlias * @param string $targetEntityPropertyName * @return string * @throws InvalidQueryRewritingConstraintException * @throws \Exception */ - protected function getSqlForSimpleProperty(DoctrineSqlFilter $sqlFilter, QuoteStrategy $quoteStrategy, ClassMetadata $targetEntity, $targetTableAlias, $targetEntityPropertyName) + protected function getSqlForSimpleProperty(DoctrineSqlFilter $sqlFilter, QuoteStrategy $quoteStrategy, ORMClassMetadata $targetEntity, $targetTableAlias, $targetEntityPropertyName) { $quotedColumnName = $quoteStrategy->getColumnName($targetEntityPropertyName, $targetEntity, $this->entityManager->getConnection()->getDatabasePlatform()); $propertyPointer = $targetTableAlias . '.' . $quotedColumnName; @@ -346,35 +358,37 @@ protected function getSqlForSimpleProperty(DoctrineSqlFilter $sqlFilter, QuoteSt /** * @param DoctrineSqlFilter $sqlFilter * @param QuoteStrategy $quoteStrategy - * @param ClassMetadataInfo $targetEntity + * @param ORMClassMetadata $targetEntity * @param string $targetTableAlias * @param string $targetEntityPropertyName * @return string * @throws InvalidQueryRewritingConstraintException * @throws \Exception */ - protected function getSqlForManyToOneAndOneToOneRelationsWithoutPropertyPath(DoctrineSqlFilter $sqlFilter, QuoteStrategy $quoteStrategy, ClassMetadata $targetEntity, $targetTableAlias, $targetEntityPropertyName) + protected function getSqlForManyToOneAndOneToOneRelationsWithoutPropertyPath(DoctrineSqlFilter $sqlFilter, QuoteStrategy $quoteStrategy, ORMClassMetadata $targetEntity, $targetTableAlias, $targetEntityPropertyName) { $associationMapping = $targetEntity->getAssociationMapping($targetEntityPropertyName); $constraints = []; - foreach ($associationMapping['joinColumns'] as $joinColumn) { + foreach ($associationMapping['joinColumns'] ?? [] as $joinColumn) { $quotedColumnName = $quoteStrategy->getJoinColumnName($joinColumn, $targetEntity, $this->entityManager->getConnection()->getDatabasePlatform()); $propertyPointer = $targetTableAlias . '.' . $quotedColumnName; - $operandAlias = $this->operandDefinition; - if (is_array($this->operandDefinition)) { - $operandAlias = key($this->operandDefinition); - } + $operandAlias = is_array($this->operandDefinition) + ? key($this->operandDefinition) + : $this->operandDefinition; + $currentReferencedOperandName = $operandAlias . $joinColumn['referencedColumnName']; if (is_object($this->operand)) { - $operandMetadataInfo = $this->entityManager->getClassMetadata(TypeHandling::getTypeForValue($this->operand)); + $type = TypeHandling::getTypeForValue($this->operand); + $operandMetadataInfo = $this->entityManager->getClassMetadata($type); $currentReferencedValueOfOperand = $operandMetadataInfo->getFieldValue($this->operand, $operandMetadataInfo->getFieldForColumn($joinColumn['referencedColumnName'])); $this->setParameter($sqlFilter, $currentReferencedOperandName, $currentReferencedValueOfOperand, $associationMapping['type']); } elseif (is_array($this->operandDefinition)) { foreach ($this->operandDefinition as $operandIterator => $singleOperandValue) { if (is_object($singleOperandValue)) { - $operandMetadataInfo = $this->entityManager->getClassMetadata(TypeHandling::getTypeForValue($singleOperandValue)); + $type = TypeHandling::getTypeForValue($singleOperandValue); + $operandMetadataInfo = $this->entityManager->getClassMetadata($type); $currentReferencedValueOfOperand = $operandMetadataInfo->getFieldValue($singleOperandValue, $operandMetadataInfo->getFieldForColumn($joinColumn['referencedColumnName'])); $this->setParameter($sqlFilter, $operandIterator, $currentReferencedValueOfOperand, $associationMapping['type']); } elseif ($singleOperandValue === null) { @@ -391,7 +405,7 @@ protected function getSqlForManyToOneAndOneToOneRelationsWithoutPropertyPath(Doc /** * @param DoctrineSqlFilter $sqlFilter * @param QuoteStrategy $quoteStrategy - * @param ClassMetadataInfo $targetEntity + * @param ORMClassMetadata $targetEntity * @param string $targetTableAlias * @param string $targetEntityPropertyName * @return string @@ -405,19 +419,22 @@ protected function getSqlForManyToOneAndOneToOneRelationsWithPropertyPath(Doctri $associationMapping = $targetEntity->getAssociationMapping($targetEntityPropertyName); $subselectConstraintQueries = []; - foreach ($associationMapping['joinColumns'] as $joinColumn) { + foreach ($associationMapping['joinColumns'] ?? [] as $joinColumn) { $rootAliases = $subselectQuery->getQueryBuilder()->getRootAliases(); $subselectQuery->getQueryBuilder()->select($rootAliases[0] . '.' . $targetEntity->getFieldForColumn($joinColumn['referencedColumnName'])); $subselectSql = $subselectQuery->getSql(); + if (is_array($subselectSql)) { + $subselectSql = implode(' ', $subselectSql); + } foreach ($subselectQuery->getParameters() as $parameter) { $parameterValue = $parameter->getValue(); if (is_object($parameterValue)) { $parameterValue = $this->persistenceManager->getIdentifierByObject($parameter->getValue()); } - $subselectSql = preg_replace('/\?/', $this->entityManager->getConnection()->quote($parameterValue, $parameter->getType()), $subselectSql, 1); + $subselectSql = preg_replace('/\?/', $this->entityManager->getConnection()->quote($parameterValue, $parameter->getType()), $subselectSql ?: '', 1); } $quotedColumnName = $quoteStrategy->getJoinColumnName($joinColumn, $targetEntity, $this->entityManager->getConnection()->getDatabasePlatform()); - $subselectIdentifier = 'subselect' . md5($subselectSql); + $subselectIdentifier = 'subselect' . md5($subselectSql ?: ''); $subselectConstraintQueries[] = $targetTableAlias . '.' . $quotedColumnName . ' IN (SELECT ' . $subselectIdentifier . '.' . $joinColumn['referencedColumnName'] . '_0 FROM (' . $subselectSql . ') AS ' . $subselectIdentifier . ' ) '; } @@ -425,7 +442,7 @@ protected function getSqlForManyToOneAndOneToOneRelationsWithPropertyPath(Doctri } /** - * @param ClassMetadata $targetEntity + * @param ClassMetadata $targetEntity * @param string $targetEntityPropertyName * @return Query */ @@ -494,6 +511,9 @@ protected function getConstraintStringForSimpleProperty(DoctrineSqlFilter $sqlFi } $parameter = implode(',', $parameters); } elseif (!($this->getRawParameterValue($operandDefinition) === null || ($this->operator === 'in' && $this->getRawParameterValue($operandDefinition) === []))) { + if (!is_string($operandDefinition)) { + throw new \Exception('SQL filter parameters must be of type string, ' . get_debug_type($operandDefinition) . ' given.', 1743929393); + } $parameter = $sqlFilter->getParameter($operandDefinition); } } catch (\InvalidArgumentException $exception) { @@ -567,11 +587,12 @@ public function getValueForOperand($expression) * @param DoctrineSqlFilter $sqlFilter * @param mixed $name * @param mixed $value - * @param string $type + * @param string|int|null $type * @return void */ protected function setParameter(DoctrineSqlFilter $sqlFilter, $name, $value, $type = null) { + /** @phpstan-ignore argument.type (currently, doctrine also can handle integers) */ $sqlFilter->setParameter($name, $value, $type); $this->parameters[$name] = $value; } diff --git a/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/SqlFilter.php b/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/SqlFilter.php index e43d3f70d2..e83fb7d40e 100644 --- a/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/SqlFilter.php +++ b/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/SqlFilter.php @@ -40,7 +40,7 @@ class SqlFilter extends DoctrineSqlFilter /** * Gets the SQL query part to add to a query. * - * @param ClassMetadata $targetEntity Metadata object for the target entity to be filtered + * @param ClassMetadata $targetEntity Metadata object for the target entity to be filtered * @param string $targetTableAlias The target table alias used in the current query * @return string The constraint SQL if there is available, empty string otherwise */ diff --git a/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/SqlGeneratorInterface.php b/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/SqlGeneratorInterface.php index 6f096aec14..c061488576 100644 --- a/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/SqlGeneratorInterface.php +++ b/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/SqlGeneratorInterface.php @@ -11,7 +11,7 @@ * source code. */ -use Doctrine\Persistence\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Query\Filter\SQLFilter as DoctrineSqlFilter; use Neos\Flow\Annotations as Flow; @@ -22,7 +22,7 @@ interface SqlGeneratorInterface { /** * @param DoctrineSqlFilter $sqlFilter - * @param ClassMetadata $targetEntity Metadata object for the target entity to create the constraint for + * @param ClassMetadata $targetEntity Metadata object for the target entity to create the constraint for * @param string $targetTableAlias The target table alias used in the current query * @return string */ diff --git a/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/TrueConditionGenerator.php b/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/TrueConditionGenerator.php index 71f5b97f95..68fd7e4e8a 100644 --- a/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/TrueConditionGenerator.php +++ b/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/Doctrine/TrueConditionGenerator.php @@ -24,7 +24,7 @@ class TrueConditionGenerator implements SqlGeneratorInterface * Returns an SQL query part that is basically a no-op in order to match any entity * * @param DoctrineSqlFilter $sqlFilter - * @param ClassMetadata $targetEntity + * @param ClassMetadata $targetEntity * @param string $targetTableAlias * @return string */ diff --git a/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/EntityPrivilegeInterface.php b/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/EntityPrivilegeInterface.php index e9dab06ce2..096207a62b 100644 --- a/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/EntityPrivilegeInterface.php +++ b/Neos.Flow/Classes/Security/Authorization/Privilege/Entity/EntityPrivilegeInterface.php @@ -11,7 +11,7 @@ * source code. */ -use Doctrine\Persistence\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\ClassMetadata; use Neos\Flow\Annotations as Flow; use Neos\Flow\Security\Authorization\Privilege\PrivilegeInterface; @@ -32,9 +32,9 @@ interface EntityPrivilegeInterface extends PrivilegeInterface public function matchesEntityType($entityType); /** - * @param ClassMetadata $targetEntity + * @param ClassMetadata $targetEntity * @param string $targetTableAlias - * @return string + * @return string|null */ public function getSqlConstraint(ClassMetadata $targetEntity, $targetTableAlias); } diff --git a/Neos.Flow/Classes/Security/Authorization/Privilege/Method/MethodPrivilege.php b/Neos.Flow/Classes/Security/Authorization/Privilege/Method/MethodPrivilege.php index 8bed00aa93..aa8d2fb262 100644 --- a/Neos.Flow/Classes/Security/Authorization/Privilege/Method/MethodPrivilege.php +++ b/Neos.Flow/Classes/Security/Authorization/Privilege/Method/MethodPrivilege.php @@ -27,7 +27,7 @@ class MethodPrivilege extends AbstractPrivilege implements MethodPrivilegeInterface { /** - * @var array + * @var array>> */ protected static $methodPermissions; diff --git a/Neos.Flow/Classes/Security/Authorization/Privilege/Method/MethodPrivilegePointcutFilter.php b/Neos.Flow/Classes/Security/Authorization/Privilege/Method/MethodPrivilegePointcutFilter.php index b0887246b0..2fd52aa85c 100644 --- a/Neos.Flow/Classes/Security/Authorization/Privilege/Method/MethodPrivilegePointcutFilter.php +++ b/Neos.Flow/Classes/Security/Authorization/Privilege/Method/MethodPrivilegePointcutFilter.php @@ -34,7 +34,7 @@ class MethodPrivilegePointcutFilter implements PointcutFilterInterface protected $filters = null; /** - * @var array + * @var array */ protected $methodPermissions = []; @@ -131,7 +131,7 @@ public function hasRuntimeEvaluationsDefinition(): bool /** * Returns runtime evaluations for the pointcut. * - * @return array Runtime evaluations + * @return array Runtime evaluations */ public function getRuntimeEvaluationsDefinition(): array { diff --git a/Neos.Flow/Classes/Security/Authorization/Privilege/Method/MethodTargetExpressionParser.php b/Neos.Flow/Classes/Security/Authorization/Privilege/Method/MethodTargetExpressionParser.php index dec1266201..b079bc44fc 100644 --- a/Neos.Flow/Classes/Security/Authorization/Privilege/Method/MethodTargetExpressionParser.php +++ b/Neos.Flow/Classes/Security/Authorization/Privilege/Method/MethodTargetExpressionParser.php @@ -30,7 +30,7 @@ class MethodTargetExpressionParser extends PointcutExpressionParser * @param string $operator The operator * @param string $pointcutExpression The pointcut expression (value of the designator) * @param PointcutFilterComposite $pointcutFilterComposite An instance of the pointcut filter composite. The result (ie. the pointcut filter) will be added to this composite object. - * @param array &$trace + * @param array &$trace * @return void * @throws InvalidPointcutExpressionException */ diff --git a/Neos.Flow/Classes/Security/Authorization/Privilege/Parameter/PrivilegeParameterInterface.php b/Neos.Flow/Classes/Security/Authorization/Privilege/Parameter/PrivilegeParameterInterface.php index 9403192c43..9cc8dfba11 100644 --- a/Neos.Flow/Classes/Security/Authorization/Privilege/Parameter/PrivilegeParameterInterface.php +++ b/Neos.Flow/Classes/Security/Authorization/Privilege/Parameter/PrivilegeParameterInterface.php @@ -41,7 +41,7 @@ public function getName(); public function getValue(); /** - * @return array|null + * @return array|null */ public function getPossibleValues(); diff --git a/Neos.Flow/Classes/Security/Authorization/Privilege/Parameter/StringPrivilegeParameter.php b/Neos.Flow/Classes/Security/Authorization/Privilege/Parameter/StringPrivilegeParameter.php index a099e429b2..1cc91f91d4 100644 --- a/Neos.Flow/Classes/Security/Authorization/Privilege/Parameter/StringPrivilegeParameter.php +++ b/Neos.Flow/Classes/Security/Authorization/Privilege/Parameter/StringPrivilegeParameter.php @@ -19,7 +19,7 @@ class StringPrivilegeParameter extends AbstractPrivilegeParameter { /** - * @return array|null + * @return array|null */ public function getPossibleValues() { diff --git a/Neos.Flow/Classes/Security/Authorization/Privilege/PrivilegeTarget.php b/Neos.Flow/Classes/Security/Authorization/Privilege/PrivilegeTarget.php index d8b925bce1..42da213bc7 100644 --- a/Neos.Flow/Classes/Security/Authorization/Privilege/PrivilegeTarget.php +++ b/Neos.Flow/Classes/Security/Authorization/Privilege/PrivilegeTarget.php @@ -121,7 +121,7 @@ public function hasParameters(): bool /** * @param string $permission one of "GRANT", "DENY" or "ABSTAIN" - * @param array $parameters Optional key/value array with parameter names and -values + * @param array $parameters Optional key/value array with parameter names and -values * @return PrivilegeInterface * @throws SecurityException */ @@ -151,7 +151,7 @@ public function getLabel(): string } /** - * @param array $parameters + * @param array $parameters * @return \Closure */ protected function createParameterMapper(array $parameters): \Closure @@ -163,7 +163,7 @@ protected function createParameterMapper(array $parameters): \Closure /** * @param PrivilegeParameterDefinition $parameterDefinition - * @param array $parameters + * @param array $parameters * @return PrivilegeParameterInterface * @throws SecurityException */ diff --git a/Neos.Flow/Classes/Security/Authorization/PrivilegeManager.php b/Neos.Flow/Classes/Security/Authorization/PrivilegeManager.php index 6efd9bb355..1eb0a8a031 100644 --- a/Neos.Flow/Classes/Security/Authorization/PrivilegeManager.php +++ b/Neos.Flow/Classes/Security/Authorization/PrivilegeManager.php @@ -13,6 +13,7 @@ use Neos\Flow\Annotations as Flow; use Neos\Flow\ObjectManagement\ObjectManagerInterface; +use Neos\Flow\Security\Authorization\Privilege\Parameter\PrivilegeParameterInterface; use Neos\Flow\Security\Authorization\Privilege\PrivilegeInterface; use Neos\Flow\Security\Context; use Neos\Flow\Security\Policy\Role; @@ -111,7 +112,7 @@ public function isGrantedForRoles(array $roles, $privilegeType, $subject, &$reas * Returns true if access is granted on the given privilege target in the current security context * * @param string $privilegeTargetIdentifier The identifier of the privilege target to decide on - * @param array $privilegeParameters Optional array of privilege parameters (simple key => value array) + * @param array $privilegeParameters Optional array of privilege parameters (simple key => value array) * @return boolean true if access is granted, false otherwise */ public function isPrivilegeTargetGranted($privilegeTargetIdentifier, array $privilegeParameters = []) @@ -124,7 +125,7 @@ public function isPrivilegeTargetGranted($privilegeTargetIdentifier, array $priv * * @param array $roles The roles that should be evaluated * @param string $privilegeTargetIdentifier The identifier of the privilege target to decide on - * @param array $privilegeParameters Optional array of privilege parameters (simple key => value array) + * @param array $privilegeParameters Optional array of privilege parameters (simple key => value array) * @return boolean true if access is granted, false otherwise */ public function isPrivilegeTargetGrantedForRoles(array $roles, $privilegeTargetIdentifier, array $privilegeParameters = []) @@ -134,7 +135,6 @@ public function isPrivilegeTargetGrantedForRoles(array $roles, $privilegeTargetI }; $privileges = array_map($privilegeMapper, $roles); - /** @var PrivilegePermissionResult $result */ $result = array_reduce($privileges, [$this, 'applyPrivilegeToResult'], new PrivilegePermissionResult()); if ($result->getDenies() === 0 && $result->getGrants() > 0) { diff --git a/Neos.Flow/Classes/Security/Authorization/PrivilegeManagerInterface.php b/Neos.Flow/Classes/Security/Authorization/PrivilegeManagerInterface.php index da5b738c16..2f2ef9dde4 100644 --- a/Neos.Flow/Classes/Security/Authorization/PrivilegeManagerInterface.php +++ b/Neos.Flow/Classes/Security/Authorization/PrivilegeManagerInterface.php @@ -44,7 +44,7 @@ public function isGrantedForRoles(array $roles, $privilegeType, $subject, &$reas * Returns true if access is granted on the given privilege target in the current security context * * @param string $privilegeTargetIdentifier The identifier of the privilege target to decide on - * @param array $privilegeParameters Optional array of privilege parameters (simple key => value array) + * @param array $privilegeParameters Optional array of privilege parameters (simple key => value array) * @return boolean true if access is granted, false otherwise */ public function isPrivilegeTargetGranted($privilegeTargetIdentifier, array $privilegeParameters = []); @@ -54,7 +54,7 @@ public function isPrivilegeTargetGranted($privilegeTargetIdentifier, array $priv * * @param array $roles The roles that should be evaluated * @param string $privilegeTargetIdentifier The identifier of the privilege target to decide on - * @param array $privilegeParameters Optional array of privilege parameters (simple key => value array) + * @param array $privilegeParameters Optional array of privilege parameters (simple key => value array) * @return boolean true if access is granted, false otherwise */ public function isPrivilegeTargetGrantedForRoles(array $roles, $privilegeTargetIdentifier, array $privilegeParameters = []); diff --git a/Neos.Flow/Classes/Security/Authorization/PrivilegePermissionResult.php b/Neos.Flow/Classes/Security/Authorization/PrivilegePermissionResult.php index 92eabf6397..5cb35aea57 100644 --- a/Neos.Flow/Classes/Security/Authorization/PrivilegePermissionResult.php +++ b/Neos.Flow/Classes/Security/Authorization/PrivilegePermissionResult.php @@ -25,7 +25,7 @@ class PrivilegePermissionResult protected $abstains = 0; /** - * @var array + * @var array */ protected $effectivePrivilegeIdentifiersWithPermission = []; @@ -101,7 +101,7 @@ public function getAbstains(): int } /** - * @return array + * @return array */ public function getEffectivePrivilegeIdentifiersWithPermission(): array { diff --git a/Neos.Flow/Classes/Security/Authorization/TestingPrivilegeManager.php b/Neos.Flow/Classes/Security/Authorization/TestingPrivilegeManager.php index ef758473b7..696b1c6bb3 100644 --- a/Neos.Flow/Classes/Security/Authorization/TestingPrivilegeManager.php +++ b/Neos.Flow/Classes/Security/Authorization/TestingPrivilegeManager.php @@ -51,7 +51,7 @@ public function isGranted($privilegeType, $subject, &$reason = '') * or if set based on the override decision value. * * @param string $privilegeTargetIdentifier The identifier of the privilege target to decide on - * @param array $privilegeParameters Optional array of privilege parameters (simple key => value array) + * @param array $privilegeParameters Optional array of privilege parameters (simple key => value array) * @return boolean true if access is granted, false otherwise */ public function isPrivilegeTargetGranted($privilegeTargetIdentifier, array $privilegeParameters = []) diff --git a/Neos.Flow/Classes/Security/Context.php b/Neos.Flow/Classes/Security/Context.php index 8726a4265e..f136872125 100644 --- a/Neos.Flow/Classes/Security/Context.php +++ b/Neos.Flow/Classes/Security/Context.php @@ -95,7 +95,7 @@ class Context protected $csrfProtectionStrategy = self::CSRF_ONE_PER_SESSION; /** - * @var array + * @var array */ protected $tokenStatusLabels = [ 1 => 'no credentials given', @@ -149,7 +149,7 @@ class Context /** * CSRF tokens that are valid during this request but will be gone after. - * @var array + * @var array */ protected $csrfTokensRemovedAfterCurrentRequest = []; @@ -193,7 +193,7 @@ class Context * Array of registered global objects that can be accessed as operands * * @Flow\InjectConfiguration("aop.globalObjects") - * @var array + * @var array */ protected $globalObjects = []; @@ -253,7 +253,7 @@ public function setRequest(ActionRequest $request) /** * Injects the configuration settings * - * @param array $settings + * @param array $settings * @return void * @throws Exception */ @@ -624,7 +624,7 @@ public function clearContext() /** * @param Account $account - * @return array + * @return array */ protected function collectRolesAndParentRolesFromAccount(Account $account): array { @@ -640,7 +640,7 @@ protected function collectRolesAndParentRolesFromAccount(Account $account): arra /** * @param Role $role - * @return array + * @return array */ protected function collectParentRoles(Role $role): array { @@ -692,6 +692,9 @@ protected function isTokenActive(TokenInterface $token) if (isset($requestPatternsByType[$patternType]) && $requestPatternsByType[$patternType] === true) { continue; } + if (!$this->request) { + throw new \Exception('Cannot evaluate request pattern without a request', 1743927165); + } $requestPatternsByType[$patternType] = $requestPattern->matchRequest($this->request); } return !in_array(false, $requestPatternsByType, true); @@ -704,7 +707,7 @@ protected function isTokenActive(TokenInterface $token) * * @param array $managerTokens Array of tokens provided by the authentication manager * @param array $sessionTokens Array of tokens restored from the session - * @return array Array of Authentication\TokenInterface objects + * @return array Array of Authentication\TokenInterface objects */ protected function mergeTokens(array $managerTokens, array $sessionTokens) { @@ -848,6 +851,7 @@ public function getContextHash() $this->withoutAuthorizationChecks(function () use (&$contextHashSoFar) { foreach ($this->globalObjects as $globalObjectsRegisteredClassName) { if (is_subclass_of($globalObjectsRegisteredClassName, CacheAwareInterface::class)) { + /** @var class-string $globalObjectsRegisteredClassName */ $globalObject = $this->objectManager->get($globalObjectsRegisteredClassName); $contextHashSoFar .= '<' . $globalObject->getCacheEntryIdentifier(); } diff --git a/Neos.Flow/Classes/Security/Cryptography/Algorithms.php b/Neos.Flow/Classes/Security/Cryptography/Algorithms.php index f0d1f0c969..20e2e90898 100644 --- a/Neos.Flow/Classes/Security/Cryptography/Algorithms.php +++ b/Neos.Flow/Classes/Security/Cryptography/Algorithms.php @@ -32,6 +32,15 @@ class Algorithms */ public static function pbkdf2($password, $salt, $iterationCount, $derivedKeyLength, $algorithm = 'sha256') { + if (!$algorithm) { + throw new \Exception('Algorithm must not be falsy', 1743877215); + } + if ($iterationCount < 1) { + throw new \Exception('Iteration count too low, must be at least 1', 1743877176); + } + if ($derivedKeyLength < 0) { + throw new \Exception('Derived key length too low, must be at least 0', 1743877275); + } return hash_pbkdf2($algorithm, $password, $salt, $iterationCount, $derivedKeyLength, true); } } diff --git a/Neos.Flow/Classes/Security/Cryptography/FileBasedSimpleKeyService.php b/Neos.Flow/Classes/Security/Cryptography/FileBasedSimpleKeyService.php index a2897353bd..2c9f5876a0 100644 --- a/Neos.Flow/Classes/Security/Cryptography/FileBasedSimpleKeyService.php +++ b/Neos.Flow/Classes/Security/Cryptography/FileBasedSimpleKeyService.php @@ -45,7 +45,7 @@ class FileBasedSimpleKeyService protected $hashService; /** - * @param array $settings + * @param array $settings * @return void */ public function injectSettings(array $settings) @@ -116,7 +116,7 @@ public function keyExists($name) * Returns a key by its name * * @param string $name - * @return boolean + * @return string * @throws SecurityException */ public function getKey($name) diff --git a/Neos.Flow/Classes/Security/Cryptography/HashService.php b/Neos.Flow/Classes/Security/Cryptography/HashService.php index 24df1e3af7..59f567d405 100644 --- a/Neos.Flow/Classes/Security/Cryptography/HashService.php +++ b/Neos.Flow/Classes/Security/Cryptography/HashService.php @@ -28,17 +28,17 @@ class HashService { /** * A private, unique key used for encryption tasks - * @var string + * @var string|false|null */ protected $encryptionKey = null; /** - * @var array + * @var array */ protected $passwordHashingStrategies = []; /** - * @var array + * @var array */ protected $strategySettings; @@ -57,7 +57,7 @@ class HashService /** * Injects the settings of the package this controller belongs to. * - * @param array $settings Settings container of the current package + * @param array $settings Settings container of the current package * @return void */ public function injectSettings(array $settings) @@ -72,12 +72,8 @@ public function injectSettings(array $settings) * @return string The hash of the string * @throws InvalidArgumentForHashGenerationException if something else than a string was given as parameter */ - public function generateHmac($string) + public function generateHmac(string $string): string { - if (!is_string($string)) { - throw new InvalidArgumentForHashGenerationException('A hash can only be generated for a string, but "' . gettype($string) . '" was given.', 1255069587); - } - return hash_hmac('sha1', $string, $this->getEncryptionKey()); } @@ -121,11 +117,8 @@ public function validateHmac($string, $hmac) * @throws InvalidHashException if the hash did not fit to the data. * @todo Mark as API once it is more stable */ - public function validateAndStripHmac($string) + public function validateAndStripHmac(string $string): string { - if (!is_string($string)) { - throw new InvalidArgumentForHashGenerationException('A hash can only be validated for a string, but "' . gettype($string) . '" was given.', 1320829762); - } if (strlen($string) < 40) { throw new InvalidArgumentForHashGenerationException('A hashed string must contain at least 40 characters, the given string was only ' . strlen($string) . ' characters long.', 1320830276); } @@ -188,12 +181,19 @@ protected function getPasswordHashingStrategyAndIdentifier($strategyIdentifier = } $strategyIdentifier = $this->strategySettings['default']; } + if (!is_string($strategyIdentifier)) { + throw new \Exception('Invalid strategy identifier', 1743877483); + } if (!isset($this->strategySettings[$strategyIdentifier])) { throw new MissingConfigurationException('No hashing strategy with identifier "' . $strategyIdentifier . '" configured', 1320758776); } $strategyObjectName = $this->strategySettings[$strategyIdentifier]; - $this->passwordHashingStrategies[$strategyIdentifier] = $this->objectManager->get($strategyObjectName); + $strategy = $this->objectManager->get($strategyObjectName); + if (!$strategy instanceof PasswordHashingStrategyInterface) { + throw new \Exception('Invalid password hashing strategy', 1743877416); + } + $this->passwordHashingStrategies[$strategyIdentifier] = $strategy; return [$this->passwordHashingStrategies[$strategyIdentifier], $strategyIdentifier]; } diff --git a/Neos.Flow/Classes/Security/Cryptography/Pbkdf2HashingStrategy.php b/Neos.Flow/Classes/Security/Cryptography/Pbkdf2HashingStrategy.php index e75a985c66..940f61c21c 100644 --- a/Neos.Flow/Classes/Security/Cryptography/Pbkdf2HashingStrategy.php +++ b/Neos.Flow/Classes/Security/Cryptography/Pbkdf2HashingStrategy.php @@ -68,6 +68,15 @@ public function __construct(int $dynamicSaltLength, int $iterationCount, int $de public function hashPassword($password, $staticSalt = null) { $dynamicSalt = UtilityAlgorithms::generateRandomBytes($this->dynamicSaltLength); + if (!$this->algorithm) { + throw new \Exception('Algorithm must not be falsy', 1743877215); + } + if ($this->iterationCount < 1) { + throw new \Exception('Iteration count too low, must be at least 1', 1743877176); + } + if ($this->derivedKeyLength < 0) { + throw new \Exception('Derived key length too low, must be at least 0', 1743877275); + } $result = hash_pbkdf2($this->algorithm, $password, $dynamicSalt . $staticSalt, $this->iterationCount, $this->derivedKeyLength, true); return base64_encode($dynamicSalt) . ',' . base64_encode($result); } @@ -88,6 +97,12 @@ public function validatePassword($password, $hashedPasswordAndSalt, $staticSalt if (count($parts) !== 2) { throw new \InvalidArgumentException('The derived key with salt must contain a salt, separated with a comma from the derived key', 1306172911); } + if (!$this->algorithm) { + throw new \Exception('Algorithm must not be falsy', 1743877215); + } + if ($this->iterationCount < 1) { + throw new \Exception('Iteration count too low, must be at least 1', 1743877176); + } $dynamicSalt = base64_decode($parts[0]); $derivedKey = base64_decode($parts[1]); $derivedKeyLength = strlen($derivedKey); diff --git a/Neos.Flow/Classes/Security/Cryptography/RsaWalletServiceInterface.php b/Neos.Flow/Classes/Security/Cryptography/RsaWalletServiceInterface.php index 8803d39061..c839393a1e 100644 --- a/Neos.Flow/Classes/Security/Cryptography/RsaWalletServiceInterface.php +++ b/Neos.Flow/Classes/Security/Cryptography/RsaWalletServiceInterface.php @@ -54,7 +54,7 @@ public function registerPublicKeyFromString($publicKeyString); * @return OpenSslRsaKey The public key * @throws InvalidKeyPairIdException If the given fingerprint identifies no valid key pair */ - public function getPublicKey($fingerprint); + public function getPublicKey(string $fingerprint): OpenSslRsaKey; /** * Decrypts the given cypher with the private key identified by the given fingerprint @@ -67,7 +67,7 @@ public function getPublicKey($fingerprint); * @throws InvalidKeyPairIdException If the given fingerprint identifies no valid keypair * @throws DecryptionNotAllowedException If the given fingerprint identifies a keypair for encrypted passwords */ - public function decrypt($cypher, $fingerprint); + public function decrypt(string $cypher, string $fingerprint): string; /** * Signs the given plaintext with the private key identified by the given fingerprint @@ -77,7 +77,7 @@ public function decrypt($cypher, $fingerprint); * @return string The signature of the given plaintext * @throws InvalidKeyPairIdException If the given fingerprint identifies no valid keypair */ - public function sign($plaintext, $fingerprint); + public function sign(string $plaintext, string $fingerprint): string; /** * Checks whether the given signature is valid for the given plaintext @@ -88,7 +88,7 @@ public function sign($plaintext, $fingerprint); * @param string $fingerprint The fingerprint to identify to correct public key * @return boolean true if the signature is correct for the given plaintext and public key */ - public function verifySignature($plaintext, $signature, $fingerprint); + public function verifySignature(string $plaintext, string $signature, string $fingerprint): bool; /** * Encrypts the given plaintext with the public key identified by the given fingerprint @@ -109,14 +109,12 @@ public function encryptWithPublicKey($plaintext, $fingerprint); * @param string $fingerprint The fingerprint to identify to correct private key * @return boolean true if the password is correct */ - public function checkRSAEncryptedPassword($encryptedPassword, $passwordHash, $salt, $fingerprint); + public function checkRSAEncryptedPassword(string $encryptedPassword, string $passwordHash, string $salt, string $fingerprint): bool; /** * Destroys the keypair identified by the given fingerprint * - * @param string $fingerprint The fingerprint - * @return void * @throws InvalidKeyPairIdException If the given fingerprint identifies no valid key pair */ - public function destroyKeypair($fingerprint); + public function destroyKeypair(string $fingerprint): void; } diff --git a/Neos.Flow/Classes/Security/Cryptography/RsaWalletServicePhp.php b/Neos.Flow/Classes/Security/Cryptography/RsaWalletServicePhp.php index e861eb01ea..e302021639 100644 --- a/Neos.Flow/Classes/Security/Cryptography/RsaWalletServicePhp.php +++ b/Neos.Flow/Classes/Security/Cryptography/RsaWalletServicePhp.php @@ -31,13 +31,13 @@ class RsaWalletServicePhp implements RsaWalletServiceInterface protected $keystorePathAndFilename; /** - * @var array + * @var array */ protected $keys = []; /** * The openSSL configuration - * @var array + * @var array */ protected $openSSLConfiguration = []; @@ -55,7 +55,7 @@ class RsaWalletServicePhp implements RsaWalletServiceInterface /** * Injects the OpenSSL configuration to be used * - * @param array $settings + * @param array $settings * @return void * @throws MissingConfigurationException * @throws SecurityException @@ -88,7 +88,7 @@ public function injectSettings(array $settings) public function initializeObject() { if (file_exists($this->keystorePathAndFilename)) { - $this->keys = unserialize(file_get_contents($this->keystorePathAndFilename), ['allowed_classes' => [OpenSslRsaKey::class]]); + $this->keys = unserialize(file_get_contents($this->keystorePathAndFilename) ?: '', ['allowed_classes' => [OpenSslRsaKey::class]]); } $this->saveKeysOnShutdown = false; } @@ -128,6 +128,9 @@ public function generateNewKeypair($usedForPasswords = false) public function registerKeyPairFromPrivateKeyString($privateKeyString, $usedForPasswords = false) { $keyResource = openssl_pkey_get_private($privateKeyString); + if ($keyResource === false) { + throw new \Exception('Failed to load private key', 1743877014); + } $modulus = $this->getModulus($keyResource); $publicKeyString = $this->getPublicKeyString($keyResource); @@ -149,6 +152,9 @@ public function registerKeyPairFromPrivateKeyString($privateKeyString, $usedForP public function registerPublicKeyFromString($publicKeyString) { $keyResource = openssl_pkey_get_public($publicKeyString); + if ($keyResource === false) { + throw new \Exception('Failed to load public key', 1743876948); + } $modulus = $this->getModulus($keyResource); $publicKey = new OpenSslRsaKey($modulus, $publicKeyString); @@ -163,9 +169,9 @@ public function registerPublicKeyFromString($publicKeyString) * @return OpenSslRsaKey The public key * @throws InvalidKeyPairIdException If the given fingerprint identifies no valid key pair */ - public function getPublicKey($fingerprint) + public function getPublicKey(string $fingerprint): OpenSslRsaKey { - if ($fingerprint === null || !isset($this->keys[$fingerprint])) { + if (!isset($this->keys[$fingerprint])) { throw new InvalidKeyPairIdException('Invalid keypair fingerprint given', 1231438860); } @@ -199,16 +205,16 @@ public function encryptWithPublicKey($plaintext, $fingerprint) * Note: You should never decrypt a password with this function. Use checkRSAEncryptedPassword() * to check passwords! * - * @param string $cipher cipher text to decrypt + * @param string $cypher cipher text to decrypt * @param string $fingerprint The fingerprint to identify the private key (RSA public key fingerprint) * @return string The decrypted text * @throws InvalidKeyPairIdException If the given fingerprint identifies no valid keypair * @throws DecryptionNotAllowedException If the given fingerprint identifies a keypair for encrypted passwords * @throws SecurityException If decryption failed for some other reason */ - public function decrypt($cipher, $fingerprint) + public function decrypt(string $cypher, string $fingerprint): string { - if ($fingerprint === null || !isset($this->keys[$fingerprint])) { + if (!isset($this->keys[$fingerprint])) { throw new InvalidKeyPairIdException('Invalid keypair fingerprint given', 1231438861); } @@ -218,7 +224,7 @@ public function decrypt($cipher, $fingerprint) throw new DecryptionNotAllowedException('You are not allowed to decrypt passwords!', 1233655350); } - return $this->decryptWithPrivateKey($cipher, $keyPair['privateKey']); + return $this->decryptWithPrivateKey($cypher, $keyPair['privateKey']); } /** @@ -229,9 +235,9 @@ public function decrypt($cipher, $fingerprint) * @return string The signature of the given plaintext * @throws InvalidKeyPairIdException If the given fingerprint identifies no valid keypair */ - public function sign($plaintext, $fingerprint) + public function sign(string $plaintext, string $fingerprint): string { - if ($fingerprint === null || !isset($this->keys[$fingerprint])) { + if (!isset($this->keys[$fingerprint])) { throw new InvalidKeyPairIdException('Invalid keypair fingerprint given', 1299095799); } @@ -251,9 +257,9 @@ public function sign($plaintext, $fingerprint) * @return boolean true if the signature is correct for the given plaintext and public key * @throws InvalidKeyPairIdException */ - public function verifySignature($plaintext, $signature, $fingerprint) + public function verifySignature(string $plaintext, string $signature, string $fingerprint): bool { - if ($fingerprint === null || !isset($this->keys[$fingerprint])) { + if (!isset($this->keys[$fingerprint])) { throw new InvalidKeyPairIdException('Invalid keypair fingerprint given', 1304959763); } @@ -273,9 +279,9 @@ public function verifySignature($plaintext, $signature, $fingerprint) * @return boolean true if the password is correct * @throws InvalidKeyPairIdException If the given fingerprint identifies no valid keypair */ - public function checkRSAEncryptedPassword($encryptedPassword, $passwordHash, $salt, $fingerprint) + public function checkRSAEncryptedPassword(string $encryptedPassword, string $passwordHash, string $salt, string $fingerprint): bool { - if ($fingerprint === null || !isset($this->keys[$fingerprint])) { + if (!isset($this->keys[$fingerprint])) { throw new InvalidKeyPairIdException('Invalid keypair fingerprint given', 1233655216); } @@ -288,12 +294,11 @@ public function checkRSAEncryptedPassword($encryptedPassword, $passwordHash, $sa * Destroys the keypair identified by the given fingerprint * * @param string $fingerprint The fingerprint - * @return void * @throws InvalidKeyPairIdException If the given fingerprint identifies no valid key pair */ - public function destroyKeypair($fingerprint) + public function destroyKeypair(string $fingerprint): void { - if ($fingerprint === null || !isset($this->keys[$fingerprint])) { + if (!isset($this->keys[$fingerprint])) { throw new InvalidKeyPairIdException('Invalid keypair fingerprint given', 1231438863); } @@ -322,6 +327,9 @@ private function getPrivateKeyString(\OpenSSLAsymmetricKey $keyResource) private function getPublicKeyString(\OpenSSLAsymmetricKey $keyResource) { $keyDetails = openssl_pkey_get_details($keyResource); + if ($keyDetails === false) { + throw new \Exception('Failed to get public key details', 1743876036); + } return $keyDetails['key']; } @@ -335,6 +343,9 @@ private function getPublicKeyString(\OpenSSLAsymmetricKey $keyResource) private function getModulus(\OpenSSLAsymmetricKey $keyResource) { $keyDetails = openssl_pkey_get_details($keyResource); + if ($keyDetails === false) { + throw new \Exception('Failed to load public key details', 1743875916); + } return strtoupper(bin2hex($keyDetails['rsa']['n'])); } @@ -350,6 +361,9 @@ private function decryptWithPrivateKey($cipher, OpenSslRsaKey $privateKey) { $decrypted = ''; $key = openssl_pkey_get_private($privateKey->getKeyString()); + if ($key === false) { + throw new \Exception('Failed to load private key', 1743875855); + } if (openssl_private_decrypt($cipher, $decrypted, $key, $this->paddingAlgorithm) === false) { // Fallback for data that was encrypted with old default OPENSSL_PKCS1_PADDING if ($this->paddingAlgorithm !== OPENSSL_PKCS1_PADDING) { @@ -374,7 +388,7 @@ private function decryptWithPrivateKey($cipher, OpenSslRsaKey $privateKey) * consistent key access. * * @param OpenSslRsaKey $publicKey The public key - * @param OpenSslRsaKey $privateKey The private key + * @param OpenSslRsaKey|null $privateKey The private key * @param boolean $usedForPasswords true if this keypair should be used to encrypt passwords (then decryption won't be allowed!). * @return string The fingerprint which is used as an identifier for storing the key pair */ @@ -439,7 +453,13 @@ public function shutdownObject() public function getFingerprintByPublicKey($publicKeyString) { $keyResource = openssl_pkey_get_public($publicKeyString); + if ($keyResource === false) { + throw new \Exception('Could not extract public key', 1743875618); + } $keyDetails = openssl_pkey_get_details($keyResource); + if ($keyDetails === false) { + throw new \Exception('Could not extract public key details', 1743875650); + } $modulus = $this->sshConvertMpint($keyDetails['rsa']['n']); $publicExponent = $this->sshConvertMpint($keyDetails['rsa']['e']); diff --git a/Neos.Flow/Classes/Security/DummyContext.php b/Neos.Flow/Classes/Security/DummyContext.php index a9689677ef..57474421d9 100644 --- a/Neos.Flow/Classes/Security/DummyContext.php +++ b/Neos.Flow/Classes/Security/DummyContext.php @@ -35,7 +35,7 @@ class DummyContext extends Context /** * Array of configured tokens (might have request patterns) - * @var array + * @var array */ protected $tokens = []; @@ -86,7 +86,7 @@ public function getAuthenticationStrategy() * Sets the Authentication\Tokens of the security context which should be active. * * @param TokenInterface[] $tokens Array of set tokens - * @return array + * @return array */ public function setAuthenticationTokens(array $tokens) { @@ -110,7 +110,7 @@ public function getAuthenticationTokens() * active for the current request and of the given type. If a token has a request pattern that cannot match * against the current request it is determined as not active. * - * @param string $className The class name + * @param class-string $className The class name * @return TokenInterface[] Array of set tokens of the specified type */ public function getAuthenticationTokensOfType($className) @@ -132,7 +132,7 @@ public function getAuthenticationTokensOfType($className) * * The "Neos.Flow:Everybody" roles is always returned. * - * @return Role[] + * @return Role[]|null */ public function getRoles() { @@ -185,7 +185,7 @@ public function setCsrfProtectionToken($csrfProtectionToken) * Returns the current CSRF protection token. A new one is created when needed, depending on the configured CSRF * protection strategy. * - * @return string + * @return string|null */ public function getCsrfProtectionToken() { @@ -231,7 +231,7 @@ public function setInterceptedRequest(?ActionRequest $interceptedRequest = null) * Returns the request, that has been stored for later resuming after it * has been intercepted by a security exception, NULL if there is none. * - * @return ActionRequest + * @return ActionRequest|null */ public function getInterceptedRequest() { diff --git a/Neos.Flow/Classes/Security/Policy/PolicyService.php b/Neos.Flow/Classes/Security/Policy/PolicyService.php index f2e8228ccb..aab68aa904 100644 --- a/Neos.Flow/Classes/Security/Policy/PolicyService.php +++ b/Neos.Flow/Classes/Security/Policy/PolicyService.php @@ -46,7 +46,7 @@ class PolicyService protected $configurationManager; /** - * @var array + * @var array */ protected $policyConfiguration; @@ -198,7 +198,7 @@ protected function initializePrivilegeTargets(): void if ($parameterClassName === null) { throw new SecurityException(sprintf('No "className" defined for parameter "%s" in privilegeTarget "%s"', $parameterName, $privilegeTargetIdentifier), 1396021782); } - if (!in_array(PrivilegeParameterInterface::class, class_implements($parameterClassName), true)) { + if (!in_array(PrivilegeParameterInterface::class, class_implements($parameterClassName) ?: [], true)) { throw new SecurityException(sprintf('PrivilegeParameterInterface must be implemented for "className" defined for parameter "%s" in privilegeTarget "%s"', $parameterName, $privilegeTargetIdentifier), 1396021782); } $parameterDefinitions[$parameterName] = new PrivilegeParameterDefinition($parameterName, $parameterClassName); @@ -266,7 +266,7 @@ public function getRoles($includeAbstract = false): array * Returns all privileges of the given type * * @param string $type Full qualified class or interface name - * @return array + * @return array * @throws InvalidConfigurationTypeException * @throws SecurityException */ @@ -325,7 +325,7 @@ public function reset(): void * This signal can be used to add roles and/or privilegeTargets during runtime. In the slot make sure to receive the * $policyConfiguration array by reference so you can alter it. * - * @param array $policyConfiguration The policy configuration + * @param array $policyConfiguration The policy configuration * @return void * @Flow\Signal */ diff --git a/Neos.Flow/Classes/Security/Policy/Role.php b/Neos.Flow/Classes/Security/Policy/Role.php index e4ae2790ac..b1e5968140 100644 --- a/Neos.Flow/Classes/Security/Policy/Role.php +++ b/Neos.Flow/Classes/Security/Policy/Role.php @@ -14,6 +14,7 @@ */ use Neos\Flow\Annotations as Flow; +use Neos\Flow\Security\Authorization\Privilege\Parameter\PrivilegeParameterInterface; use Neos\Flow\Security\Authorization\Privilege\PrivilegeInterface; /** @@ -246,7 +247,7 @@ public function getPrivilegesByType(string $className): array /** * @param string $privilegeTargetIdentifier - * @param array $privilegeParameters + * @param array $privilegeParameters * @return PrivilegeInterface|null the matching privilege or NULL if no privilege exists for the given constraints */ public function getPrivilegeForTarget(string $privilegeTargetIdentifier, array $privilegeParameters = []): ?PrivilegeInterface diff --git a/Neos.Flow/Classes/Security/Policy/RoleConverter.php b/Neos.Flow/Classes/Security/Policy/RoleConverter.php index bd599cb85d..b90c842404 100644 --- a/Neos.Flow/Classes/Security/Policy/RoleConverter.php +++ b/Neos.Flow/Classes/Security/Policy/RoleConverter.php @@ -52,7 +52,7 @@ class RoleConverter extends AbstractTypeConverter * * @param mixed $source * @param string $targetType - * @param array $convertedChildProperties + * @param array $convertedChildProperties * @param PropertyMappingConfigurationInterface|null $configuration * @return object the target type */ diff --git a/Neos.Flow/Classes/Security/RequestPattern/ControllerObjectName.php b/Neos.Flow/Classes/Security/RequestPattern/ControllerObjectName.php index b95619530c..9c61769698 100644 --- a/Neos.Flow/Classes/Security/RequestPattern/ControllerObjectName.php +++ b/Neos.Flow/Classes/Security/RequestPattern/ControllerObjectName.php @@ -21,14 +21,14 @@ class ControllerObjectName implements RequestPatternInterface { /** - * @var array + * @var array */ protected $options; /** * Expects options in the form array('controllerObjectNamePattern' => '') * - * @param array $options + * @param array $options */ public function __construct(array $options) { diff --git a/Neos.Flow/Classes/Security/RequestPattern/CsrfProtection.php b/Neos.Flow/Classes/Security/RequestPattern/CsrfProtection.php index ffa2eef7e2..f7a7bc8922 100644 --- a/Neos.Flow/Classes/Security/RequestPattern/CsrfProtection.php +++ b/Neos.Flow/Classes/Security/RequestPattern/CsrfProtection.php @@ -101,6 +101,9 @@ public function matchRequest(ActionRequest $request) } $controllerClassName = $this->objectManager->getClassNameByObjectName($request->getControllerObjectName()); + if ($controllerClassName === false) { + throw new \Exception('Failed to resolve controller class name for ' . $request->getControllerObjectName(), 1743926779); + } $actionMethodName = $request->getControllerActionName() . 'Action'; if (!$this->hasPolicyEntryForMethod($controllerClassName, $actionMethodName)) { diff --git a/Neos.Flow/Classes/Security/RequestPattern/Host.php b/Neos.Flow/Classes/Security/RequestPattern/Host.php index 1b1c1a00a9..3265905e68 100644 --- a/Neos.Flow/Classes/Security/RequestPattern/Host.php +++ b/Neos.Flow/Classes/Security/RequestPattern/Host.php @@ -25,14 +25,14 @@ class Host implements RequestPatternInterface { /** - * @var array + * @var array */ protected $options; /** * Expects options in the form array('hostPattern' => '') * - * @param array $options + * @param array $options */ public function __construct(array $options) { diff --git a/Neos.Flow/Classes/Security/RequestPattern/Ip.php b/Neos.Flow/Classes/Security/RequestPattern/Ip.php index 18cf903ad5..02358ebf46 100644 --- a/Neos.Flow/Classes/Security/RequestPattern/Ip.php +++ b/Neos.Flow/Classes/Security/RequestPattern/Ip.php @@ -35,14 +35,14 @@ class Ip implements RequestPatternInterface { /** - * @var array + * @var array */ protected $options; /** * Expects options in the form array('cidrPattern' => '') * - * @param array $options + * @param array $options */ public function __construct(array $options) { diff --git a/Neos.Flow/Classes/Security/RequestPattern/Uri.php b/Neos.Flow/Classes/Security/RequestPattern/Uri.php index 9127691d1b..0121585cf1 100644 --- a/Neos.Flow/Classes/Security/RequestPattern/Uri.php +++ b/Neos.Flow/Classes/Security/RequestPattern/Uri.php @@ -21,14 +21,14 @@ class Uri implements RequestPatternInterface { /** - * @var array + * @var array */ protected $options; /** * Expects options in the form array('uriPattern' => '') * - * @param array $options + * @param array $options */ public function __construct(array $options) { diff --git a/Neos.Flow/Classes/Security/SessionDataContainer.php b/Neos.Flow/Classes/Security/SessionDataContainer.php index d35fb8e870..5741c6e519 100644 --- a/Neos.Flow/Classes/Security/SessionDataContainer.php +++ b/Neos.Flow/Classes/Security/SessionDataContainer.php @@ -4,6 +4,7 @@ use Neos\Flow\Annotations as Flow; use Neos\Flow\Mvc\ActionRequest; use Neos\Flow\Security\Authentication\Token\SessionlessTokenInterface; +use Neos\Flow\Security\Authentication\TokenInterface; /** * @Flow\Scope("session") @@ -14,14 +15,14 @@ class SessionDataContainer /** * The current list of security tokens. * - * @var array + * @var array */ protected $securityTokens = []; /** * The current list of CSRF tokens * - * @var array + * @var array */ protected $csrfProtectionTokens = []; @@ -35,7 +36,7 @@ class SessionDataContainer /** * Get the current list of security tokens. * - * @return array + * @return array */ public function getSecurityTokens(): array { @@ -45,9 +46,9 @@ public function getSecurityTokens(): array /** * Set the current list of security tokens with their data. * - * @param array $securityTokens + * @param array $securityTokens */ - public function setSecurityTokens(array $securityTokens) + public function setSecurityTokens(array $securityTokens): void { foreach ($securityTokens as $token) { if ($token instanceof SessionlessTokenInterface) { @@ -60,7 +61,7 @@ public function setSecurityTokens(array $securityTokens) /** * Get the current list of active CSRF tokens. * - * @return array + * @return array */ public function getCsrfProtectionTokens(): array { @@ -70,9 +71,9 @@ public function getCsrfProtectionTokens(): array /** * set the list of currently active CSRF tokens. * - * @param array $csrfProtectionTokens + * @param array $csrfProtectionTokens */ - public function setCsrfProtectionTokens(array $csrfProtectionTokens) + public function setCsrfProtectionTokens(array $csrfProtectionTokens): void { $this->csrfProtectionTokens = $csrfProtectionTokens; } diff --git a/Neos.Flow/Classes/Session/Aspect/LazyLoadingAspect.php b/Neos.Flow/Classes/Session/Aspect/LazyLoadingAspect.php index 6254dd84a7..996819e604 100644 --- a/Neos.Flow/Classes/Session/Aspect/LazyLoadingAspect.php +++ b/Neos.Flow/Classes/Session/Aspect/LazyLoadingAspect.php @@ -29,6 +29,9 @@ class LazyLoadingAspect #[Flow\Inject] protected ?LoggerInterface $logger = null; + /** + * @var array + */ protected array $sessionOriginalInstances = []; public function __construct( diff --git a/Neos.Flow/Classes/Session/Aspect/SessionObjectMethodsPointcutFilter.php b/Neos.Flow/Classes/Session/Aspect/SessionObjectMethodsPointcutFilter.php index 1f89a3be0b..0f4e24cefe 100644 --- a/Neos.Flow/Classes/Session/Aspect/SessionObjectMethodsPointcutFilter.php +++ b/Neos.Flow/Classes/Session/Aspect/SessionObjectMethodsPointcutFilter.php @@ -37,7 +37,7 @@ public function injectObjectManager(CompileTimeObjectManager $objectManager): vo * Checks if the specified class and method matches against the filter * * @param string $className Name of the class to check against - * @param string $methodName Name of the method to check against + * @param ?string $methodName Name of the method to check against * @param string $methodDeclaringClassName Name of the class the method was originally declared in * @param mixed $pointcutQueryIdentifier Some identifier for this query - must at least differ from a previous identifier. Used for circular reference detection. * @return bool true if the class / method match, otherwise false @@ -79,7 +79,7 @@ public function hasRuntimeEvaluationsDefinition(): bool /** * Returns runtime evaluations for a previously matched pointcut * - * @return array Runtime evaluations + * @return array Runtime evaluations */ public function getRuntimeEvaluationsDefinition(): array { diff --git a/Neos.Flow/Classes/Session/CookieEnabledInterface.php b/Neos.Flow/Classes/Session/CookieEnabledInterface.php index 522f07079c..c722999dad 100644 --- a/Neos.Flow/Classes/Session/CookieEnabledInterface.php +++ b/Neos.Flow/Classes/Session/CookieEnabledInterface.php @@ -18,5 +18,8 @@ interface CookieEnabledInterface extends SessionInterface { public function getSessionCookie(): Cookie; + /** + * @param array $tags + */ public static function createFromCookieAndSessionInformation(Cookie $sessionCookie, string $storageIdentifier, int $lastActivityTimestamp, array $tags = []): SessionInterface|CookieEnabledInterface; } diff --git a/Neos.Flow/Classes/Session/Data/SessionKeyValueStore.php b/Neos.Flow/Classes/Session/Data/SessionKeyValueStore.php index 18793fcaf5..a4ec948c1e 100644 --- a/Neos.Flow/Classes/Session/Data/SessionKeyValueStore.php +++ b/Neos.Flow/Classes/Session/Data/SessionKeyValueStore.php @@ -72,6 +72,9 @@ public function store(StorageIdentifier $storageIdentifier, string $key, mixed $ { $entryIdentifier = $this->createEntryIdentifier($storageIdentifier, $key); $serializedValue = ($this->useIgBinary === true) ? igbinary_serialize($value) : serialize($value); + if (is_null($serializedValue)) { + throw new \Exception('Failed to serialize value', 1743874462); + } $valueHash = md5($serializedValue); $debounceHash = $this->writeDebounceHashes[$storageIdentifier->value][$key] ?? null; if ($debounceHash !== null && $debounceHash === $valueHash) { @@ -90,7 +93,7 @@ public function remove(StorageIdentifier $storageIdentifier): int return $this->cache->flushByTag($storageIdentifier->value); } - private function createEntryIdentifier(StorageIdentifier $storageIdentifier, $key): string + private function createEntryIdentifier(StorageIdentifier $storageIdentifier, string $key): string { return $storageIdentifier->value . md5($key); } diff --git a/Neos.Flow/Classes/Session/Data/SessionMetaDataStore.php b/Neos.Flow/Classes/Session/Data/SessionMetaDataStore.php index a61eec9bff..81a21f9905 100644 --- a/Neos.Flow/Classes/Session/Data/SessionMetaDataStore.php +++ b/Neos.Flow/Classes/Session/Data/SessionMetaDataStore.php @@ -82,6 +82,15 @@ public function retrieve(SessionIdentifier $sessionIdentifier): ?SessionMetaData } if (is_array($metaDataFromCache)) { + if (!is_string($metaDataFromCache['storageIdentifier'] ?? null)) { + throw new \Exception('Missing storageIdentifier', 1743874214); + } + if (!is_int($metaDataFromCache['lastActivityTimestamp'])) { + throw new \Exception('Missing lastActivityTimestamp', 1743874236); + } + if (!is_array($metaDataFromCache['tags'])) { + throw new \Exception('Missing tags', 1743874272); + } $metaDataFromCache = SessionMetaData::createFromSessionIdentifierStringAndOldArrayCacheFormat($sessionIdentifier->value, $metaDataFromCache); $this->writeDebounceCache[$metaDataFromCache->sessionIdentifier->value] = $metaDataFromCache; return $metaDataFromCache; @@ -104,6 +113,15 @@ public function retrieveByTag(string $tag): \Generator $this->writeDebounceCache[$sessionIdentifier] = $sessionMetaData; yield $sessionIdentifier => $sessionMetaData; } elseif (is_array($sessionMetaData)) { + if (!is_string($sessionMetaData['storageIdentifier'] ?? null)) { + throw new \Exception('Missing storageIdentifier', 1743874214); + } + if (!is_int($sessionMetaData['lastActivityTimestamp'])) { + throw new \Exception('Missing lastActivityTimestamp', 1743874236); + } + if (!is_array($sessionMetaData['tags'])) { + throw new \Exception('Missing tags', 1743874272); + } $sessionMetaData = SessionMetaData::createFromSessionIdentifierStringAndOldArrayCacheFormat($sessionIdentifier, $sessionMetaData); $this->writeDebounceCache[$sessionIdentifier] = $sessionMetaData; yield $sessionIdentifier => $sessionMetaData; @@ -125,7 +143,19 @@ public function retrieveAll(): \Generator $this->writeDebounceCache[$sessionIdentifier] = $sessionMetaData; yield $sessionIdentifier => $sessionMetaData; } elseif (is_array($sessionMetaData)) { - $sessionMetaData = SessionMetaData::createFromSessionIdentifierStringAndOldArrayCacheFormat($sessionIdentifier, $sessionMetaData); + if (!is_string($sessionMetaData['storageIdentifier'] ?? null)) { + throw new \Exception('Missing storageIdentifier', 1743874214); + } + if (!is_int($sessionMetaData['lastActivityTimestamp'])) { + throw new \Exception('Missing lastActivityTimestamp', 1743874236); + } + if (!is_array($sessionMetaData['tags'])) { + throw new \Exception('Missing tags', 1743874272); + } + $sessionMetaData = SessionMetaData::createFromSessionIdentifierStringAndOldArrayCacheFormat( + $sessionIdentifier, + $sessionMetaData + ); $this->writeDebounceCache[$sessionIdentifier] = $sessionMetaData; yield $sessionIdentifier => $sessionMetaData; } diff --git a/Neos.Flow/Classes/Session/Session.php b/Neos.Flow/Classes/Session/Session.php index 1d67a029b9..8233bb8d13 100644 --- a/Neos.Flow/Classes/Session/Session.php +++ b/Neos.Flow/Classes/Session/Session.php @@ -71,6 +71,9 @@ class Session implements CookieEnabledInterface protected ?string $sessionCookieSameSite = null; protected ?Cookie $sessionCookie = null; protected int $inactivityTimeout; + /** + * @var array + */ protected array $tags = []; protected int $now = 0; protected ?SessionMetaData $sessionMetaData = null; @@ -91,7 +94,10 @@ public static function create(): self return new static(); } - public static function createRemote(string $sessionIdentifier, string $storageIdentifier, ?int $lastActivityTimestamp = null, array $tags = []): self + /** + * @param array $tags + */ + public static function createRemote(string $sessionIdentifier, string $storageIdentifier, int $lastActivityTimestamp, array $tags = []): self { $session = new static(); $session->sessionMetaData = new SessionMetaData( @@ -114,6 +120,9 @@ public static function createRemoteFromSessionMetaData(SessionMetaData $sessionM return $session; } + /** + * @param array $tags + */ public static function createFromCookieAndSessionInformation(Cookie $sessionCookie, string $storageIdentifier, int $lastActivityTimestamp, array $tags = []): SessionInterface|CookieEnabledInterface { $session = new static(); @@ -127,6 +136,9 @@ public static function createFromCookieAndSessionInformation(Cookie $sessionCook return $session; } + /** + * @param array $settings + */ public function injectSettings(array $settings): void { $this->sessionCookieName = $settings['session']['name']; @@ -141,6 +153,9 @@ public function injectSettings(array $settings): void public function getSessionCookie(): Cookie { + if (!$this->sessionCookie) { + throw new \Exception('Missing session cookie'); + } return $this->sessionCookie; } @@ -172,6 +187,9 @@ public function start(): void { if ($this->started === false) { $this->sessionMetaData = SessionMetaData::createWithTimestamp($this->now); + if (!$this->sessionCookiePath) { + throw new \Exception('Cannot start a session without a path'); + } $this->sessionCookie = new Cookie($this->sessionCookieName, $this->sessionMetaData->sessionIdentifier->value, 0, $this->sessionCookieLifetime, $this->sessionCookieDomain, $this->sessionCookiePath, $this->sessionCookieSecure, $this->sessionCookieHttpOnly, $this->sessionCookieSameSite); $this->started = true; @@ -221,6 +239,9 @@ public function canBeResumed(): bool public function resume(): ?int { if ($this->started === false && $this->canBeResumed()) { + if (!$this->sessionMetaData) { + throw new \Exception('Cannot resume a session without metadata'); + } $this->started = true; $sessionObjects = $this->sessionKeyValueStore->retrieve($this->sessionMetaData->storageIdentifier, self::FLOW_OBJECT_STORAGE_KEY); @@ -256,7 +277,7 @@ public function resume(): ?int */ public function getId(): string { - if ($this->started !== true) { + if ($this->started !== true || !$this->sessionMetaData) { throw new Exception\SessionNotStartedException('Tried to retrieve the session identifier, but the session has not been started yet.)', 1351171517); } return $this->sessionMetaData->sessionIdentifier->value; @@ -276,6 +297,12 @@ public function renewId(): string if ($this->started !== true) { throw new Exception\SessionNotStartedException('Tried to renew the session identifier, but the session has not been started yet.', 1351182429); } + if (!$this->sessionMetaData) { + throw new \Exception('Missing session metadata'); + } + if (!$this->sessionCookie) { + throw new \Exception('Missing session cookie'); + } if ($this->remote === true) { throw new Exception\OperationNotSupportedException(sprintf('Tried to renew the session identifier on a remote session (%s).', $this->sessionMetaData->sessionIdentifier->value), 1354034230); } @@ -284,6 +311,12 @@ public function renewId(): string $this->sessionMetaData = $this->sessionMetaData->withNewSessionIdentifier(); $this->writeSessionMetaDataCacheEntry(); + if (!$this->sessionMetaData) { + throw new \Exception('Missing session metadata'); + } + if (!$this->sessionCookie) { + throw new \Exception('Missing session cookie'); + } $this->sessionCookie->setValue($this->sessionMetaData->sessionIdentifier->value); return $this->sessionMetaData->sessionIdentifier->value; } @@ -297,7 +330,7 @@ public function renewId(): string */ public function getData(string $key): mixed { - if ($this->started !== true) { + if ($this->started !== true || !$this->sessionMetaData) { throw new Exception\SessionNotStartedException('Tried to get session data, but the session has not been started yet.', 1351162255); } return $this->sessionKeyValueStore->retrieve($this->sessionMetaData->storageIdentifier, $key); @@ -312,7 +345,7 @@ public function getData(string $key): mixed */ public function hasKey(string $key): bool { - if ($this->started !== true) { + if ($this->started !== true || !$this->sessionMetaData) { throw new Exception\SessionNotStartedException('Tried to check a session data entry, but the session has not been started yet.', 1352488661); } return $this->sessionKeyValueStore->has($this->sessionMetaData->storageIdentifier, $key); @@ -332,7 +365,7 @@ public function hasKey(string $key): bool */ public function putData(string $key, mixed $data): void { - if ($this->started !== true) { + if ($this->started !== true || !$this->sessionMetaData) { throw new Exception\SessionNotStartedException('Tried to create a session data entry, but the session has not been started yet.', 1351162259); } if (is_resource($data)) { @@ -354,7 +387,7 @@ public function putData(string $key, mixed $data): void */ public function getLastActivityTimestamp(): int { - if ($this->started !== true) { + if ($this->started !== true || !$this->sessionMetaData) { throw new Exception\SessionNotStartedException('Tried to retrieve the last activity timestamp of a session which has not been started yet.', 1354290378); } return $this->sessionMetaData->lastActivityTimestamp; @@ -378,9 +411,9 @@ public function addTag(string $tag): void throw new Exception\SessionNotStartedException('Tried to tag a session which has not been started yet.', 1355143533); } if (!$this->sessionMetaDataStore->isValidSessionTag($tag)) { - throw new \InvalidArgumentException(sprintf('The tag used for tagging session %s contained invalid characters. Make sure it matches this regular expression: "%s"', $this->sessionMetaData->sessionIdentifier->value, FrontendInterface::PATTERN_TAG)); + throw new \InvalidArgumentException(sprintf('The tag used for tagging session %s contained invalid characters. Make sure it matches this regular expression: "%s"', $this->sessionMetaData?->sessionIdentifier->value ?: '', FrontendInterface::PATTERN_TAG)); } - $this->sessionMetaData = $this->sessionMetaData->withAddedTag($tag); + $this->sessionMetaData = $this->sessionMetaData?->withAddedTag($tag); } /** @@ -396,14 +429,14 @@ public function removeTag(string $tag): void if ($this->started !== true) { throw new Exception\SessionNotStartedException('Tried to tag a session which has not been started yet.', 1355150140); } - $this->sessionMetaData = $this->sessionMetaData->withRemovedTag($tag); + $this->sessionMetaData = $this->sessionMetaData?->withRemovedTag($tag); } /** * Returns the tags this session has been tagged with. * - * @return array The tags or an empty array if there aren't any + * @return array The tags or an empty array if there aren't any * @throws Exception\SessionNotStartedException * @api */ @@ -412,7 +445,7 @@ public function getTags(): array if ($this->started !== true) { throw new Exception\SessionNotStartedException('Tried to retrieve tags from a session which has not been started yet.', 1355141501); } - return $this->sessionMetaData->tags; + return $this->sessionMetaData?->tags ?: []; } /** @@ -428,7 +461,7 @@ public function touch(): void // Only makes sense for remote sessions because the currently active session // will be updated on shutdown anyway: - if ($this->remote === true) { + if ($this->remote === true && $this->sessionMetaData) { $this->sessionMetaData = $this->sessionMetaData->withLastActivityTimestamp($this->now); $this->writeSessionMetaDataCacheEntry(); } @@ -461,12 +494,14 @@ public function destroy(?string $reason = null): void } if ($this->remote !== true) { - $this->sessionCookie->expire(); + $this->sessionCookie?->expire(); } - $this->sessionMetaDataStore->remove($this->sessionMetaData); - $this->sessionKeyValueStore->remove($this->sessionMetaData->storageIdentifier); - $this->sessionMetaData = null; + if ($this->sessionMetaData) { + $this->sessionMetaDataStore->remove($this->sessionMetaData); + $this->sessionKeyValueStore->remove($this->sessionMetaData->storageIdentifier); + $this->sessionMetaData = null; + } $this->started = false; } @@ -482,8 +517,12 @@ public function destroy(?string $reason = null): void public function shutdownObject(): void { if ($this->started === true && $this->remote === false) { - if ($this->sessionMetaDataStore->has($this->sessionMetaData->sessionIdentifier)) { - $this->sessionKeyValueStore->store($this->sessionMetaData->storageIdentifier, self::FLOW_OBJECT_STORAGE_KEY, $this->objectManager->getSessionInstances() ?? []); + if ($this->sessionMetaData && $this->sessionMetaDataStore->has($this->sessionMetaData->sessionIdentifier)) { + $this->sessionKeyValueStore->store( + $this->sessionMetaData->storageIdentifier, + self::FLOW_OBJECT_STORAGE_KEY, + $this->objectManager->getSessionInstances() + ); $this->writeSessionMetaDataCacheEntry(); } $this->started = false; @@ -497,11 +536,11 @@ public function shutdownObject(): void */ protected function autoExpire(): bool { - $lastActivitySecondsAgo = $this->now - $this->sessionMetaData->lastActivityTimestamp; + $lastActivitySecondsAgo = $this->now - ($this->sessionMetaData?->lastActivityTimestamp ?: 0); $expired = false; if ($this->inactivityTimeout !== 0 && $lastActivitySecondsAgo > $this->inactivityTimeout) { $this->started = true; - $this->destroy(sprintf('Session %s was inactive for %s seconds, more than the configured timeout of %s seconds.', $this->sessionMetaData->sessionIdentifier->value, $lastActivitySecondsAgo, $this->inactivityTimeout)); + $this->destroy(sprintf('Session %s was inactive for %s seconds, more than the configured timeout of %s seconds.', $this->sessionMetaData?->sessionIdentifier->value ?: '', $lastActivitySecondsAgo, $this->inactivityTimeout)); $expired = true; } return $expired; @@ -521,6 +560,8 @@ protected function autoExpire(): bool */ protected function writeSessionMetaDataCacheEntry(): void { - $this->sessionMetaDataStore->store($this->sessionMetaData); + if ($this->sessionMetaData) { + $this->sessionMetaDataStore->store($this->sessionMetaData); + } } } diff --git a/Neos.Flow/Classes/Session/SessionInterface.php b/Neos.Flow/Classes/Session/SessionInterface.php index 791f21debf..b9c8b51485 100644 --- a/Neos.Flow/Classes/Session/SessionInterface.php +++ b/Neos.Flow/Classes/Session/SessionInterface.php @@ -107,7 +107,7 @@ public function removeTag(string $tag): void; /** * Returns the tags this session has been tagged with. * - * @return array The tags or an empty array if there aren't any + * @return array The tags or an empty array if there aren't any * @throws Exception\SessionNotStartedException * @api */ diff --git a/Neos.Flow/Classes/Session/SessionManager.php b/Neos.Flow/Classes/Session/SessionManager.php index cdf8d89a90..c6629baccd 100644 --- a/Neos.Flow/Classes/Session/SessionManager.php +++ b/Neos.Flow/Classes/Session/SessionManager.php @@ -27,6 +27,10 @@ class SessionManager implements SessionManagerInterface { protected ?SessionInterface $currentSession = null; + + /** + * @var array + */ protected array $remoteSessions = []; public function __construct( @@ -139,7 +143,7 @@ public function getActiveSessions(): array * Returns all sessions which are tagged by the specified tag. * * @param string $tag A valid Cache Frontend tag - * @return array A collection of Session objects or an empty array if tag did not match + * @return array A collection of Session objects or an empty array if tag did not match * @throws NotSupportedByBackendException * @api */ @@ -195,10 +199,8 @@ public function collectGarbage(): ?int foreach ($this->sessionMetaDataStore->retrieveAll() as $sessionMetadata) { $lastActivitySecondsAgo = $now - $sessionMetadata->lastActivityTimestamp; if ($lastActivitySecondsAgo > $this->inactivityTimeout) { - if ($sessionMetadata->lastActivityTimestamp !== null) { - $this->sessionKeyValueStore->remove($sessionMetadata->storageIdentifier); - $sessionRemovalCount++; - } + $this->sessionKeyValueStore->remove($sessionMetadata->storageIdentifier); + $sessionRemovalCount++; $this->sessionMetaDataStore->remove($sessionMetadata); } if ($sessionRemovalCount >= $this->garbageCollectionMaximumPerRun) { @@ -223,7 +225,7 @@ public function collectGarbage(): ?int public function shutdownObject(): void { if (str_contains((string)$this->garbageCollectionProbability, '.')) { - $decimals = strlen(strrchr((string)$this->garbageCollectionProbability, '.')) - 1; + $decimals = strlen(strrchr((string)$this->garbageCollectionProbability, '.') ?: '') - 1; $factor = $decimals * 10; } else { $factor = 1; diff --git a/Neos.Flow/Classes/Session/SessionManagerInterface.php b/Neos.Flow/Classes/Session/SessionManagerInterface.php index de71da87fd..1455968e47 100644 --- a/Neos.Flow/Classes/Session/SessionManagerInterface.php +++ b/Neos.Flow/Classes/Session/SessionManagerInterface.php @@ -51,7 +51,7 @@ public function getActiveSessions(): array; * Returns all sessions which are tagged by the specified tag. * * @param string $tag A valid Cache Frontend tag - * @return array A collection of Session objects or an empty array if tag did not match + * @return array A collection of Session objects or an empty array if tag did not match * @api */ public function getSessionsByTag(string $tag): array; diff --git a/Neos.Flow/Classes/Session/TransientSession.php b/Neos.Flow/Classes/Session/TransientSession.php index 0e15bb8231..815184ea8a 100644 --- a/Neos.Flow/Classes/Session/TransientSession.php +++ b/Neos.Flow/Classes/Session/TransientSession.php @@ -26,8 +26,14 @@ class TransientSession implements SessionInterface { protected string $sessionId; protected bool $started = false; + /** + * @var array + */ protected array $data = []; protected ?int $lastActivityTimestamp = null; + /** + * @var array + */ protected array $tags; public function isStarted(): bool @@ -165,6 +171,7 @@ public function getLastActivityTimestamp(): int if ($this->lastActivityTimestamp === null) { $this->touch(); } + /** @phpstan-ignore return.type (touch() initializes the timestamp) */ return $this->lastActivityTimestamp; } @@ -217,7 +224,7 @@ public function removeTag(string $tag): void /** * Returns the tags this session has been tagged with. * - * @return array The tags or an empty array if there aren't any + * @return array The tags or an empty array if there aren't any * @throws Exception\SessionNotStartedException * @api */ diff --git a/Neos.Flow/Classes/SignalSlot/Dispatcher.php b/Neos.Flow/Classes/SignalSlot/Dispatcher.php index b802e14517..45331881f0 100644 --- a/Neos.Flow/Classes/SignalSlot/Dispatcher.php +++ b/Neos.Flow/Classes/SignalSlot/Dispatcher.php @@ -24,18 +24,21 @@ */ class Dispatcher { - /** - * @var ObjectManagerInterface - */ - protected $objectManager; + protected ?ObjectManagerInterface $objectManager = null; /** * Information about all slots connected a certain signal. * Indexed by [$signalClassName][$signalMethodName] and then numeric with an * array of information about the slot - * @var array + * @var array>> */ - protected $slots = []; + protected array $slots = []; /** * Injects the object manager @@ -56,7 +59,7 @@ public function injectObjectManager(ObjectManagerInterface $objectManager): void * When $passSignalInformation is true, the slot will be passed a string (EmitterClassName::signalName) as the last * parameter. * - * @param string $signalClassName Name of the class containing the signal + * @param class-string $signalClassName Name of the class containing the signal * @param string $signalName Name of the signal * @param mixed $slotClassNameOrObject Name of the class containing the slot or the instantiated class or a Closure object * @param string $slotMethodName Name of the method to be used as a slot. If $slotClassNameOrObject is a Closure object, this parameter is ignored @@ -78,7 +81,7 @@ public function connect(string $signalClassName, string $signalName, $slotClassN * * The slot will be passed a an instance of SignalInformation as the sole parameter. * - * @param string $signalClassName Name of the class containing the signal + * @param class-string $signalClassName Name of the class containing the signal * @param string $signalName Name of the signal * @param mixed $slotClassNameOrObject Name of the class containing the slot or the instantiated class or a Closure object * @param string $slotMethodName Name of the method to be used as a slot. If $slotClassNameOrObject is a Closure object, this parameter is ignored @@ -94,7 +97,7 @@ public function wire(string $signalClassName, string $signalName, $slotClassName } /** - * @param string $signalClassName + * @param class-string $signalClassName * @param string $signalName * @param mixed $slotClassNameOrObject * @param string $slotMethodName @@ -116,6 +119,9 @@ private function connectSignalToSlot(string $signalClassName, string $signalName $object = $slotClassNameOrObject; $method = ($slotClassNameOrObject instanceof \Closure) ? '__invoke' : $slotMethodName; } else { + if (!class_exists($slotClassNameOrObject)) { + throw new \Exception('Unkown class ' . $slotClassNameOrObject, 1743926351); + } if ($slotMethodName === '') { throw new \InvalidArgumentException('The slot method name must not be empty (except for closures).', 1229531659); } @@ -137,7 +143,7 @@ private function connectSignalToSlot(string $signalClassName, string $signalName * * @param string $signalClassName Name of the class containing the signal * @param string $signalName Name of the signal - * @param array $signalArguments arguments passed to the signal method + * @param array $signalArguments arguments passed to the signal method * @return void * @throws Exception\InvalidSlotException if the slot is not valid * @api @@ -153,37 +159,48 @@ public function dispatch(string $signalClassName, string $signalName, array $sig if (isset($slotInformation['object'])) { $object = $slotInformation['object']; } elseif (strpos($slotInformation['method'], '::') === 0) { - if (!isset($this->objectManager)) { + if ($this->objectManager === null) { if (is_callable($slotInformation['class'] . $slotInformation['method'])) { $object = $slotInformation['class']; } else { throw new Exception\InvalidSlotException(sprintf('Cannot dispatch %s::%s to class %s. The object manager is not yet available in the Signal Slot Dispatcher and therefore it cannot dispatch classes.', $signalClassName, $signalName, $slotInformation['class']), 1298113624); } } else { + if ($slotInformation['class'] === null) { + throw new Exception\InvalidSlotException('Slot class is undefined.', 1743958214); + } $object = $this->objectManager->getClassNameByObjectName($slotInformation['class']); } $slotInformation['method'] = substr($slotInformation['method'], 2); } else { - if (!isset($this->objectManager)) { + if ($this->objectManager === null) { throw new Exception\InvalidSlotException(sprintf('Cannot dispatch %s::%s to class %s. The object manager is not yet available in the Signal Slot Dispatcher and therefore it cannot dispatch classes.', $signalClassName, $signalName, $slotInformation['class']), 1298113624); } + if ($slotInformation['class'] === null) { + throw new Exception\InvalidSlotException('Slot class is undefined.', 1743958214); + } if (!$this->objectManager->isRegistered($slotInformation['class'])) { throw new Exception\InvalidSlotException('The given class "' . $slotInformation['class'] . '" is not a registered object.', 1245673367); } $object = $this->objectManager->get($slotInformation['class']); } - if (!method_exists($object, $slotInformation['method'])) { + if (is_object($object) && !method_exists($object, $slotInformation['method'])) { throw new Exception\InvalidSlotException('The slot method ' . get_class($object) . '->' . $slotInformation['method'] . '() does not exist.', 1245673368); } + $callable = [$object, $slotInformation['method']]; + if (!is_callable($callable)) { + throw new \Exception('Invalid slot method ' . $slotInformation['method'], 1743955562); + } + if ($slotInformation['useSignalInformationObject'] === true) { - call_user_func([$object, $slotInformation['method']], new SignalInformation($signalClassName, $signalName, $finalSignalArguments)); + call_user_func($callable, new SignalInformation($signalClassName, $signalName, $finalSignalArguments)); } else { if ($slotInformation['passSignalInformation'] === true) { $finalSignalArguments[] = $signalClassName . '::' . $signalName; } // Need to use call_user_func_array here, because $object may be the class name when the slot is a static method - call_user_func_array([$object, $slotInformation['method']], $finalSignalArguments); + call_user_func_array($callable, $finalSignalArguments); } } } @@ -191,9 +208,15 @@ public function dispatch(string $signalClassName, string $signalName, array $sig /** * Returns all slots which are connected with the given signal * - * @param string $signalClassName Name of the class containing the signal + * @param class-string $signalClassName Name of the class containing the signal * @param string $signalName Name of the signal - * @return array An array of arrays with slot information + * @return array An array of arrays with slot information * @api */ public function getSlots(string $signalClassName, string $signalName): array @@ -204,7 +227,13 @@ public function getSlots(string $signalClassName, string $signalName): array /** * Returns all signals with its slots * - * @return array An array of arrays with slot information + * @return array>> An array of arrays with slot information * @api */ public function getSignals(): array diff --git a/Neos.Flow/Classes/SignalSlot/SignalInformation.php b/Neos.Flow/Classes/SignalSlot/SignalInformation.php index 650b133a76..3ad58347bc 100644 --- a/Neos.Flow/Classes/SignalSlot/SignalInformation.php +++ b/Neos.Flow/Classes/SignalSlot/SignalInformation.php @@ -35,10 +35,13 @@ final class SignalInformation protected $signalName; /** - * @var array + * @var array */ protected $signalArguments; + /** + * @param array $signalArguments + */ public function __construct(string $signalClassName, string $signalName, array $signalArguments) { $this->signalClassName = $signalClassName; @@ -56,6 +59,9 @@ public function getSignalName(): string return $this->signalName; } + /** + * @return array + */ public function getSignalArguments(): array { return $this->signalArguments; diff --git a/Neos.Flow/Classes/Utility/Algorithms.php b/Neos.Flow/Classes/Utility/Algorithms.php index 9991b4cfe4..fb46608466 100644 --- a/Neos.Flow/Classes/Utility/Algorithms.php +++ b/Neos.Flow/Classes/Utility/Algorithms.php @@ -36,7 +36,7 @@ class Algorithms */ public static function generateUUID(): string { - if (is_callable('uuid_create')) { + if (function_exists('uuid_create')) { return strtolower(uuid_create(UUID_TYPE_RANDOM)); } diff --git a/Neos.Flow/Classes/Utility/Environment.php b/Neos.Flow/Classes/Utility/Environment.php index 44787d3f87..28845bb93f 100644 --- a/Neos.Flow/Classes/Utility/Environment.php +++ b/Neos.Flow/Classes/Utility/Environment.php @@ -140,18 +140,15 @@ public static function composeTemporaryDirectoryName(string $temporaryDirectoryB * * @param string $temporaryDirectoryBase Full path to the base for the temporary directory * @return string The full path to the temporary directory - * @throws UtilityException if the temporary directory could not be created or is not writable + * @throws FilesException if the temporary directory could not be created + * @throws UtilityException if the temporary directory is not writable */ protected function createTemporaryDirectory(string $temporaryDirectoryBase): string { $temporaryDirectory = self::composeTemporaryDirectoryName($temporaryDirectoryBase, $this->context); if (!is_dir($temporaryDirectory) && !is_link($temporaryDirectory)) { - try { - Files::createDirectoryRecursively($temporaryDirectory); - } catch (ErrorException $exception) { - throw new UtilityException('The temporary directory "' . $temporaryDirectory . '" could not be created. Please make sure permissions are correct for this path or define another temporary directory in your Settings.yaml with the path "Neos.Flow.utility.environment.temporaryDirectoryBase".', 1335382361); - } + Files::createDirectoryRecursively($temporaryDirectory); } if (!is_writable($temporaryDirectory)) { diff --git a/Neos.Flow/Classes/Validation/Validator/AbstractCompositeValidator.php b/Neos.Flow/Classes/Validation/Validator/AbstractCompositeValidator.php index 4803782fa4..dba8d72acb 100644 --- a/Neos.Flow/Classes/Validation/Validator/AbstractCompositeValidator.php +++ b/Neos.Flow/Classes/Validation/Validator/AbstractCompositeValidator.php @@ -24,29 +24,29 @@ abstract class AbstractCompositeValidator implements ObjectValidatorInterface, \ /** * This contains the supported options, their default values and descriptions. * - * @var array + * @var array */ protected $supportedOptions = []; /** - * @var array + * @var array */ protected $options = []; /** - * @var \SplObjectStorage + * @var \SplObjectStorage */ protected $validators; /** - * @var \SplObjectStorage + * @var \SplObjectStorage */ protected $validatedInstancesContainer; /** * Constructs the composite validator and sets validation options * - * @param array $options Options for the validator + * @param array $options Options for the validator * @api * @throws InvalidValidationOptionsException */ @@ -84,7 +84,7 @@ function ($value) { /** * Allows to set a container to keep track of validated instances. * - * @param \SplObjectStorage $validatedInstancesContainer A container to keep track of validated instances + * @param \SplObjectStorage $validatedInstancesContainer A container to keep track of validated instances * @return void * @api */ @@ -102,7 +102,7 @@ public function setValidatedInstancesContainer(\SplObjectStorage $validatedInsta */ public function addValidator(ValidatorInterface $validator) { - if ($validator instanceof ObjectValidatorInterface && isset($this->validatedInstancesContainer)) { + if ($validator instanceof ObjectValidatorInterface) { $validator->setValidatedInstancesContainer($this->validatedInstancesContainer); } $this->validators->attach($validator); @@ -115,7 +115,7 @@ public function addValidator(ValidatorInterface $validator) * @throws NoSuchValidatorException * @api */ - public function removeValidator(ValidatorInterface $validator) + public function removeValidator(ValidatorInterface $validator): void { if (!$this->validators->contains($validator)) { throw new NoSuchValidatorException('Cannot remove validator because its not in the conjunction.', 1207020177); @@ -137,7 +137,7 @@ public function count(): int /** * Returns the child validators of this Composite Validator * - * @return \SplObjectStorage + * @return \SplObjectStorage */ public function getValidators() { @@ -147,7 +147,7 @@ public function getValidators() /** * Returns the options for this validator * - * @return array + * @return array */ public function getOptions() { diff --git a/Neos.Flow/Classes/Validation/Validator/AbstractValidator.php b/Neos.Flow/Classes/Validation/Validator/AbstractValidator.php index 7a11ff35e9..9f91868935 100644 --- a/Neos.Flow/Classes/Validation/Validator/AbstractValidator.php +++ b/Neos.Flow/Classes/Validation/Validator/AbstractValidator.php @@ -41,17 +41,17 @@ abstract class AbstractValidator implements ValidatorInterface * 2 => type * 3 => required (boolean, optional) * - * @var array + * @var array */ protected $supportedOptions = []; /** - * @var array + * @var array */ protected $options = []; /** - * @var ErrorResult + * @var ErrorResult|null */ private $result; @@ -63,7 +63,7 @@ abstract class AbstractValidator implements ValidatorInterface /** * Constructs the validator and sets validation options * - * @param array $options Options for the validator + * @param array $options Options for the validator * @throws InvalidValidationOptionsException if unsupported options are found * @api */ @@ -117,7 +117,7 @@ protected function pushResult() /** * Pop and return the current Result from the stack and make $this->result point to the last Result again. * @since Flow 4.3 - * @return ErrorResult + * @return ErrorResult|null */ protected function popResult() { @@ -143,7 +143,7 @@ protected function getResult() * the Error Messages object which occurred. * * @param mixed $value The value that should be validated - * @return ErrorResult + * @return ErrorResult|null * @throws InvalidValidationOptionsException * @api */ @@ -171,19 +171,22 @@ abstract protected function isValid($value); * * @param string $message The error message * @param integer $code The error code (a unix timestamp) - * @param array $arguments Arguments to be replaced in message + * @param array $arguments Arguments to be replaced in message * @return void * @api */ protected function addError($message, $code, array $arguments = []) { + if ($this->result === null) { + $this->result = new ErrorResult(); + } $this->result->addError(new ValidationError($message, $code, $arguments)); } /** * Returns the options of this validator * - * @return array + * @return array */ public function getOptions() { diff --git a/Neos.Flow/Classes/Validation/Validator/BooleanValueValidator.php b/Neos.Flow/Classes/Validation/Validator/BooleanValueValidator.php index be9ac2833f..c0d773440c 100644 --- a/Neos.Flow/Classes/Validation/Validator/BooleanValueValidator.php +++ b/Neos.Flow/Classes/Validation/Validator/BooleanValueValidator.php @@ -22,7 +22,7 @@ class BooleanValueValidator extends AbstractValidator { /** - * @var array + * @var array */ protected $supportedOptions = [ 'expectedValue' => [true, 'The expected boolean value', 'boolean'] diff --git a/Neos.Flow/Classes/Validation/Validator/CollectionValidator.php b/Neos.Flow/Classes/Validation/Validator/CollectionValidator.php index e02bf51efb..85376bc5ca 100644 --- a/Neos.Flow/Classes/Validation/Validator/CollectionValidator.php +++ b/Neos.Flow/Classes/Validation/Validator/CollectionValidator.php @@ -22,7 +22,7 @@ class CollectionValidator extends GenericObjectValidator { /** - * @var array + * @var array */ protected $supportedOptions = [ 'elementValidator' => [null, 'The validator type to use for the collection elements', 'string'], @@ -52,7 +52,7 @@ protected function isValid($value) { if ($value instanceof \Doctrine\Common\Collections\AbstractLazyCollection && !$value->isInitialized()) { return; - } elseif ((is_object($value) && !TypeHandling::isCollectionType(get_class($value))) && !is_array($value)) { + } elseif (is_object($value) && !TypeHandling::isCollectionType(get_class($value))) { $this->addError('The given subject was not a collection.', 1317204797); return; } elseif (is_object($value) && $this->isValidatedAlready($value)) { @@ -75,7 +75,9 @@ protected function isValid($value) $collectionElementValidator->setValidatedInstancesContainer($this->validatedInstancesContainer); } - $this->getResult()->forProperty($index)->merge($collectionElementValidator->validate($collectionElement)); + if ($this->getResult() && $collectionElementValidator) { + $this->getResult()->forProperty($index)->merge($collectionElementValidator->validate($collectionElement)); + } } } } diff --git a/Neos.Flow/Classes/Validation/Validator/ConjunctionValidator.php b/Neos.Flow/Classes/Validation/Validator/ConjunctionValidator.php index 1dc9ff520b..c2bbd67562 100644 --- a/Neos.Flow/Classes/Validation/Validator/ConjunctionValidator.php +++ b/Neos.Flow/Classes/Validation/Validator/ConjunctionValidator.php @@ -25,7 +25,7 @@ class ConjunctionValidator extends AbstractCompositeValidator * Every validator has to be valid, to make the whole conjunction valid. * * @param mixed $value The value that should be validated - * @return ErrorResult + * @return ErrorResult|null * @api */ public function validate($value) diff --git a/Neos.Flow/Classes/Validation/Validator/CountValidator.php b/Neos.Flow/Classes/Validation/Validator/CountValidator.php index 97a2b8cc7d..9e80afb73e 100644 --- a/Neos.Flow/Classes/Validation/Validator/CountValidator.php +++ b/Neos.Flow/Classes/Validation/Validator/CountValidator.php @@ -20,7 +20,7 @@ class CountValidator extends AbstractValidator { /** - * @var array + * @var array */ protected $supportedOptions = [ 'minimum' => [0, 'The minimum count to accept', 'integer'], diff --git a/Neos.Flow/Classes/Validation/Validator/DateTimeRangeValidator.php b/Neos.Flow/Classes/Validation/Validator/DateTimeRangeValidator.php index cf5266d104..1e9a2fece4 100644 --- a/Neos.Flow/Classes/Validation/Validator/DateTimeRangeValidator.php +++ b/Neos.Flow/Classes/Validation/Validator/DateTimeRangeValidator.php @@ -21,7 +21,7 @@ class DateTimeRangeValidator extends AbstractValidator { /** - * @var array + * @var array */ protected $supportedOptions = [ 'latestDate' => [null, 'The latest date to accept', 'string'], diff --git a/Neos.Flow/Classes/Validation/Validator/DateTimeValidator.php b/Neos.Flow/Classes/Validation/Validator/DateTimeValidator.php index db14ba9293..ebd10f2c2d 100644 --- a/Neos.Flow/Classes/Validation/Validator/DateTimeValidator.php +++ b/Neos.Flow/Classes/Validation/Validator/DateTimeValidator.php @@ -23,7 +23,7 @@ class DateTimeValidator extends AbstractValidator { /** - * @var array + * @var array */ protected $supportedOptions = [ 'locale' => [null, 'The locale to use for date parsing', 'string|Locale'], diff --git a/Neos.Flow/Classes/Validation/Validator/DisjunctionValidator.php b/Neos.Flow/Classes/Validation/Validator/DisjunctionValidator.php index 605a2c69c5..6373ac2c47 100644 --- a/Neos.Flow/Classes/Validation/Validator/DisjunctionValidator.php +++ b/Neos.Flow/Classes/Validation/Validator/DisjunctionValidator.php @@ -28,7 +28,7 @@ class DisjunctionValidator extends AbstractCompositeValidator * Errors are only returned if all validators failed. * * @param mixed $value The value that should be validated - * @return ErrorResult + * @return ErrorResult|null * @api */ public function validate($value) diff --git a/Neos.Flow/Classes/Validation/Validator/EmailAddressValidator.php b/Neos.Flow/Classes/Validation/Validator/EmailAddressValidator.php index bf9609d4cb..2d814e1bc8 100644 --- a/Neos.Flow/Classes/Validation/Validator/EmailAddressValidator.php +++ b/Neos.Flow/Classes/Validation/Validator/EmailAddressValidator.php @@ -27,7 +27,7 @@ class EmailAddressValidator extends AbstractValidator { /** - * @var array + * @var array */ protected $supportedOptions = [ 'strict' => [false, 'Whether to fail validation on RFC warnings', 'bool'], diff --git a/Neos.Flow/Classes/Validation/Validator/FileExtensionValidator.php b/Neos.Flow/Classes/Validation/Validator/FileExtensionValidator.php index c3534f7ff4..fd72ac0bb5 100644 --- a/Neos.Flow/Classes/Validation/Validator/FileExtensionValidator.php +++ b/Neos.Flow/Classes/Validation/Validator/FileExtensionValidator.php @@ -31,7 +31,7 @@ class FileExtensionValidator extends AbstractValidator * 2 => type * 3 => required (boolean, optional) * - * @var array + * @var array */ protected $supportedOptions = [ 'allowedExtensions' => [[], 'Array of allowed file extensions', 'array', true] @@ -58,9 +58,9 @@ protected function isValid($value) return; } - $fileExtension = pathinfo((string)$filename, PATHINFO_EXTENSION); + $fileExtension = pathinfo((string)$filename, PATHINFO_EXTENSION); - if ($fileExtension === null || $fileExtension === '') { + if ($fileExtension === '') { $this->addError('The file has no file extension.', 1677934932); return; } diff --git a/Neos.Flow/Classes/Validation/Validator/FileSizeValidator.php b/Neos.Flow/Classes/Validation/Validator/FileSizeValidator.php index 13283f7624..7507d821ad 100644 --- a/Neos.Flow/Classes/Validation/Validator/FileSizeValidator.php +++ b/Neos.Flow/Classes/Validation/Validator/FileSizeValidator.php @@ -31,7 +31,7 @@ class FileSizeValidator extends AbstractValidator * 2 => type * 3 => required (boolean, optional) * - * @var array + * @var array */ protected $supportedOptions = [ 'minimum' => [null, 'Minimum allowed filesize in bytes', 'integer', false], diff --git a/Neos.Flow/Classes/Validation/Validator/GenericObjectValidator.php b/Neos.Flow/Classes/Validation/Validator/GenericObjectValidator.php index e211fc144b..bd38927e04 100644 --- a/Neos.Flow/Classes/Validation/Validator/GenericObjectValidator.php +++ b/Neos.Flow/Classes/Validation/Validator/GenericObjectValidator.php @@ -23,26 +23,26 @@ class GenericObjectValidator extends AbstractValidator implements ObjectValidatorInterface { /** - * @var array + * @var array */ protected $supportedOptions = [ 'skipUnInitializedProxies' => [false, 'Whether proxies not yet initialized should be skipped during validation', 'boolean'] ]; /** - * @var array + * @var array> */ protected $propertyValidators = []; /** - * @var \SplObjectStorage + * @var \SplObjectStorage */ protected $validatedInstancesContainer; /** * Allows to set a container to keep track of validated instances. * - * @param \SplObjectStorage $validatedInstancesContainer A container to keep track of validated instances + * @param \SplObjectStorage $validatedInstancesContainer A container to keep track of validated instances * @return void * @api */ @@ -77,16 +77,17 @@ protected function isValid($object) $propertyValue = $this->getPropertyValue($object, $propertyName); $result = $this->checkProperty($propertyValue, $validators); if ($result !== null) { - $this->getResult()->forProperty($propertyName)->merge($result); + if ($this->getResult()) { + $this->getResult()->forProperty($propertyName)->merge($result); + } } } } /** - * @param $object * @return boolean */ - protected function isUninitializedProxy($object) + protected function isUninitializedProxy(object $object) { return ($object instanceof DoctrineProxy && $object->__isInitialized() === false); } @@ -132,7 +133,7 @@ protected function getPropertyValue($object, $propertyName) * found errors to the $messages object. * * @param mixed $value The value to be validated - * @param array $validators The validators to be called on the value + * @param array|\SplObjectStorage $validators The validators to be called on the value * @return NULL|ErrorResult */ protected function checkProperty($value, $validators) @@ -162,10 +163,12 @@ protected function checkProperty($value, $validators) * @return void * @api */ - public function addPropertyValidator($propertyName, ValidatorInterface $validator) + public function addPropertyValidator(string $propertyName, ValidatorInterface $validator) { if (!isset($this->propertyValidators[$propertyName])) { - $this->propertyValidators[$propertyName] = new \SplObjectStorage(); + /** @var \SplObjectStorage $storage */ + $storage = new \SplObjectStorage(); + $this->propertyValidators[$propertyName] = $storage; } $this->propertyValidators[$propertyName]->attach($validator); } @@ -173,13 +176,18 @@ public function addPropertyValidator($propertyName, ValidatorInterface $validato /** * Returns all property validators - or only validators of the specified property * - * @param string $propertyName Name of the property to return validators for - * @return array An array of validators + * @param ?string $propertyName Name of the property to return validators for + * @return ($propertyName is null ? array> : \SplObjectStorage) */ - public function getPropertyValidators($propertyName = null) + public function getPropertyValidators(?string $propertyName = null) { if ($propertyName !== null) { - return $this->propertyValidators[$propertyName] ?? []; + $propertyValidators = $this->propertyValidators[$propertyName] ?? null; + if (!$propertyValidators) { + /** @var \SplObjectStorage $propertyValidators */ + $propertyValidators = new \SplObjectStorage(); + } + return $propertyValidators; } return $this->propertyValidators; } diff --git a/Neos.Flow/Classes/Validation/Validator/MediaTypeValidator.php b/Neos.Flow/Classes/Validation/Validator/MediaTypeValidator.php index 5d0c7b3444..bc519d58c1 100644 --- a/Neos.Flow/Classes/Validation/Validator/MediaTypeValidator.php +++ b/Neos.Flow/Classes/Validation/Validator/MediaTypeValidator.php @@ -32,7 +32,7 @@ class MediaTypeValidator extends AbstractValidator * 2 => type * 3 => required (boolean, optional) * - * @var array + * @var array */ protected $supportedOptions = [ 'allowedTypes' => [[], 'Array of allowed media ranges', 'array', true], diff --git a/Neos.Flow/Classes/Validation/Validator/NumberRangeValidator.php b/Neos.Flow/Classes/Validation/Validator/NumberRangeValidator.php index ba6c31be92..07a1db9858 100644 --- a/Neos.Flow/Classes/Validation/Validator/NumberRangeValidator.php +++ b/Neos.Flow/Classes/Validation/Validator/NumberRangeValidator.php @@ -20,7 +20,7 @@ class NumberRangeValidator extends AbstractValidator { /** - * @var array + * @var array */ protected $supportedOptions = [ 'minimum' => [0, 'The minimum value to accept', 'integer'], diff --git a/Neos.Flow/Classes/Validation/Validator/NumberValidator.php b/Neos.Flow/Classes/Validation/Validator/NumberValidator.php index dd1a7db346..754a497cf9 100644 --- a/Neos.Flow/Classes/Validation/Validator/NumberValidator.php +++ b/Neos.Flow/Classes/Validation/Validator/NumberValidator.php @@ -23,7 +23,7 @@ class NumberValidator extends AbstractValidator { /** - * @var array + * @var array */ protected $supportedOptions = [ 'locale' => [null, 'The locale to use for number parsing', 'string|Locale'], diff --git a/Neos.Flow/Classes/Validation/Validator/ObjectValidatorInterface.php b/Neos.Flow/Classes/Validation/Validator/ObjectValidatorInterface.php index 43f27b1a29..e1c3fa30d8 100644 --- a/Neos.Flow/Classes/Validation/Validator/ObjectValidatorInterface.php +++ b/Neos.Flow/Classes/Validation/Validator/ObjectValidatorInterface.php @@ -21,7 +21,7 @@ interface ObjectValidatorInterface extends ValidatorInterface /** * Allows to set a container to keep track of validated instances. * - * @param \SplObjectStorage $validatedInstancesContainer A container to keep track of validated instances + * @param \SplObjectStorage $validatedInstancesContainer A container to keep track of validated instances * @return void * @api */ diff --git a/Neos.Flow/Classes/Validation/Validator/RegularExpressionValidator.php b/Neos.Flow/Classes/Validation/Validator/RegularExpressionValidator.php index b2e7ad387b..18cbf52301 100644 --- a/Neos.Flow/Classes/Validation/Validator/RegularExpressionValidator.php +++ b/Neos.Flow/Classes/Validation/Validator/RegularExpressionValidator.php @@ -21,7 +21,7 @@ class RegularExpressionValidator extends AbstractValidator { /** - * @var array + * @var array */ protected $supportedOptions = [ 'regularExpression' => ['', 'The regular expression to use for validation, used as given', 'string', true] diff --git a/Neos.Flow/Classes/Validation/Validator/StringLengthValidator.php b/Neos.Flow/Classes/Validation/Validator/StringLengthValidator.php index a48ecd7f19..e99f359400 100644 --- a/Neos.Flow/Classes/Validation/Validator/StringLengthValidator.php +++ b/Neos.Flow/Classes/Validation/Validator/StringLengthValidator.php @@ -22,7 +22,7 @@ class StringLengthValidator extends AbstractValidator { /** - * @var array + * @var array */ protected $supportedOptions = [ 'minimum' => [0, 'Minimum length for a valid string', 'integer'], diff --git a/Neos.Flow/Classes/Validation/Validator/UniqueEntityValidator.php b/Neos.Flow/Classes/Validation/Validator/UniqueEntityValidator.php index 83fcb69dd2..ed12067304 100644 --- a/Neos.Flow/Classes/Validation/Validator/UniqueEntityValidator.php +++ b/Neos.Flow/Classes/Validation/Validator/UniqueEntityValidator.php @@ -39,7 +39,7 @@ class UniqueEntityValidator extends AbstractValidator protected $persistenceManager; /** - * @var array + * @var array */ protected $supportedOptions = [ 'identityProperties' => [null, 'List of custom identity properties.', 'array'] @@ -60,7 +60,7 @@ protected function isValid($value) throw new InvalidValidationOptionsException('The value supplied for the UniqueEntityValidator must be an object.', 1358454270); } - $classSchema = $this->reflectionService->getClassSchema(TypeHandling::getTypeForValue($value)); + $classSchema = $this->reflectionService->getClassSchema($value); if ($classSchema === null || $classSchema->getModelType() !== ClassSchema::MODELTYPE_ENTITY) { throw new InvalidValidationOptionsException('The object supplied for the UniqueEntityValidator must be an entity.', 1358454284); } diff --git a/Neos.Flow/Classes/Validation/Validator/ValidatorInterface.php b/Neos.Flow/Classes/Validation/Validator/ValidatorInterface.php index b142149381..55cd6c4228 100644 --- a/Neos.Flow/Classes/Validation/Validator/ValidatorInterface.php +++ b/Neos.Flow/Classes/Validation/Validator/ValidatorInterface.php @@ -41,7 +41,7 @@ public function validate($value); /** * Returns the options of this validator which can be specified in the constructor * - * @return array + * @return array */ public function getOptions(); } diff --git a/Neos.Flow/Classes/Validation/ValidatorResolver.php b/Neos.Flow/Classes/Validation/ValidatorResolver.php index 3400c85715..11a72dffae 100644 --- a/Neos.Flow/Classes/Validation/ValidatorResolver.php +++ b/Neos.Flow/Classes/Validation/ValidatorResolver.php @@ -77,7 +77,7 @@ class ValidatorResolver protected $reflectionService; /** - * @var array + * @var array */ protected $baseValidatorConjunctions = []; @@ -87,7 +87,7 @@ class ValidatorResolver * could be resolved. * * @param string $validatorType Either one of the built-in data types or fully qualified validator class name - * @param array $validatorOptions Options to be passed to the validator + * @param array $validatorOptions Options to be passed to the validator * @return ValidatorInterface|null * @throws Exception\NoSuchValidatorException * @throws Exception\InvalidValidationConfigurationException @@ -114,10 +114,6 @@ public function createValidator($validatorType, array $validatorOptions = []) throw new Exception\NoSuchValidatorException('The validator "' . $validatorObjectName . '" is not of scope singleton or prototype!', 1300694835); } - if (!($validator instanceof ValidatorInterface)) { - throw new Exception\NoSuchValidatorException(sprintf('The validator "%s" does not implement %s!', $validatorObjectName, ValidatorInterface::class), 1300694875); - } - return $validator; } @@ -127,7 +123,7 @@ public function createValidator($validatorType, array $validatorOptions = []) * If no validation is necessary, the returned validator is empty. * * @param string $targetClassName Fully qualified class name of the target class, ie. the class which should be validated - * @param array $validationGroups The validation groups to build the validator for + * @param array $validationGroups The validation groups to build the validator for * @return ConjunctionValidator The validator conjunction * @throws Exception\InvalidValidationConfigurationException * @throws Exception\InvalidValidationOptionsException @@ -150,10 +146,10 @@ public function getBaseValidatorConjunction($targetClassName, array $validationG * - by the data type specified in the param annotations * - additional validators specified in the validate annotations of a method * - * @param string $className + * @param class-string $className * @param string $methodName - * @param array $methodParameters Optional pre-compiled array of method parameters - * @param array $methodValidateAnnotations Optional pre-compiled array of validate annotations (as array) + * @param array|null $methodParameters Optional pre-compiled array of method parameters + * @param array, argumentName: string}>|null $methodValidateAnnotations Optional pre-compiled array of validate annotations (as array) * @return array An Array of ValidatorConjunctions for each method parameters. * @throws Exception\InvalidValidationConfigurationException * @throws Exception\NoSuchValidatorException @@ -202,7 +198,11 @@ public function buildMethodArgumentsValidatorConjunctions($className, $methodNam } foreach ($methodValidateAnnotations as $annotationParameters) { - $newValidator = $this->createValidator($annotationParameters['type'], $annotationParameters['options']); + $validatorType = $annotationParameters['type'] ?? null; + if ($validatorType === null) { + throw new \Exception('Missing validator type', 1743872674); + } + $newValidator = $this->createValidator($validatorType, $annotationParameters['options']); if ($newValidator === null) { throw new Exception\NoSuchValidatorException('Invalid validate annotation in ' . $className . '->' . $methodName . '(): Could not resolve class name for validator "' . $annotationParameters['type'] . '".', 1239853109); } @@ -235,7 +235,7 @@ public function reset() * Builds a chain of nested object validators by specification of the given * object path. * - * @param array $objectPath The object path + * @param array $objectPath The object path * @param ValidatorInterface $propertyValidator The validator which should be added to the property specified by objectPath * @return GenericObjectValidator * @throws Exception\InvalidValidationOptionsException @@ -252,7 +252,12 @@ protected function buildSubObjectValidator(array $objectPath, ValidatorInterface $parentObjectValidator = $subObjectValidator; } - $parentObjectValidator->addPropertyValidator(array_shift($objectPath), $propertyValidator); + $propertyName = array_shift($objectPath); + if (!is_string($propertyName)) { + throw new \Exception('Missing propertyName', 1743872593); + } + + $parentObjectValidator->addPropertyValidator($propertyName, $propertyValidator); return $rootObjectValidator; } @@ -275,7 +280,7 @@ protected function buildSubObjectValidator(array $objectPath, ValidatorInterface * * @param string $indexKey The key to use as index in $this->baseValidatorConjunctions; calculated from target class name and validation groups * @param string $targetClassName The data type to build the validation conjunction for. Needs to be the fully qualified class name. - * @param array $validationGroups The validation groups to build the validator for + * @param array $validationGroups The validation groups to build the validator for * @return void * @throws Exception\NoSuchValidatorException * @throws \InvalidArgumentException @@ -333,6 +338,9 @@ protected function buildBaseValidatorConjunction($indexKey, $targetClassName, ar $validateAnnotations = $this->reflectionService->getPropertyAnnotations($targetClassName, $classPropertyName, Flow\Validate::class); foreach ($validateAnnotations as $validateAnnotation) { + if ($validateAnnotation->type === null) { + continue; + } if ($validateAnnotation->type === 'Collection') { $needsCollectionValidator = false; $validateAnnotation->options = array_merge(['elementType' => $parsedType['elementType'], 'validationGroups' => $validationGroups], $validateAnnotation->options); @@ -356,7 +364,9 @@ protected function buildBaseValidatorConjunction($indexKey, $targetClassName, ar if ($needsCollectionValidator) { $collectionValidator = $this->createValidator(Validator\CollectionValidator::class, ['elementType' => $parsedType['elementType'], 'validationGroups' => $validationGroups]); - $objectValidator->addPropertyValidator($classPropertyName, $collectionValidator); + if ($collectionValidator instanceof ValidatorInterface) { + $objectValidator->addPropertyValidator($classPropertyName, $collectionValidator); + } } if ($needsObjectValidator) { $validatorForProperty = $this->getBaseValidatorConjunction($propertyTargetClassName, $validationGroups); @@ -424,7 +434,7 @@ protected function addCustomValidators($targetClassName, ConjunctionValidator &$ * Returns a map of object validator class names. * * @param ObjectManagerInterface $objectManager - * @return array Array of object validator class names + * @return list> Array of object validator class names * @Flow\CompileStatic */ public static function getPolyTypeObjectValidatorImplementationClassNames($objectManager) @@ -439,7 +449,7 @@ public static function getPolyTypeObjectValidatorImplementationClassNames($objec * validator is available false is returned * * @param string $validatorType Either the fully qualified class name of the validator or the short name of a built-in validator - * @return string|false Class name of the validator or false if not available + * @return class-string|false Class name of the validator or false if not available */ protected function resolveValidatorObjectName($validatorType) { @@ -448,6 +458,7 @@ protected function resolveValidatorObjectName($validatorType) $validatorClassNames = static::getValidatorImplementationClassNames($this->objectManager); if ($this->objectManager->isRegistered($validatorType) && isset($validatorClassNames[$validatorType])) { + /** @var class-string $validatorType */ return $validatorType; } @@ -458,6 +469,7 @@ protected function resolveValidatorObjectName($validatorType) $possibleClassName = sprintf('Neos\Flow\Validation\Validator\%sValidator', $this->getValidatorType($validatorType)); } if ($this->objectManager->isRegistered($possibleClassName) && isset($validatorClassNames[$possibleClassName])) { + /** @var class-string $possibleClassName */ return $possibleClassName; } @@ -468,7 +480,7 @@ protected function resolveValidatorObjectName($validatorType) * Returns all class names implementing the ValidatorInterface. * * @param ObjectManagerInterface $objectManager - * @return array Array of class names implementing ValidatorInterface indexed by class name + * @return array,int> Array of class names implementing ValidatorInterface indexed by class name * @Flow\CompileStatic */ public static function getValidatorImplementationClassNames($objectManager) diff --git a/Neos.FluidAdaptor/Classes/Core/Cache/CacheAdaptor.php b/Neos.FluidAdaptor/Classes/Core/Cache/CacheAdaptor.php index 43bb16e5a4..a80d47190b 100644 --- a/Neos.FluidAdaptor/Classes/Core/Cache/CacheAdaptor.php +++ b/Neos.FluidAdaptor/Classes/Core/Cache/CacheAdaptor.php @@ -50,7 +50,7 @@ public function get($name) * @param string $name * @param string $value */ - public function set($name, $value) + public function set($name, $value): void { // we need to strip the first line with the php header as the flow cache adds that again. $this->flowCache->set($name, substr($value, strpos($value, "\n") + 1)); diff --git a/Neos.FluidAdaptor/Classes/Core/Parser/Interceptor/ResourceInterceptor.php b/Neos.FluidAdaptor/Classes/Core/Parser/Interceptor/ResourceInterceptor.php index 286870a6ac..9334df24ad 100644 --- a/Neos.FluidAdaptor/Classes/Core/Parser/Interceptor/ResourceInterceptor.php +++ b/Neos.FluidAdaptor/Classes/Core/Parser/Interceptor/ResourceInterceptor.php @@ -97,7 +97,7 @@ public function process(NodeInterface $node, $interceptorPosition, ParsingState if (strpos($node->getText(), 'Public/') === false) { return $node; } - $textParts = preg_split(self::PATTERN_SPLIT_AT_RESOURCE_URIS, $node->getText(), -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); + $textParts = preg_split(self::PATTERN_SPLIT_AT_RESOURCE_URIS, $node->getText(), -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) ?: []; $node = new RootNode(); foreach ($textParts as $part) { $matches = []; @@ -109,7 +109,7 @@ public function process(NodeInterface $node, $interceptorPosition, ParsingState if ($this->defaultPackageKey !== null) { $arguments['package'] = new TextNode($this->defaultPackageKey); } - if (isset($matches['Package']) && FlowPackageKey::isPackageKeyValid($matches['Package'])) { + if (FlowPackageKey::isPackageKeyValid($matches['Package'])) { $arguments['package'] = new TextNode($matches['Package']); } @@ -127,7 +127,7 @@ public function process(NodeInterface $node, $interceptorPosition, ParsingState /** * This interceptor wants to hook into text nodes. * - * @return array Array of INTERCEPT_* constants + * @return array Array of INTERCEPT_* constants */ public function getInterceptionPoints() { diff --git a/Neos.FluidAdaptor/Classes/Core/Parser/SyntaxTree/Expression/LegacyNamespaceExpressionNode.php b/Neos.FluidAdaptor/Classes/Core/Parser/SyntaxTree/Expression/LegacyNamespaceExpressionNode.php index 1af2886355..cd63f4f625 100644 --- a/Neos.FluidAdaptor/Classes/Core/Parser/SyntaxTree/Expression/LegacyNamespaceExpressionNode.php +++ b/Neos.FluidAdaptor/Classes/Core/Parser/SyntaxTree/Expression/LegacyNamespaceExpressionNode.php @@ -24,14 +24,14 @@ class LegacyNamespaceExpressionNode extends AbstractExpressionNode implements Ex * Pattern which detects namespace declarations made inline. * syntax, e.g. {namespace neos=TYPO3\Neos\ViewHelpers}. */ - public static $detectionExpression = '/{namespace\\s*([a-z0-9]+)\\s*=\\s*([a-z0-9_\\\\]+)\\s*}/i'; + public static string $detectionExpression = '/{namespace\\s*([a-z0-9]+)\\s*=\\s*([a-z0-9_\\\\]+)\\s*}/i'; /** * Evaluates the expression stored in this node, in the context of $renderingcontext. * * @param RenderingContextInterface $renderingContext * @param string $expression - * @param array $matches + * @param array $matches * @return mixed */ public static function evaluateExpression(RenderingContextInterface $renderingContext, $expression, array $matches) diff --git a/Neos.FluidAdaptor/Classes/Core/Parser/SyntaxTree/ResourceUriNode.php b/Neos.FluidAdaptor/Classes/Core/Parser/SyntaxTree/ResourceUriNode.php index db44103330..81301ef0d4 100644 --- a/Neos.FluidAdaptor/Classes/Core/Parser/SyntaxTree/ResourceUriNode.php +++ b/Neos.FluidAdaptor/Classes/Core/Parser/SyntaxTree/ResourceUriNode.php @@ -35,18 +35,17 @@ class ResourceUriNode extends ViewHelperNode protected $viewHelperResolver; /** - * @var string + * @var class-string */ protected $viewHelperClassName = ResourceViewHelper::class; /** * @param ViewHelperResolver $viewHelperResolver */ - public function injectViewHelperResolver(ViewHelperResolver $viewHelperResolver) + public function injectViewHelperResolver(ViewHelperResolver $viewHelperResolver): void { $this->viewHelperResolver = $viewHelperResolver; $this->uninitializedViewHelper = $this->viewHelperResolver->createViewHelperInstanceFromClassName($this->viewHelperClassName); - /** @phpstan-ignore-next-line we use internal api */ $this->uninitializedViewHelper->setViewHelperNode($this); $this->argumentDefinitions = $this->viewHelperResolver->getArgumentDefinitionsForViewHelper($this->uninitializedViewHelper); } diff --git a/Neos.FluidAdaptor/Classes/Core/Parser/TemplateParser.php b/Neos.FluidAdaptor/Classes/Core/Parser/TemplateParser.php index 1ad1054885..db67139b41 100644 --- a/Neos.FluidAdaptor/Classes/Core/Parser/TemplateParser.php +++ b/Neos.FluidAdaptor/Classes/Core/Parser/TemplateParser.php @@ -17,7 +17,7 @@ public function isEscapingEnabled() /** * @param boolean $escapingEnabled */ - public function setEscapingEnabled($escapingEnabled) + public function setEscapingEnabled($escapingEnabled): void { $this->escapingEnabled = $escapingEnabled; } diff --git a/Neos.FluidAdaptor/Classes/Core/Parser/TemplateProcessor/EscapingFlagProcessor.php b/Neos.FluidAdaptor/Classes/Core/Parser/TemplateProcessor/EscapingFlagProcessor.php index fbdc9a72a1..1b61da951e 100644 --- a/Neos.FluidAdaptor/Classes/Core/Parser/TemplateProcessor/EscapingFlagProcessor.php +++ b/Neos.FluidAdaptor/Classes/Core/Parser/TemplateProcessor/EscapingFlagProcessor.php @@ -25,12 +25,12 @@ class EscapingFlagProcessor implements TemplateProcessorInterface */ protected $renderingContext; - public static $SCAN_PATTERN_ESCAPINGMODIFIER = '/{escapingEnabled\s*=\s*(?Ptrue|false)\s*}/i'; + public static string $SCAN_PATTERN_ESCAPINGMODIFIER = '/{escapingEnabled\s*=\s*(?Ptrue|false)\s*}/i'; /** * @param RenderingContextInterface $renderingContext */ - public function setRenderingContext(RenderingContextInterface $renderingContext) + public function setRenderingContext(RenderingContextInterface $renderingContext): void { $this->renderingContext = $renderingContext; } @@ -56,7 +56,7 @@ public function preProcessSource($templateSource) if (strtolower($matches[0]['enabled']) === 'false') { $this->renderingContext->getTemplateParser()->setEscapingEnabled(false); } - $templateSource = preg_replace(self::$SCAN_PATTERN_ESCAPINGMODIFIER, '', $templateSource); + $templateSource = preg_replace(self::$SCAN_PATTERN_ESCAPINGMODIFIER, '', $templateSource) ?: ''; return $templateSource; } diff --git a/Neos.FluidAdaptor/Classes/Core/Parser/TemplateProcessor/NamespaceDetectionTemplateProcessor.php b/Neos.FluidAdaptor/Classes/Core/Parser/TemplateProcessor/NamespaceDetectionTemplateProcessor.php index b6c18f2664..3c813c0c01 100644 --- a/Neos.FluidAdaptor/Classes/Core/Parser/TemplateProcessor/NamespaceDetectionTemplateProcessor.php +++ b/Neos.FluidAdaptor/Classes/Core/Parser/TemplateProcessor/NamespaceDetectionTemplateProcessor.php @@ -29,7 +29,7 @@ class NamespaceDetectionTemplateProcessor extends FluidNamespaceDetectionTemplat /** * Extension of the default pattern for dynamic tags including namespaces with uppercase letters. */ - public static $EXTENDED_SPLIT_PATTERN_TEMPLATE_DYNAMICTAGS = '/ + public static string $EXTENDED_SPLIT_PATTERN_TEMPLATE_DYNAMICTAGS = '/ ( (?: <\/? # Start dynamic tags (?:(?:[a-zA-Z0-9\\.]*):[a-zA-Z0-9\\.]+) # A tag consists of the namespace prefix and word characters @@ -75,7 +75,7 @@ public function preProcessSource($templateSource) */ public function protectCDataSectionsFromParser(string $templateSource) { - $parts = preg_split('/(\<\!\[CDATA\[|\]\]\>)/', $templateSource, -1, PREG_SPLIT_DELIM_CAPTURE); + $parts = preg_split('/(\<\!\[CDATA\[|\]\]\>)/', $templateSource, -1, PREG_SPLIT_DELIM_CAPTURE) ?: []; $balance = 0; $content = ''; $resultingParts = []; @@ -127,7 +127,7 @@ public function protectCDataSectionsFromParser(string $templateSource) public function throwExceptionsForUnhandledNamespaces(string $templateSource): void { $viewHelperResolver = $this->renderingContext->getViewHelperResolver(); - $splitTemplate = preg_split(static::$EXTENDED_SPLIT_PATTERN_TEMPLATE_DYNAMICTAGS, $templateSource, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + $splitTemplate = preg_split(static::$EXTENDED_SPLIT_PATTERN_TEMPLATE_DYNAMICTAGS, $templateSource, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY) ?: []; foreach ($splitTemplate as $templateElement) { if (preg_match(Patterns::$SCAN_PATTERN_TEMPLATE_VIEWHELPERTAG, $templateElement, $matchedVariables) > 0) { if (!$viewHelperResolver->isNamespaceValidOrIgnored($matchedVariables['NamespaceIdentifier'])) { @@ -148,11 +148,9 @@ public function throwExceptionsForUnhandledNamespaces(string $templateSource): v foreach ($sections as $section) { if (preg_match(Patterns::$SCAN_PATTERN_SHORTHANDSYNTAX_OBJECTACCESSORS, $section, $matchedVariables) > 0) { preg_match_all(Patterns::$SPLIT_PATTERN_SHORTHANDSYNTAX_VIEWHELPER, $section, $shorthandViewHelpers, PREG_SET_ORDER); - if (is_array($shorthandViewHelpers) === true) { - foreach ($shorthandViewHelpers as $shorthandViewHelper) { - if (!$viewHelperResolver->isNamespaceValidOrIgnored($shorthandViewHelper['NamespaceIdentifier'])) { - throw new UnknownNamespaceException('Unknown Namespace: ' . $shorthandViewHelper['NamespaceIdentifier']); - } + foreach ($shorthandViewHelpers as $shorthandViewHelper) { + if (!$viewHelperResolver->isNamespaceValidOrIgnored($shorthandViewHelper['NamespaceIdentifier'])) { + throw new UnknownNamespaceException('Unknown Namespace: ' . $shorthandViewHelper['NamespaceIdentifier']); } } } diff --git a/Neos.FluidAdaptor/Classes/Core/Rendering/RenderingContext.php b/Neos.FluidAdaptor/Classes/Core/Rendering/RenderingContext.php index 3c68fb552b..1fb6c223f5 100644 --- a/Neos.FluidAdaptor/Classes/Core/Rendering/RenderingContext.php +++ b/Neos.FluidAdaptor/Classes/Core/Rendering/RenderingContext.php @@ -25,6 +25,7 @@ use Neos\FluidAdaptor\Core\ViewHelper\ViewHelperResolver; use Neos\FluidAdaptor\View\TemplatePaths; use TYPO3Fluid\Fluid\Core\Parser\Configuration; +use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\Expression\AbstractExpressionNode; use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\Expression\CastingExpressionNode; use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\Expression\MathExpressionNode; use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\Expression\TernaryExpressionNode; @@ -42,7 +43,7 @@ class RenderingContext extends FluidRenderingContext implements FlowAwareRenderi * which will be consulted when an expression does not match * any built-in parser expression types. * - * @var array + * @var array> */ protected $expressionNodeTypes = [ LegacyNamespaceExpressionNode::class, @@ -81,7 +82,7 @@ class RenderingContext extends FluidRenderingContext implements FlowAwareRenderi /** * RenderingContext constructor. * - * @param array $options + * @param array $options */ public function __construct(array $options = []) { @@ -100,7 +101,7 @@ public function __construct(array $options = []) /** * @param ObjectManagerInterface $objectManager */ - public function injectObjectManager(ObjectManagerInterface $objectManager) + public function injectObjectManager(ObjectManagerInterface $objectManager): void { $this->objectManager = $objectManager; } @@ -116,11 +117,11 @@ public function getControllerContext() /** * @param ControllerContext $controllerContext */ - public function setControllerContext($controllerContext) + public function setControllerContext($controllerContext): void { $this->controllerContext = $controllerContext; $request = $controllerContext->getRequest(); - if (!$this->templatePaths instanceof TemplatePaths || !$request instanceof ActionRequest) { + if (!$this->templatePaths instanceof TemplatePaths) { return; } diff --git a/Neos.FluidAdaptor/Classes/Core/ViewHelper/AbstractConditionViewHelper.php b/Neos.FluidAdaptor/Classes/Core/ViewHelper/AbstractConditionViewHelper.php index 052ef09e98..60c8b848f9 100644 --- a/Neos.FluidAdaptor/Classes/Core/ViewHelper/AbstractConditionViewHelper.php +++ b/Neos.FluidAdaptor/Classes/Core/ViewHelper/AbstractConditionViewHelper.php @@ -46,7 +46,7 @@ abstract class AbstractConditionViewHelper extends AbstractViewHelper /** * Initializes the "then" and "else" arguments */ - public function initializeArguments() + public function initializeArguments(): void { $this->registerArgument('then', 'mixed', 'Value to be returned if the condition if met.', false); $this->registerArgument('else', 'mixed', 'Value to be returned if the condition if not met.', false); @@ -66,18 +66,19 @@ public function initializeArguments() * subclasses that will be using this base class in the future. Let this * be a warning if someone considers changing this method signature! * - * @param array|NULL $arguments + * @param array|NULL $arguments * @param RenderingContextInterface $renderingContext * @return boolean * @api */ protected static function evaluateCondition($arguments, RenderingContextInterface $renderingContext) { - return (boolean)$arguments['condition']; + // fallback value derived from registerArgument default value + return (boolean)($arguments['condition'] ?? false); } /** - * @param array $arguments + * @param array $arguments * @param \Closure $renderChildrenClosure * @param RenderingContextInterface $renderingContext * @return mixed @@ -103,8 +104,8 @@ public static function renderStatic(array $arguments, \Closure $renderChildrenCl } /** - * @param array $closures - * @param array $conditionClosures + * @param array $closures + * @param array $conditionClosures * @param RenderingContextInterface $renderingContext * @return string */ @@ -236,7 +237,7 @@ public function compile($argumentsName, $closureName, &$initializationPhpCode, V /** * @param boolean $isConditionFullfilled - * @param array $arguments + * @param array $arguments * @param RenderingContextInterface $renderingContext * @return mixed */ diff --git a/Neos.FluidAdaptor/Classes/Core/ViewHelper/AbstractLocaleAwareViewHelper.php b/Neos.FluidAdaptor/Classes/Core/ViewHelper/AbstractLocaleAwareViewHelper.php index 5f33d1e651..436817dfea 100644 --- a/Neos.FluidAdaptor/Classes/Core/ViewHelper/AbstractLocaleAwareViewHelper.php +++ b/Neos.FluidAdaptor/Classes/Core/ViewHelper/AbstractLocaleAwareViewHelper.php @@ -39,7 +39,7 @@ public function __construct() /** * @param I18n\Service $localizationService */ - public function injectLocalizationService(I18n\Service $localizationService) + public function injectLocalizationService(I18n\Service $localizationService): void { $this->localizationService = $localizationService; } diff --git a/Neos.FluidAdaptor/Classes/Core/ViewHelper/AbstractTagBasedViewHelper.php b/Neos.FluidAdaptor/Classes/Core/ViewHelper/AbstractTagBasedViewHelper.php index d4743808a6..2ce96b048d 100644 --- a/Neos.FluidAdaptor/Classes/Core/ViewHelper/AbstractTagBasedViewHelper.php +++ b/Neos.FluidAdaptor/Classes/Core/ViewHelper/AbstractTagBasedViewHelper.php @@ -32,7 +32,7 @@ abstract class AbstractTagBasedViewHelper extends AbstractViewHelper /** * Names of all registered tag attributes * - * @var array + * @var array */ private static $tagAttributes = []; @@ -156,7 +156,7 @@ protected function registerUniversalTagAttributes() * which in the default implementation will throw an error * about "undeclared argument used". * - * @param array $arguments + * @param array|array|null> $arguments * @return void */ public function handleAdditionalArguments(array $arguments) diff --git a/Neos.FluidAdaptor/Classes/Core/ViewHelper/AbstractViewHelper.php b/Neos.FluidAdaptor/Classes/Core/ViewHelper/AbstractViewHelper.php index 6731a63562..e80a9b38de 100644 --- a/Neos.FluidAdaptor/Classes/Core/ViewHelper/AbstractViewHelper.php +++ b/Neos.FluidAdaptor/Classes/Core/ViewHelper/AbstractViewHelper.php @@ -59,9 +59,7 @@ public function setRenderingContext(RenderingContextInterface $renderingContext) $this->renderingContext = $renderingContext; $this->templateVariableContainer = $renderingContext->getVariableProvider(); $this->viewHelperVariableContainer = $renderingContext->getViewHelperVariableContainer(); - if ($renderingContext instanceof FlowAwareRenderingContextInterface) { - $this->controllerContext = $renderingContext->getControllerContext(); - } + $this->controllerContext = $renderingContext->getControllerContext(); } /** diff --git a/Neos.FluidAdaptor/Classes/Core/ViewHelper/ViewHelperResolver.php b/Neos.FluidAdaptor/Classes/Core/ViewHelper/ViewHelperResolver.php index 8351bfa4f1..9282263018 100644 --- a/Neos.FluidAdaptor/Classes/Core/ViewHelper/ViewHelperResolver.php +++ b/Neos.FluidAdaptor/Classes/Core/ViewHelper/ViewHelperResolver.php @@ -15,6 +15,7 @@ use Neos\Flow\ObjectManagement\ObjectManagerInterface; use Neos\Flow\Package\Package; use Neos\Flow\Package\PackageManager; +use TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperInterface; /** * Class ViewHelperResolver @@ -49,17 +50,17 @@ class ViewHelperResolver extends \TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperRes * will look for classes in both namespaces starting * from the bottom. * - * @var array + * @var array */ protected $namespaces = []; /** * @Flow\InjectConfiguration(path="namespaces") - * @var array + * @var array */ protected $namespacesFromConfiguration; - public function initializeObject($reason) + public function initializeObject(int $reason): void { if ($reason === ObjectManagerInterface::INITIALIZATIONCAUSE_RECREATED) { return; @@ -83,8 +84,9 @@ public function initializeObject($reason) } /** - * @param string $viewHelperClassName - * @return \TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperInterface + * @template T of ViewHelperInterface + * @param class-string $viewHelperClassName + * @return T */ public function createViewHelperInstanceFromClassName($viewHelperClassName) { @@ -125,7 +127,7 @@ public function createViewHelperInstanceFromClassName($viewHelperClassName) * when you use this method you should always include the "f" namespace. * * @param string $identifier - * @param string|array $phpNamespace + * @param string|array|null $phpNamespace * @return void */ public function addNamespace($identifier, $phpNamespace) @@ -149,7 +151,7 @@ public function addNamespace($identifier, $phpNamespace) * @param string $identifier * @param string $phpNamespace */ - protected function addNamespaceInternal($identifier, $phpNamespace) + protected function addNamespaceInternal($identifier, $phpNamespace): void { if (!isset($this->namespaces[$identifier])) { $this->namespaces[$identifier] = []; diff --git a/Neos.FluidAdaptor/Classes/Core/Widget/AbstractWidgetController.php b/Neos.FluidAdaptor/Classes/Core/Widget/AbstractWidgetController.php index c7b38b4a80..4c07eab1e2 100644 --- a/Neos.FluidAdaptor/Classes/Core/Widget/AbstractWidgetController.php +++ b/Neos.FluidAdaptor/Classes/Core/Widget/AbstractWidgetController.php @@ -36,7 +36,7 @@ abstract class AbstractWidgetController extends ActionController /** * Configuration for this widget. * - * @var array + * @var array * @api */ protected $widgetConfiguration; @@ -60,7 +60,6 @@ abstract class AbstractWidgetController extends ActionController */ public function processRequest(ActionRequest $request): ResponseInterface { - /** @var WidgetContext $widgetContext */ $widgetContext = $request->getInternalArgument('__widgetContext'); if (!$widgetContext instanceof WidgetContext) { throw new WidgetContextNotFoundException('The widget context could not be found in the request.', 1307450180); diff --git a/Neos.FluidAdaptor/Classes/Core/Widget/AbstractWidgetViewHelper.php b/Neos.FluidAdaptor/Classes/Core/Widget/AbstractWidgetViewHelper.php index e390a32db6..db341c1186 100644 --- a/Neos.FluidAdaptor/Classes/Core/Widget/AbstractWidgetViewHelper.php +++ b/Neos.FluidAdaptor/Classes/Core/Widget/AbstractWidgetViewHelper.php @@ -20,6 +20,7 @@ use Neos\FluidAdaptor\Core\ViewHelper\AbstractViewHelper; use Neos\FluidAdaptor\Core\ViewHelper\Facets\ChildNodeAccessInterface; use TYPO3Fluid\Fluid\Core\Compiler\TemplateCompiler; +use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\NodeInterface; use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\RootNode; use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\ViewHelperNode; @@ -38,7 +39,7 @@ abstract class AbstractWidgetViewHelper extends AbstractViewHelper implements Ch * This needs to be filled by the individual subclass using * property injection. * - * @var AbstractWidgetController + * @var AbstractWidgetController|DependencyProxy * @api */ protected $controller; @@ -150,7 +151,7 @@ private function initializeWidgetContext() * Stores the syntax tree child nodes in the Widget Context, so they can be * rendered with lateron. * - * @param array $childNodes The SyntaxTree Child nodes of this ViewHelper. + * @param array $childNodes The SyntaxTree Child nodes of this ViewHelper. * @return void */ public function setChildNodes(array $childNodes) @@ -166,7 +167,7 @@ public function setChildNodes(array $childNodes) /** * Generate the configuration for this widget. Override to adjust. * - * @return array + * @return array * @api */ protected function getWidgetConfiguration() @@ -179,7 +180,7 @@ protected function getWidgetConfiguration() * * By default, returns getWidgetConfiguration(). Should become API later. * - * @return array + * @return array */ protected function getAjaxWidgetConfiguration() { @@ -191,7 +192,7 @@ protected function getAjaxWidgetConfiguration() * * By default, returns getWidgetConfiguration(). Should become API later. * - * @return array + * @return array */ protected function getNonAjaxWidgetConfiguration() { @@ -231,6 +232,9 @@ protected function initiateSubRequest() } $subRequest->setControllerObjectName($this->widgetContext->getControllerObjectName()); try { + if (!($this->controller instanceof AbstractWidgetController)) { + throw new Exception\MissingControllerException('initiateSubRequest() can not be called if there is no controller inside $this->controller. Make sure to add the @Neos\Flow\Annotations\Inject annotation in your widget class.', 1284401632); + } $subResponse = $this->controller->processRequest($subRequest); // We need to make sure to not merge content up into the parent ActionResponse because that _could_ break the parent response. diff --git a/Neos.FluidAdaptor/Classes/Core/Widget/AjaxWidgetContextHolder.php b/Neos.FluidAdaptor/Classes/Core/Widget/AjaxWidgetContextHolder.php index f78a163c2f..81a7077866 100644 --- a/Neos.FluidAdaptor/Classes/Core/Widget/AjaxWidgetContextHolder.php +++ b/Neos.FluidAdaptor/Classes/Core/Widget/AjaxWidgetContextHolder.php @@ -30,7 +30,7 @@ class AjaxWidgetContextHolder * An array $ajaxWidgetIdentifier => $widgetContext * which stores the widget context. * - * @var array + * @var array */ protected $widgetContexts = []; diff --git a/Neos.FluidAdaptor/Classes/Core/Widget/WidgetContext.php b/Neos.FluidAdaptor/Classes/Core/Widget/WidgetContext.php index de1beed414..fb679f500f 100644 --- a/Neos.FluidAdaptor/Classes/Core/Widget/WidgetContext.php +++ b/Neos.FluidAdaptor/Classes/Core/Widget/WidgetContext.php @@ -48,7 +48,7 @@ class WidgetContext * controller as $this->widgetConfiguration, if being inside an AJAX * request * - * @var array + * @var array */ protected $ajaxWidgetConfiguration; @@ -57,7 +57,7 @@ class WidgetContext * controller as $this->widgetConfiguration, if being inside a non-AJAX * request * - * @var array + * @var array */ protected $nonAjaxWidgetConfiguration; /** @@ -71,7 +71,7 @@ class WidgetContext * The child nodes of the Widget ViewHelper. * Only available inside non-AJAX requests. * - * @var RootNode + * @var ?RootNode * @Flow\Transient */ protected $viewHelperChildNodes; @@ -80,7 +80,7 @@ class WidgetContext * The rendering context of the ViewHelperChildNodes. * Only available inside non-AJAX requests. * - * @var RenderingContextInterface + * @var ?RenderingContextInterface * @Flow\Transient */ protected $viewHelperChildNodeRenderingContext; @@ -120,7 +120,7 @@ public function setAjaxWidgetIdentifier($ajaxWidgetIdentifier) } /** - * @return array + * @return array */ public function getWidgetConfiguration() { @@ -132,7 +132,7 @@ public function getWidgetConfiguration() } /** - * @param array $ajaxWidgetConfiguration + * @param array $ajaxWidgetConfiguration * @return void */ public function setAjaxWidgetConfiguration(array $ajaxWidgetConfiguration) @@ -141,7 +141,7 @@ public function setAjaxWidgetConfiguration(array $ajaxWidgetConfiguration) } /** - * @param array $nonAjaxWidgetConfiguration + * @param array $nonAjaxWidgetConfiguration * @return void */ public function setNonAjaxWidgetConfiguration(array $nonAjaxWidgetConfiguration) @@ -178,17 +178,14 @@ public function setViewHelperChildNodes(RootNode $viewHelperChildNodes, Renderin } /** - * @return RootNode + * @return ?RootNode */ public function getViewHelperChildNodes() { return $this->viewHelperChildNodes; } - /** - * @return RenderingContextInterface - */ - public function getViewHelperChildNodeRenderingContext() + public function getViewHelperChildNodeRenderingContext(): ?RenderingContextInterface { return $this->viewHelperChildNodeRenderingContext; } diff --git a/Neos.FluidAdaptor/Classes/Service/AbstractGenerator.php b/Neos.FluidAdaptor/Classes/Service/AbstractGenerator.php index 0b37087aab..6001780143 100644 --- a/Neos.FluidAdaptor/Classes/Service/AbstractGenerator.php +++ b/Neos.FluidAdaptor/Classes/Service/AbstractGenerator.php @@ -54,7 +54,7 @@ public function __construct() * Get all class names inside this namespace and return them as array. * * @param string $namespace - * @return array Array of all class names inside a given namespace. + * @return array> Array of all class names inside a given namespace. */ protected function getClassNamesInNamespace($namespace) { @@ -101,7 +101,7 @@ protected function getTagNameForClass($className, $namespace) * @param \SimpleXMLElement $parentXmlNode Parent XML Node to add the child to * @param string $childNodeName Name of the child node * @param string $childNodeValue Value of the child node. Will be placed inside CDATA. - * @return \SimpleXMLElement the new element + * @return ?\SimpleXMLElement the new element */ protected function addChildWithCData(\SimpleXMLElement $parentXmlNode, $childNodeName, $childNodeValue) { @@ -110,8 +110,12 @@ protected function addChildWithCData(\SimpleXMLElement $parentXmlNode, $childNod $childNode = $domDocument->appendChild($domDocument->createElement($childNodeName)); $childNode->appendChild($domDocument->createCDATASection($childNodeValue)); - $childNodeTarget = $parentDomNode->ownerDocument->importNode($childNode, true); - $parentDomNode->appendChild($childNodeTarget); - return simplexml_import_dom($childNodeTarget); + $childNodeTarget = $parentDomNode->ownerDocument?->importNode($childNode, true); + if ($childNodeTarget instanceof \DOMNode) { + $parentDomNode->appendChild($childNodeTarget); + return simplexml_import_dom($childNodeTarget); + } + + return null; } } diff --git a/Neos.FluidAdaptor/Classes/Service/XsdGenerator.php b/Neos.FluidAdaptor/Classes/Service/XsdGenerator.php index c67220d2e9..be4fda8617 100644 --- a/Neos.FluidAdaptor/Classes/Service/XsdGenerator.php +++ b/Neos.FluidAdaptor/Classes/Service/XsdGenerator.php @@ -54,7 +54,11 @@ public function generateXsd($viewHelperNamespace, $xsdNamespace) $this->generateXmlForClassName($className, $viewHelperNamespace, $xmlRootNode); } - return $xmlRootNode->asXML(); + $result = $xmlRootNode->asXML(); + + return is_string($result) + ? $result + : throw new \Exception('Failed to generate xsd', 1743851104); } /** @@ -75,15 +79,22 @@ protected function generateXmlForClassName($className, $viewHelperNamespace, \Si $tagName = $this->getTagNameForClass($className, $viewHelperNamespace); $xsdElement = $xmlRootNode->addChild('xsd:element'); + /** @phpstan-ignore argument.type (offsetSet accepts mixed) */ $xsdElement->offsetSet('name', $tagName); - $this->docCommentParser->parseDocComment($reflectionClass->getDocComment()); + $docComment = $reflectionClass->getDocComment(); + if ($docComment !== false) { + $this->docCommentParser->parseDocComment($docComment); + } $this->addDocumentation($this->docCommentParser->getDescription(), $xsdElement); $xsdComplexType = $xsdElement->addChild('xsd:complexType'); + /** @phpstan-ignore argument.type (offsetSet accepts mixed) */ $xsdComplexType->offsetSet('mixed', 'true'); $xsdSequence = $xsdComplexType->addChild('xsd:sequence'); $xsdAny = $xsdSequence->addChild('xsd:any'); + /** @phpstan-ignore argument.type (offsetSet accepts mixed) */ $xsdAny->offsetSet('minOccurs', '0'); + /** @phpstan-ignore argument.type (offsetSet accepts mixed) */ $xsdAny->offsetSet('maxOccurs', 'unbounded'); $this->addAttributes($className, $xsdComplexType); @@ -106,10 +117,13 @@ protected function addAttributes($className, \SimpleXMLElement $xsdElement) foreach ($argumentDefinitions as $argumentDefinition) { /** @var \SimpleXMLElement $xsdAttribute */ $xsdAttribute = $xsdElement->addChild('xsd:attribute'); + /** @phpstan-ignore argument.type (offsetSet accepts mixed) */ $xsdAttribute->offsetSet('type', 'xsd:string'); + /** @phpstan-ignore argument.type (offsetSet accepts mixed) */ $xsdAttribute->offsetSet('name', $argumentDefinition->getName()); $this->addDocumentation($argumentDefinition->getDescription(), $xsdAttribute); if ($argumentDefinition->isRequired()) { + /** @phpstan-ignore argument.type (offsetSet accepts mixed) */ $xsdAttribute->offsetSet('use', 'required'); } } diff --git a/Neos.FluidAdaptor/Classes/View/AbstractTemplateView.php b/Neos.FluidAdaptor/Classes/View/AbstractTemplateView.php index 37a64bfa17..162312c818 100644 --- a/Neos.FluidAdaptor/Classes/View/AbstractTemplateView.php +++ b/Neos.FluidAdaptor/Classes/View/AbstractTemplateView.php @@ -36,7 +36,7 @@ abstract class AbstractTemplateView extends \TYPO3Fluid\Fluid\View\AbstractTempl * ... * ) * - * @var array + * @var array */ protected $supportedOptions = [ 'templateRootPathPattern' => [ @@ -101,7 +101,7 @@ abstract class AbstractTemplateView extends \TYPO3Fluid\Fluid\View\AbstractTempl * * @see $supportedOptions * - * @var array + * @var array */ protected $options = []; @@ -124,6 +124,9 @@ public function assign($key, $value): self return parent::assign($key, $value); } + /** + * @param array $values + */ public function assignMultiple(array $values): self { // layer to fix incompatibility error with typo3 fluid interface @@ -133,7 +136,7 @@ public function assignMultiple(array $values): self /** * Factory method to create an instance with given options. * - * @param array $options + * @param array $options * @return static */ public static function createWithOptions(array $options): self @@ -144,7 +147,7 @@ public static function createWithOptions(array $options): self /** * Set default options based on the supportedOptions provided * - * @param array $options + * @param array|null $options * @throws Exception */ public function __construct(?array $options = null) @@ -184,14 +187,9 @@ public function setControllerContext(ControllerContext $controllerContext) $renderingContext->setControllerContext($controllerContext); } - $paths = $this->getTemplatePaths(); $request = $controllerContext->getRequest(); - if (!$request instanceof ActionRequest) { - return; - } - $paths->setFormat($request->getFormat()); if ($paths->getTemplateRootPaths() === [] && $paths->getLayoutRootPaths() === [] && $paths->getPartialRootPaths() === []) { @@ -205,7 +203,7 @@ public function setControllerContext(ControllerContext $controllerContext) * Renders a given section. * * @param string $sectionName Name of section to render - * @param array $variables The variables to use + * @param array $variables The variables to use * @param boolean $ignoreUnknown Ignore an unknown section and just return an empty string * @return string rendered template for the section * @throws \Neos\FluidAdaptor\View\Exception\InvalidSectionException @@ -217,13 +215,14 @@ public function renderSection($sectionName, array $variables = [], $ignoreUnknow $variables = $this->getRenderingContext()->getVariableProvider()->getAll(); } + /** @phpstan-ignore argument.type (we assume Fluid actually supports \ArrayAccess here, too) */ return parent::renderSection($sectionName, $variables, $ignoreUnknown); } /** * Validate options given to this view. * - * @param array $options + * @param array $options * @return void * @throws Exception */ @@ -250,10 +249,10 @@ function ($supportedOptionData, $supportedOptionName, $options) { * Merges the given options with the default values * and sets the resulting options in this object. * - * @param array $options + * @param array $options * @return void */ - protected function setOptions(array $options) + protected function setOptions(array $options): void { $this->options = array_merge( array_map( diff --git a/Neos.FluidAdaptor/Classes/View/StandaloneView.php b/Neos.FluidAdaptor/Classes/View/StandaloneView.php index 6fdb79413a..d4a3cbfc6f 100644 --- a/Neos.FluidAdaptor/Classes/View/StandaloneView.php +++ b/Neos.FluidAdaptor/Classes/View/StandaloneView.php @@ -51,7 +51,7 @@ class StandaloneView extends AbstractTemplateView protected $environment; /** - * @var ActionRequest + * @var ?ActionRequest */ protected $request; @@ -64,7 +64,7 @@ class StandaloneView extends AbstractTemplateView /** * Factory method to create an instance with given options. * - * @param array $options + * @param array $options * @return static */ public static function createWithOptions(array $options): self @@ -75,11 +75,12 @@ public static function createWithOptions(array $options): self /** * Constructor * - * @param ActionRequest $request The current action request. If none is specified it will be created from the environment. - * @param array $options + * @param ?ActionRequest $request The current action request. If none is specified it will be created from the environment. + * @param array|null $options * @throws \Neos\FluidAdaptor\Exception + * @phpstan-ignore method.childParameterType (parameters are differently ordered and thus cannot match) */ - public function __construct(?ActionRequest $request = null, array $options = []) + public function __construct(?ActionRequest $request = null, ?array $options = []) { $this->request = $request; parent::__construct($options); @@ -116,7 +117,7 @@ public function initializeObject() /** * @param string $templateName */ - public function setTemplate($templateName) + public function setTemplate($templateName): void { $this->baseRenderingContext->setControllerAction($templateName); } @@ -130,6 +131,9 @@ public function setTemplate($templateName) */ public function setFormat($format) { + if (!$this->request) { + throw new \Exception('Cannot set format without a request', 1743856310); + } $this->request->setFormat($format); $this->baseRenderingContext->getTemplatePaths()->setFormat($format); } @@ -137,18 +141,18 @@ public function setFormat($format) /** * Returns the format of the current request (defaults is "html") * - * @return string $format + * @return ?string $format * @api */ public function getFormat() { - return $this->request->getFormat(); + return $this->request?->getFormat(); } /** * Returns the current request object * - * @return ActionRequest + * @return ?ActionRequest */ public function getRequest() { @@ -177,12 +181,20 @@ public function setTemplatePathAndFilename($templatePathAndFilename) /** * Returns the absolute path to a Fluid template file if it was specified with setTemplatePathAndFilename() before * - * @return string Fluid template path + * @return ?string Fluid template path * @api */ public function getTemplatePathAndFilename() { - return $this->baseRenderingContext->getTemplatePaths()->resolveTemplateFileForControllerAndActionAndFormat($this->request->getControllerName(), $this->request->getControllerActionName(), $this->request->getFormat()); + return $this->request + ? $this->baseRenderingContext + ->getTemplatePaths() + ->resolveTemplateFileForControllerAndActionAndFormat( + $this->request->getControllerName(), + $this->request->getControllerActionName(), + $this->request->getFormat() + ) + : null; } /** @@ -225,7 +237,7 @@ public function setLayoutRootPaths(array $layoutRootPaths) /** * Resolves the layout root to be used inside other paths. * - * @return array Fluid layout root paths + * @return array Fluid layout root paths * @throws InvalidTemplateResourceException * @api */ @@ -262,7 +274,7 @@ public function setPartialRootPaths(array $partialRootPaths) /** * Returns the absolute path to the folder that contains Fluid partial files * - * @return array Fluid partial root paths + * @return array Fluid partial root paths * @throws InvalidTemplateResourceException * @api */ diff --git a/Neos.FluidAdaptor/Classes/View/TemplatePaths.php b/Neos.FluidAdaptor/Classes/View/TemplatePaths.php index ee45bae7eb..6c577db4ae 100644 --- a/Neos.FluidAdaptor/Classes/View/TemplatePaths.php +++ b/Neos.FluidAdaptor/Classes/View/TemplatePaths.php @@ -52,7 +52,7 @@ class TemplatePaths extends \TYPO3Fluid\Fluid\View\TemplatePaths ]; /** - * @var array + * @var array */ protected $options = []; @@ -61,6 +61,9 @@ class TemplatePaths extends \TYPO3Fluid\Fluid\View\TemplatePaths */ protected $packageManager; + /** + * @param array $options + */ public function __construct(array $options = []) { foreach ($options as $optionName => $optionValue) { @@ -71,7 +74,7 @@ public function __construct(array $options = []) /** * @param PackageManager $packageManager */ - public function injectPackageManager(PackageManager $packageManager) + public function injectPackageManager(PackageManager $packageManager): void { $this->packageManager = $packageManager; } @@ -87,7 +90,7 @@ public function getTemplateRootPathPattern(): string /** * @param string $templateRootPathPattern */ - public function setTemplateRootPathPattern(string $templateRootPathPattern) + public function setTemplateRootPathPattern(string $templateRootPathPattern): void { $this->templateRootPathPattern = $templateRootPathPattern; } @@ -95,7 +98,7 @@ public function setTemplateRootPathPattern(string $templateRootPathPattern) /** * @param string $layoutRootPathPattern */ - public function setLayoutRootPathPattern(string $layoutRootPathPattern) + public function setLayoutRootPathPattern(string $layoutRootPathPattern): void { $this->layoutRootPathPattern = $layoutRootPathPattern; } @@ -103,7 +106,7 @@ public function setLayoutRootPathPattern(string $layoutRootPathPattern) /** * @param string $partialRootPathPattern */ - public function setPartialRootPathPattern(string $partialRootPathPattern) + public function setPartialRootPathPattern(string $partialRootPathPattern): void { $this->partialRootPathPattern = $partialRootPathPattern; } @@ -111,7 +114,7 @@ public function setPartialRootPathPattern(string $partialRootPathPattern) /** * @param string $templateRootPath */ - public function setTemplateRootPath($templateRootPath) + public function setTemplateRootPath($templateRootPath): void { $this->templateRootPaths = [$templateRootPath]; } @@ -119,7 +122,7 @@ public function setTemplateRootPath($templateRootPath) /** * Resolves the template root to be used inside other paths. * - * @return array Path(s) to template root directory + * @return array Path(s) to template root directory */ public function getTemplateRootPaths() { @@ -140,7 +143,7 @@ public function getTemplateRootPaths() } /** - * @return array + * @return array */ public function getLayoutRootPaths() { @@ -160,7 +163,10 @@ public function getLayoutRootPaths() return [$layoutRootPath]; } - public function getPartialRootPaths() + /** + * @return array + */ + public function getPartialRootPaths(): array { if ($this->partialRootPaths !== []) { return $this->partialRootPaths; @@ -181,7 +187,7 @@ public function getPartialRootPaths() /** * @param string $layoutRootPath */ - public function setLayoutRootPath($layoutRootPath) + public function setLayoutRootPath($layoutRootPath): void { $this->layoutRootPaths = [$layoutRootPath]; } @@ -189,7 +195,7 @@ public function setLayoutRootPath($layoutRootPath) /** * @param string $partialRootPath */ - public function setPartialRootPath($partialRootPath) + public function setPartialRootPath($partialRootPath): void { $this->partialRootPaths = [$partialRootPath]; } @@ -205,7 +211,7 @@ public function getPatternReplacementVariables() /** * @param string[] $patternReplacementVariables */ - public function setPatternReplacementVariables($patternReplacementVariables) + public function setPatternReplacementVariables($patternReplacementVariables): void { $this->patternReplacementVariables = $patternReplacementVariables; } @@ -389,10 +395,10 @@ protected function sanitizePath($path) * replaced by the current request format, and once with ."@format" stripped off. * * @param string $pattern Pattern to be resolved - * @param array $patternReplacementVariables The variables to replace in the pattern + * @param array $patternReplacementVariables The variables to replace in the pattern * @param boolean $bubbleControllerAndSubpackage if true, then we successively split off parts from "@controller" and "@subpackage" until both are empty. * @param boolean $formatIsOptional if true, then half of the resulting strings will have ."@format" stripped off, and the other half will have it. - * @return array unix style paths + * @return array unix style paths */ protected function expandGenericPathPattern($pattern, array $patternReplacementVariables, $bubbleControllerAndSubpackage, $formatIsOptional) { @@ -426,10 +432,10 @@ protected function expandGenericPathPattern($pattern, array $patternReplacementV } /** - * @param array $paths + * @param array $paths * @param string $variableName * @param string $variableValue - * @return array + * @return array */ protected function replacePatternVariable($paths, $variableName, $variableValue) { @@ -441,11 +447,11 @@ protected function replacePatternVariable($paths, $variableName, $variableValue) } /** - * @param array $paths + * @param array $paths * @param string $controllerName * @param string $subPackageKey * @param bool $bubbleControllerAndSubpackage - * @return array + * @return array */ protected function expandSubPackageAndController(array $paths, string $controllerName, string $subPackageKey = '', bool $bubbleControllerAndSubpackage = false): array { @@ -460,7 +466,7 @@ protected function expandSubPackageAndController(array $paths, string $controlle $numberOfSubpackageParts = count($subpackageKeyParts); $subpackageReplacements = []; for ($i = 0; $i <= $numberOfSubpackageParts; $i++) { - $subpackageReplacements[] = implode('/', ($i < 0 ? $subpackageKeyParts : array_slice($subpackageKeyParts, $i))); + $subpackageReplacements[] = implode('/', array_slice($subpackageKeyParts, $i)); } $paths = $this->expandPatterns($paths, '@subpackage', $subpackageReplacements); @@ -476,10 +482,10 @@ protected function expandSubPackageAndController(array $paths, string $controlle * Expands the given $patterns by adding an array element for each $replacement * replacing occurrences of $search. * - * @param array $patterns + * @param array $patterns * @param string $search - * @param array $replacements - * @return array + * @param array $replacements + * @return array */ protected function expandPatterns(array $patterns, string $search, array $replacements): array { diff --git a/Neos.FluidAdaptor/Classes/ViewHelpers/DebugViewHelper.php b/Neos.FluidAdaptor/Classes/ViewHelpers/DebugViewHelper.php index 0b189fa7b2..39d42bc727 100644 --- a/Neos.FluidAdaptor/Classes/ViewHelpers/DebugViewHelper.php +++ b/Neos.FluidAdaptor/Classes/ViewHelpers/DebugViewHelper.php @@ -78,6 +78,6 @@ public function render() $expressionToExamine = (is_object($expressionToExamine) ? get_class($expressionToExamine) : gettype($expressionToExamine)); } - return \Neos\Flow\var_dump($expressionToExamine, $this->arguments['title'], true); + return \Neos\Flow\var_dump($expressionToExamine, $this->arguments['title'], true) ?: ''; } } diff --git a/Neos.FluidAdaptor/Classes/ViewHelpers/FlashMessagesViewHelper.php b/Neos.FluidAdaptor/Classes/ViewHelpers/FlashMessagesViewHelper.php index 5198e6ed28..73ccbe633e 100644 --- a/Neos.FluidAdaptor/Classes/ViewHelpers/FlashMessagesViewHelper.php +++ b/Neos.FluidAdaptor/Classes/ViewHelpers/FlashMessagesViewHelper.php @@ -105,7 +105,7 @@ public function render() * Render the flash messages as unsorted list. This is triggered if no "as" argument is given * to the ViewHelper. * - * @param list $flashMessages + * @param array $flashMessages * @return string */ protected function renderAsList(array $flashMessages) @@ -131,7 +131,7 @@ protected function renderAsList(array $flashMessages) * the flash messages are stored in the template inside the variable specified * in "as". * - * @param array $flashMessages + * @param array $flashMessages * @param string $as * @return string */ diff --git a/Neos.FluidAdaptor/Classes/ViewHelpers/Form/AbstractFormFieldViewHelper.php b/Neos.FluidAdaptor/Classes/ViewHelpers/Form/AbstractFormFieldViewHelper.php index 489844b745..a3da57a863 100644 --- a/Neos.FluidAdaptor/Classes/ViewHelpers/Form/AbstractFormFieldViewHelper.php +++ b/Neos.FluidAdaptor/Classes/ViewHelpers/Form/AbstractFormFieldViewHelper.php @@ -84,7 +84,6 @@ protected function getNameWithoutPrefix(): string $name = $this->arguments['name']; } if ($this->hasArgument('value')) { - /** @var object $value */ $value = $this->arguments['value']; $multiple = $this->hasArgument('multiple') && $this->arguments['multiple'] === true; if (!$multiple @@ -223,7 +222,7 @@ protected function getPropertyPath(): string return $formObjectName . '.' . $this->arguments['property']; } - return rtrim(preg_replace('/(\]\[|\[|\])/', '.', $this->getNameWithoutPrefix()), '.'); + return rtrim(preg_replace('/(\]\[|\[|\])/', '.', $this->getNameWithoutPrefix()) ?: '', '.'); } /** diff --git a/Neos.FluidAdaptor/Classes/ViewHelpers/Form/AbstractFormViewHelper.php b/Neos.FluidAdaptor/Classes/ViewHelpers/Form/AbstractFormViewHelper.php index 60aa6d6da6..c58c50daa5 100644 --- a/Neos.FluidAdaptor/Classes/ViewHelpers/Form/AbstractFormViewHelper.php +++ b/Neos.FluidAdaptor/Classes/ViewHelpers/Form/AbstractFormViewHelper.php @@ -41,7 +41,7 @@ public function injectPersistenceManager(PersistenceManagerInterface $persistenc /** * Prefixes / namespaces the given name with the form field prefix * - * @param string $fieldName field name to be prefixed + * @param ?string $fieldName field name to be prefixed * @return string namespaced field name */ protected function prefixFieldName($fieldName) @@ -68,13 +68,13 @@ protected function prefixFieldName($fieldName) * Renders a hidden form field containing the technical identity of the given object. * * @param object $object Object to create the identity field for - * @param string $name Name + * @param ?string $name Name * @return string A hidden field containing the Identity (UUID in Flow) of the given object or empty string if the object is unknown to the persistence framework * @see \Neos\Flow\Mvc\Controller\Argument::setValue() */ - protected function renderHiddenIdentityField($object, $name) + protected function renderHiddenIdentityField(object $object, $name) { - if (!is_object($object) || $this->persistenceManager->isNewObject($object)) { + if ($this->persistenceManager->isNewObject($object) || $name === null) { return ''; } $identifier = $this->persistenceManager->getIdentifierByObject($object); diff --git a/Neos.FluidAdaptor/Classes/ViewHelpers/Form/CheckboxViewHelper.php b/Neos.FluidAdaptor/Classes/ViewHelpers/Form/CheckboxViewHelper.php index d7d4e3b574..d1fc34c4fb 100644 --- a/Neos.FluidAdaptor/Classes/ViewHelpers/Form/CheckboxViewHelper.php +++ b/Neos.FluidAdaptor/Classes/ViewHelpers/Form/CheckboxViewHelper.php @@ -98,7 +98,11 @@ public function render() if ($checked === null) { $checked = false; foreach ($propertyValue as $value) { - if (TypeHandling::isSimpleType(TypeHandling::getTypeForValue($value))) { + $typeForValue = TypeHandling::getTypeForValue($value); + if ($typeForValue === null) { + continue; + } + if (TypeHandling::isSimpleType($typeForValue)) { $checked = $valueAttribute === $value; } else { // assume an entity diff --git a/Neos.FluidAdaptor/Classes/ViewHelpers/Form/SelectViewHelper.php b/Neos.FluidAdaptor/Classes/ViewHelpers/Form/SelectViewHelper.php index a2dde44370..f9c0f08933 100644 --- a/Neos.FluidAdaptor/Classes/ViewHelpers/Form/SelectViewHelper.php +++ b/Neos.FluidAdaptor/Classes/ViewHelpers/Form/SelectViewHelper.php @@ -200,7 +200,7 @@ public function render() /** * Render the option tags. * - * @param array $options the options for the form. + * @param array $options the options for the form. * @return string rendered tags. */ protected function renderOptionTags($options) @@ -227,7 +227,7 @@ protected function renderOptionTags($options) /** * Render the option tags. * - * @return array an associative array of options, key will be the value of the option tag + * @return array an associative array of options, key will be the value of the option tag * @throws ViewHelper\Exception */ protected function getOptions() @@ -354,6 +354,7 @@ protected function getOptionValueScalar($valueElement) } elseif ($this->persistenceManager->getIdentifierByObject($valueElement) !== null) { return $this->persistenceManager->getIdentifierByObject($valueElement); } else { + /** @phpstan-ignore cast.string (we'll try anyway -.-) */ return (string)$valueElement; } } else { diff --git a/Neos.FluidAdaptor/Classes/ViewHelpers/FormViewHelper.php b/Neos.FluidAdaptor/Classes/ViewHelpers/FormViewHelper.php index 9d627d6dcf..3500474fbb 100644 --- a/Neos.FluidAdaptor/Classes/ViewHelpers/FormViewHelper.php +++ b/Neos.FluidAdaptor/Classes/ViewHelpers/FormViewHelper.php @@ -299,7 +299,7 @@ protected function renderHiddenReferrerFields() $result = chr(10); $request = $this->controllerContext->getRequest(); $argumentNamespace = null; - if ($request instanceof ActionRequest && $request->isMainRequest() === false) { + if ($request->isMainRequest() === false) { $argumentNamespace = $request->getArgumentNamespace(); $referrer = [ @@ -313,13 +313,10 @@ protected function renderHiddenReferrerFields() $referrerValue = $referrerValue ? htmlspecialchars($referrerValue) : ''; $result .= '' . chr(10); } + /** @var ActionRequest $request we know this since $request is not the main request */ $request = $request->getParentRequest(); } - if ($request === null) { - throw new \RuntimeException('No ActionRequest could be found to evaluate form argument namespace.', 1565945918); - } - $arguments = $request->getArguments(); if ($argumentNamespace !== null && isset($arguments[$argumentNamespace])) { // A sub request was there; thus we can unset the sub requests arguments, diff --git a/Neos.FluidAdaptor/Classes/ViewHelpers/Format/BytesViewHelper.php b/Neos.FluidAdaptor/Classes/ViewHelpers/Format/BytesViewHelper.php index 76a4b946e1..a538a7d88d 100644 --- a/Neos.FluidAdaptor/Classes/ViewHelpers/Format/BytesViewHelper.php +++ b/Neos.FluidAdaptor/Classes/ViewHelpers/Format/BytesViewHelper.php @@ -72,7 +72,7 @@ class BytesViewHelper extends AbstractLocaleAwareViewHelper /** * @param float $bytes - * @return array + * @return array{0: float, 1: string} */ protected static function maximizeUnit(float $bytes): array { diff --git a/Neos.FluidAdaptor/Classes/ViewHelpers/Format/CaseViewHelper.php b/Neos.FluidAdaptor/Classes/ViewHelpers/Format/CaseViewHelper.php index 9dc3ecd2c2..8eefa16a80 100644 --- a/Neos.FluidAdaptor/Classes/ViewHelpers/Format/CaseViewHelper.php +++ b/Neos.FluidAdaptor/Classes/ViewHelpers/Format/CaseViewHelper.php @@ -123,7 +123,7 @@ public function render() } /** - * @param array $arguments + * @param array $arguments * @param \Closure $renderChildrenClosure * @param RenderingContextInterface $renderingContext * @return string diff --git a/Neos.FluidAdaptor/Classes/ViewHelpers/Format/CropViewHelper.php b/Neos.FluidAdaptor/Classes/ViewHelpers/Format/CropViewHelper.php index 4ab2986948..e9de2cb5be 100644 --- a/Neos.FluidAdaptor/Classes/ViewHelpers/Format/CropViewHelper.php +++ b/Neos.FluidAdaptor/Classes/ViewHelpers/Format/CropViewHelper.php @@ -78,7 +78,7 @@ public function render() } /** - * @param array $arguments + * @param array $arguments * @param \Closure $renderChildrenClosure * @param RenderingContextInterface $renderingContext * @return string diff --git a/Neos.FluidAdaptor/Classes/ViewHelpers/Format/HtmlentitiesDecodeViewHelper.php b/Neos.FluidAdaptor/Classes/ViewHelpers/Format/HtmlentitiesDecodeViewHelper.php index 0316b42dcc..660c7f2fee 100644 --- a/Neos.FluidAdaptor/Classes/ViewHelpers/Format/HtmlentitiesDecodeViewHelper.php +++ b/Neos.FluidAdaptor/Classes/ViewHelpers/Format/HtmlentitiesDecodeViewHelper.php @@ -79,7 +79,7 @@ public function render() /** * Applies html_entity_decode() on the specified value. * - * @param array $arguments + * @param array $arguments * @param \Closure $renderChildrenClosure * @param RenderingContextInterface $renderingContext * @return string|mixed diff --git a/Neos.FluidAdaptor/Classes/ViewHelpers/Format/HtmlentitiesViewHelper.php b/Neos.FluidAdaptor/Classes/ViewHelpers/Format/HtmlentitiesViewHelper.php index a1d67c8532..0f0dbe84ed 100644 --- a/Neos.FluidAdaptor/Classes/ViewHelpers/Format/HtmlentitiesViewHelper.php +++ b/Neos.FluidAdaptor/Classes/ViewHelpers/Format/HtmlentitiesViewHelper.php @@ -79,7 +79,7 @@ public function render() /** * Applies htmlentities() on the specified value. * - * @param array $arguments + * @param array $arguments * @param \Closure $renderChildrenClosure * @param RenderingContextInterface $renderingContext * @return string|mixed diff --git a/Neos.FluidAdaptor/Classes/ViewHelpers/Format/JsonViewHelper.php b/Neos.FluidAdaptor/Classes/ViewHelpers/Format/JsonViewHelper.php index 87c216d4d5..d8c7b228e4 100644 --- a/Neos.FluidAdaptor/Classes/ViewHelpers/Format/JsonViewHelper.php +++ b/Neos.FluidAdaptor/Classes/ViewHelpers/Format/JsonViewHelper.php @@ -86,7 +86,7 @@ public function render() $options = $options | JSON_FORCE_OBJECT; } - return json_encode($value, $options); + return json_encode($value, $options | JSON_THROW_ON_ERROR); } /** diff --git a/Neos.FluidAdaptor/Classes/ViewHelpers/Format/PaddingViewHelper.php b/Neos.FluidAdaptor/Classes/ViewHelpers/Format/PaddingViewHelper.php index 476db7b55e..cc37d1f089 100644 --- a/Neos.FluidAdaptor/Classes/ViewHelpers/Format/PaddingViewHelper.php +++ b/Neos.FluidAdaptor/Classes/ViewHelpers/Format/PaddingViewHelper.php @@ -79,7 +79,7 @@ public function render() /** * Applies str_pad() on the specified value. * - * @param array $arguments + * @param array $arguments * @param \Closure $renderChildrenClosure * @param RenderingContextInterface $renderingContext * @return string diff --git a/Neos.FluidAdaptor/Classes/ViewHelpers/Format/UrlencodeViewHelper.php b/Neos.FluidAdaptor/Classes/ViewHelpers/Format/UrlencodeViewHelper.php index f312b713b2..f0650790bb 100644 --- a/Neos.FluidAdaptor/Classes/ViewHelpers/Format/UrlencodeViewHelper.php +++ b/Neos.FluidAdaptor/Classes/ViewHelpers/Format/UrlencodeViewHelper.php @@ -72,7 +72,7 @@ public function render() /** * Applies rawurlencode() on the specified value. * - * @param array $arguments + * @param array $arguments * @param \Closure $renderChildrenClosure * @param RenderingContextInterface $renderingContext * @return string|mixed diff --git a/Neos.FluidAdaptor/Classes/ViewHelpers/RenderChildrenViewHelper.php b/Neos.FluidAdaptor/Classes/ViewHelpers/RenderChildrenViewHelper.php index 9d35aca2ef..1041792388 100644 --- a/Neos.FluidAdaptor/Classes/ViewHelpers/RenderChildrenViewHelper.php +++ b/Neos.FluidAdaptor/Classes/ViewHelpers/RenderChildrenViewHelper.php @@ -74,7 +74,7 @@ public function render(): string $widgetChildNodes = $this->getWidgetChildNodes(); $this->addArgumentsToTemplateVariableContainer($this->arguments['arguments']); - $output = $widgetChildNodes->evaluate($renderingContext); + $output = $widgetChildNodes?->evaluate($renderingContext) ?: ''; $this->removeArgumentsFromTemplateVariableContainer($this->arguments['arguments']); return $output; @@ -97,10 +97,10 @@ protected function getWidgetRenderingContext(): RenderingContextInterface } /** - * @return RootNode + * @return ?RootNode * @throws WidgetContextNotFoundException */ - protected function getWidgetChildNodes(): RootNode + protected function getWidgetChildNodes(): ?RootNode { return $this->getWidgetContext()->getViewHelperChildNodes(); } @@ -123,7 +123,7 @@ protected function getWidgetContext(): WidgetContext /** * Add the given arguments to the TemplateVariableContainer of the widget. * - * @param array $arguments + * @param array $arguments * @return void * @throws RenderingContextNotFoundException * @throws WidgetContextNotFoundException @@ -139,7 +139,7 @@ protected function addArgumentsToTemplateVariableContainer(array $arguments): vo /** * Remove the given arguments from the TemplateVariableContainer of the widget. * - * @param array $arguments + * @param array $arguments * @return void * @throws RenderingContextNotFoundException * @throws WidgetContextNotFoundException diff --git a/Neos.FluidAdaptor/Classes/ViewHelpers/Security/IfAccessViewHelper.php b/Neos.FluidAdaptor/Classes/ViewHelpers/Security/IfAccessViewHelper.php index 225621a2e7..a6d05335eb 100644 --- a/Neos.FluidAdaptor/Classes/ViewHelpers/Security/IfAccessViewHelper.php +++ b/Neos.FluidAdaptor/Classes/ViewHelpers/Security/IfAccessViewHelper.php @@ -57,7 +57,7 @@ class IfAccessViewHelper extends AbstractConditionViewHelper /** * Initializes the "then" and "else" arguments */ - public function initializeArguments() + public function initializeArguments(): void { parent::initializeArguments(); $this->registerArgument('privilegeTarget', 'string', 'Condition expression conforming to Fluid boolean rules', true); @@ -80,9 +80,9 @@ public function render() } /** - * @param array $arguments + * @param array $arguments * @param \Closure $renderChildrenClosure - * @param RenderingContextInterface $renderingContext + * @param FlowAwareRenderingContextInterface&RenderingContextInterface $renderingContext * @return mixed */ public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext) @@ -91,28 +91,30 @@ public static function renderStatic(array $arguments, \Closure $renderChildrenCl } /** - * @param array|null $arguments + * @param array|null $arguments * @param FlowAwareRenderingContextInterface&RenderingContextInterface $renderingContext * @return boolean */ protected static function evaluateCondition($arguments, RenderingContextInterface $renderingContext) { $objectManager = $renderingContext->getObjectManager(); - /** @var Context $securityContext */ $securityContext = $objectManager->get(Context::class); - if ($securityContext !== null && !$securityContext->canBeInitialized()) { + if (!$securityContext->canBeInitialized()) { return false; } $privilegeManager = static::getPrivilegeManager($renderingContext); - return $privilegeManager->isPrivilegeTargetGranted($arguments['privilegeTarget'], $arguments['parameters'] ?? []); + $privilegeTarget = $arguments['privilegeTarget'] ?? null; + if (!is_string($privilegeTarget)) { + throw new \Exception('Missing privilegeTarget argument.', 1743848049); + } + return $privilegeManager->isPrivilegeTargetGranted($privilegeTarget, $arguments['parameters'] ?? []); } /** - * @param RenderingContext $renderingContext * @return PrivilegeManagerInterface */ - protected static function getPrivilegeManager(RenderingContext $renderingContext) + protected static function getPrivilegeManager(FlowAwareRenderingContextInterface $renderingContext) { $objectManager = $renderingContext->getObjectManager(); return $objectManager->get(PrivilegeManagerInterface::class); diff --git a/Neos.FluidAdaptor/Classes/ViewHelpers/Security/IfAuthenticatedViewHelper.php b/Neos.FluidAdaptor/Classes/ViewHelpers/Security/IfAuthenticatedViewHelper.php index a7a78ae4b9..f7f6c6cb1f 100644 --- a/Neos.FluidAdaptor/Classes/ViewHelpers/Security/IfAuthenticatedViewHelper.php +++ b/Neos.FluidAdaptor/Classes/ViewHelpers/Security/IfAuthenticatedViewHelper.php @@ -66,7 +66,7 @@ public function render() } /** - * @param null $arguments + * @param array|null $arguments * @param FlowAwareRenderingContextInterface&RenderingContextInterface $renderingContext * @return bool */ diff --git a/Neos.FluidAdaptor/Classes/ViewHelpers/Security/IfHasRoleViewHelper.php b/Neos.FluidAdaptor/Classes/ViewHelpers/Security/IfHasRoleViewHelper.php index 9be411ea51..5db4eeac33 100644 --- a/Neos.FluidAdaptor/Classes/ViewHelpers/Security/IfHasRoleViewHelper.php +++ b/Neos.FluidAdaptor/Classes/ViewHelpers/Security/IfHasRoleViewHelper.php @@ -79,7 +79,7 @@ class IfHasRoleViewHelper extends AbstractConditionViewHelper /** * Initializes the "then" and "else" arguments */ - public function initializeArguments() + public function initializeArguments(): void { parent::initializeArguments(); $this->registerArgument('role', 'mixed', 'The role or role identifier.', true); @@ -104,7 +104,7 @@ public function render() } /** - * @param array|null $arguments + * @param array|null $arguments * @param FlowAwareRenderingContextInterface&RenderingContextInterface $renderingContext * @return boolean */ @@ -120,8 +120,8 @@ protected static function evaluateCondition($arguments, RenderingContextInterfac return false; } - $role = $arguments['role']; - $account = $arguments['account']; + $role = $arguments['role'] ?? null; + $account = $arguments['account'] ?? null; $packageKey = isset($arguments['packageKey']) ? $arguments['packageKey'] : $renderingContext->getControllerContext()->getRequest()->getControllerPackageKey(); if (is_string($role)) { diff --git a/Neos.FluidAdaptor/Classes/ViewHelpers/TranslateViewHelper.php b/Neos.FluidAdaptor/Classes/ViewHelpers/TranslateViewHelper.php index c4c0d06f0b..540306e4da 100644 --- a/Neos.FluidAdaptor/Classes/ViewHelpers/TranslateViewHelper.php +++ b/Neos.FluidAdaptor/Classes/ViewHelpers/TranslateViewHelper.php @@ -122,9 +122,7 @@ public function render() } if ($package === null) { $request = $this->renderingContext->getControllerContext()->getRequest(); - if ($request instanceof ActionRequest) { - $package = $request->getControllerPackageKey(); - } + $package = $request->getControllerPackageKey(); if (empty($package)) { throw new ViewHelperException( 'The current package key can\'t be resolved. Make sure to initialize the Fluid view with a proper ActionRequest and/or specify the "package" argument when using the f:translate ViewHelper', @@ -151,7 +149,7 @@ public function render() /** * @param Translator $translator */ - public function injectTranslator(Translator $translator) + public function injectTranslator(Translator $translator): void { $this->translator = $translator; } diff --git a/Neos.FluidAdaptor/Classes/ViewHelpers/Uri/ResourceViewHelper.php b/Neos.FluidAdaptor/Classes/ViewHelpers/Uri/ResourceViewHelper.php index 6254572c13..7caf99eec5 100644 --- a/Neos.FluidAdaptor/Classes/ViewHelpers/Uri/ResourceViewHelper.php +++ b/Neos.FluidAdaptor/Classes/ViewHelpers/Uri/ResourceViewHelper.php @@ -100,7 +100,7 @@ public function render() } /** - * @param array $arguments + * @param array $arguments * @param \Closure $renderChildrenClosure * @param FlowAwareRenderingContextInterface&RenderingContextInterface $renderingContext * @return string diff --git a/Neos.FluidAdaptor/Classes/ViewHelpers/Validation/IfHasErrorsViewHelper.php b/Neos.FluidAdaptor/Classes/ViewHelpers/Validation/IfHasErrorsViewHelper.php index 3efab8366c..ed51f1f7ce 100644 --- a/Neos.FluidAdaptor/Classes/ViewHelpers/Validation/IfHasErrorsViewHelper.php +++ b/Neos.FluidAdaptor/Classes/ViewHelpers/Validation/IfHasErrorsViewHelper.php @@ -42,10 +42,9 @@ class IfHasErrorsViewHelper extends AbstractConditionViewHelper { /** - * @return void * @throws \Neos\FluidAdaptor\Core\ViewHelper\Exception */ - public function initializeArguments() + public function initializeArguments(): void { $this->registerArgument('then', 'mixed', 'Value to be returned if the condition if met.', false); $this->registerArgument('else', 'mixed', 'Value to be returned if the condition if not met.', false); @@ -70,7 +69,7 @@ public function render() } /** - * @param null $arguments + * @param array $arguments * @param FlowAwareRenderingContextInterface&RenderingContextInterface $renderingContext * @return boolean */ diff --git a/Neos.FluidAdaptor/Classes/ViewHelpers/Widget/Controller/AutocompleteController.php b/Neos.FluidAdaptor/Classes/ViewHelpers/Widget/Controller/AutocompleteController.php index 9dd40909a6..a9468b212a 100644 --- a/Neos.FluidAdaptor/Classes/ViewHelpers/Widget/Controller/AutocompleteController.php +++ b/Neos.FluidAdaptor/Classes/ViewHelpers/Widget/Controller/AutocompleteController.php @@ -21,7 +21,7 @@ class AutocompleteController extends AbstractWidgetController { /** - * @var array + * @var array */ protected $configuration = ['limit' => 10]; @@ -82,6 +82,6 @@ public function autocompleteAction($term) 'value' => $val ]; } - return json_encode($output); + return json_encode($output, JSON_THROW_ON_ERROR); } } diff --git a/Neos.FluidAdaptor/Classes/ViewHelpers/Widget/Controller/PaginateController.php b/Neos.FluidAdaptor/Classes/ViewHelpers/Widget/Controller/PaginateController.php index 1778c2c0a1..a7b50ed016 100644 --- a/Neos.FluidAdaptor/Classes/ViewHelpers/Widget/Controller/PaginateController.php +++ b/Neos.FluidAdaptor/Classes/ViewHelpers/Widget/Controller/PaginateController.php @@ -26,7 +26,7 @@ class PaginateController extends AbstractWidgetController protected $objects; /** - * @var array + * @var array */ protected $configuration = ['itemsPerPage' => 10, 'insertAbove' => false, 'insertBelow' => true, 'maximumNumberOfLinks' => 99]; @@ -130,16 +130,24 @@ protected function calculateDisplayRange() } /** - * Returns an array with the keys "pages", "current", "numberOfPages", "nextPage" & "previousPage" - * - * @return array + * @return array{ + * pages: list, + * current: int, + * numberOfPages: int, + * displayRangeStart: float, + * displayRangeEnd: float, + * hasLessPages: bool, + * hasMorePages: bool, + * nextPage?: int, + * previousPage?: int, + * } */ protected function buildPagination() { $this->calculateDisplayRange(); $pages = []; for ($i = $this->displayRangeStart; $i <= $this->displayRangeEnd; $i++) { - $pages[] = ['number' => $i, 'isCurrent' => ($i === $this->currentPage)]; + $pages[] = ['number' => (int)$i, 'isCurrent' => ($i == $this->currentPage)]; } $pagination = [ 'pages' => $pages, diff --git a/Neos.FluidAdaptor/Classes/ViewHelpers/Widget/LinkViewHelper.php b/Neos.FluidAdaptor/Classes/ViewHelpers/Widget/LinkViewHelper.php index 55de2b8c53..5a6800624a 100644 --- a/Neos.FluidAdaptor/Classes/ViewHelpers/Widget/LinkViewHelper.php +++ b/Neos.FluidAdaptor/Classes/ViewHelpers/Widget/LinkViewHelper.php @@ -120,7 +120,7 @@ protected function getAjaxUri(): string } else { $arguments['__widgetId'] = $widgetContext->getAjaxWidgetIdentifier(); } - return '?' . http_build_query($arguments, null, '&'); + return '?' . http_build_query(data: $arguments, arg_separator: '&'); } /** diff --git a/Neos.FluidAdaptor/Classes/ViewHelpers/Widget/UriViewHelper.php b/Neos.FluidAdaptor/Classes/ViewHelpers/Widget/UriViewHelper.php index f96c6702e2..a57cb01bcf 100644 --- a/Neos.FluidAdaptor/Classes/ViewHelpers/Widget/UriViewHelper.php +++ b/Neos.FluidAdaptor/Classes/ViewHelpers/Widget/UriViewHelper.php @@ -104,7 +104,7 @@ protected function getAjaxUri(): string } else { $arguments['__widgetId'] = $widgetContext->getAjaxWidgetIdentifier(); } - return '?' . http_build_query($arguments, null, '&'); + return '?' . http_build_query(data: $arguments, arg_separator: '&'); } /** diff --git a/Neos.Http.Factories/Classes/FlowUploadedFile.php b/Neos.Http.Factories/Classes/FlowUploadedFile.php index e9f5e296cb..be9592c68b 100644 --- a/Neos.Http.Factories/Classes/FlowUploadedFile.php +++ b/Neos.Http.Factories/Classes/FlowUploadedFile.php @@ -14,12 +14,12 @@ class FlowUploadedFile extends UploadedFile * This is either the persistent identifier of a previously submitted resource file * or an array with the "__identity" key set to the persistent identifier. * - * @var array|string + * @var array{__identity: string}|string|null */ protected $originallySubmittedResource; /** - * @var string + * @var ?string */ protected $collectionName; @@ -27,7 +27,7 @@ class FlowUploadedFile extends UploadedFile * This is either the persistent identifier of a previously submitted resource file * or an array with the "__identity" key set to the persistent identifier. * - * @return array|string + * @return array{__identity: string}|string|null */ public function getOriginallySubmittedResource() { @@ -40,15 +40,15 @@ public function getOriginallySubmittedResource() * This is either the persistent identifier of a previously submitted resource file * or an array with the "__identity" key set to the persistent identifier. * - * @param array|string $originallySubmittedResource + * @param array{__identity: string}|string $originallySubmittedResource */ - public function setOriginallySubmittedResource($originallySubmittedResource) + public function setOriginallySubmittedResource($originallySubmittedResource): void { $this->originallySubmittedResource = $originallySubmittedResource; } /** - * @return string + * @return ?string */ public function getCollectionName() { @@ -58,7 +58,7 @@ public function getCollectionName() /** * @param string $collectionName */ - public function setCollectionName($collectionName) + public function setCollectionName($collectionName): void { $this->collectionName = $collectionName; } diff --git a/Neos.Http.Factories/Classes/ServerRequestFactoryTrait.php b/Neos.Http.Factories/Classes/ServerRequestFactoryTrait.php index b4be972e24..1208f5569f 100644 --- a/Neos.Http.Factories/Classes/ServerRequestFactoryTrait.php +++ b/Neos.Http.Factories/Classes/ServerRequestFactoryTrait.php @@ -56,6 +56,7 @@ public function __construct( /** * @inheritDoc + * @param array $serverParams */ public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface { diff --git a/Neos.Http.Factories/Classes/StreamFactoryTrait.php b/Neos.Http.Factories/Classes/StreamFactoryTrait.php index 6c79adcc84..f8201880bc 100644 --- a/Neos.Http.Factories/Classes/StreamFactoryTrait.php +++ b/Neos.Http.Factories/Classes/StreamFactoryTrait.php @@ -18,6 +18,9 @@ trait StreamFactoryTrait public function createStream(string $content = ''): StreamInterface { $fileHandle = fopen('php://temp', 'r+'); + if (!is_resource($fileHandle)) { + throw new \Exception('unable to open php://temp', 1743846274); + } fwrite($fileHandle, $content); rewind($fileHandle); @@ -30,11 +33,15 @@ public function createStream(string $content = ''): StreamInterface public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface { $fileHandle = fopen($filename, $mode); + if (!is_resource($fileHandle)) { + throw new \Exception('unable to open ' . $filename, 1743846243); + } return $this->createStreamFromResource($fileHandle); } /** * @inheritDoc + * @param resource $resource */ public function createStreamFromResource($resource): StreamInterface { diff --git a/Neos.Kickstarter/Classes/Command/KickstartCommandController.php b/Neos.Kickstarter/Classes/Command/KickstartCommandController.php index 246b927c0c..ef9dd18a25 100644 --- a/Neos.Kickstarter/Classes/Command/KickstartCommandController.php +++ b/Neos.Kickstarter/Classes/Command/KickstartCommandController.php @@ -49,7 +49,7 @@ class KickstartCommandController extends CommandController * @param string $packageType Optional package type, e.g. "neos-plugin" * @see neos.flow:package:create */ - public function packageCommand($packageKey, $packageType = PackageInterface::DEFAULT_COMPOSER_TYPE) + public function packageCommand($packageKey, $packageType = PackageInterface::DEFAULT_COMPOSER_TYPE): void { $this->validatePackageKey($packageKey); @@ -101,7 +101,7 @@ public function packageCommand($packageKey, $packageType = PackageInterface::DEF * @param boolean $force Overwrite any existing controller or template code. Regardless of this flag, the package, model and repository will never be overwritten. * @see neos.kickstarter:kickstart:commandcontroller */ - public function actionControllerCommand($packageKey, $controllerName, $generateActions = false, $generateTemplates = true, $generateFusion = false, $generateRelated = false, $force = false) + public function actionControllerCommand($packageKey, $controllerName, $generateActions = false, $generateTemplates = true, $generateFusion = false, $generateRelated = false, $force = false): void { $subpackageName = ''; if (strpos($packageKey, '/') !== false) { @@ -195,7 +195,7 @@ public function actionControllerCommand($packageKey, $controllerName, $generateA * @param boolean $force Overwrite any existing controller. * @see neos.kickstarter:kickstart:actioncontroller */ - public function commandControllerCommand($packageKey, $controllerName, $force = false) + public function commandControllerCommand($packageKey, $controllerName, $force = false): void { $this->validatePackageKey($packageKey); if (!$this->packageManager->isPackageAvailable($packageKey)) { @@ -222,7 +222,7 @@ public function commandControllerCommand($packageKey, $controllerName, $force = * @param boolean $force Overwrite any existing model. * @see neos.kickstarter:kickstart:repository */ - public function modelCommand($packageKey, $modelName, $force = false) + public function modelCommand($packageKey, $modelName, $force = false): void { $this->validatePackageKey($packageKey); if (!$this->packageManager->isPackageAvailable($packageKey)) { @@ -264,7 +264,7 @@ public function modelCommand($packageKey, $modelName, $force = false) * @param boolean $force Overwrite any existing repository. * @see neos.kickstarter:kickstart:model */ - public function repositoryCommand($packageKey, $modelName, $force = false) + public function repositoryCommand($packageKey, $modelName, $force = false): void { $this->validatePackageKey($packageKey); if (!$this->packageManager->isPackageAvailable($packageKey)) { @@ -283,7 +283,7 @@ public function repositoryCommand($packageKey, $modelName, $force = false) * * @param string $packageKey The package key of the package for the documentation */ - public function documentationCommand($packageKey) + public function documentationCommand($packageKey): void { $this->validatePackageKey($packageKey); if (!$this->packageManager->isPackageAvailable($packageKey)) { @@ -303,7 +303,7 @@ public function documentationCommand($packageKey) * * @param string $packageKey The package key of the package for the translation * @param string $sourceLanguageKey The language key of the default language - * @param array $targetLanguageKeys Comma separated language keys for the target translations + * @param array $targetLanguageKeys Comma separated language keys for the target translations * @return void */ public function translationCommand($packageKey, $sourceLanguageKey, array $targetLanguageKeys = []) @@ -339,7 +339,7 @@ protected function validatePackageKey($packageKey) * @param string $modelName * @see http://www.php.net/manual/en/reserved.keywords.php */ - protected function validateModelName($modelName) + protected function validateModelName($modelName): void { if (Validation::isReservedKeyword($modelName)) { $this->outputLine('The name of the model cannot be one of the reserved words of PHP!'); diff --git a/Neos.Kickstarter/Classes/Service/GeneratorService.php b/Neos.Kickstarter/Classes/Service/GeneratorService.php index e872b5f181..7ff7b55ba2 100644 --- a/Neos.Kickstarter/Classes/Service/GeneratorService.php +++ b/Neos.Kickstarter/Classes/Service/GeneratorService.php @@ -51,9 +51,9 @@ class GeneratorService protected $reflectionService; /** - * @var array + * @var array */ - protected $generatedFiles = []; + protected array $generatedFiles = []; /** * Generate a controller with the given name for the given package @@ -63,7 +63,7 @@ class GeneratorService * @param string $controllerName The name of the new controller * @param boolean $fusionView If the controller should default to a FusionView * @param boolean $overwrite Overwrite any existing files? - * @return array An array of generated filenames + * @return array An array of generated filenames */ public function generateActionController($packageKey, $subpackage, $controllerName, $fusionView = false, $overwrite = false) { @@ -102,7 +102,7 @@ public function generateActionController($packageKey, $subpackage, $controllerNa * @param string $controllerName The name of the new controller * @param boolean $fusionView If the controller should default to a FusionView * @param boolean $overwrite Overwrite any existing files? - * @return array An array of generated filenames + * @return array An array of generated filenames */ public function generateCrudController($packageKey, $subpackage, $controllerName, $fusionView = false, $overwrite = false) { @@ -143,7 +143,7 @@ public function generateCrudController($packageKey, $subpackage, $controllerName * @param string $packageKey The package key of the controller's package * @param string $controllerName The name of the new controller * @param boolean $overwrite Overwrite any existing files? - * @return array An array of generated filenames + * @return array An array of generated filenames */ public function generateCommandController($packageKey, $controllerName, $overwrite = false) { @@ -179,7 +179,7 @@ public function generateCommandController($packageKey, $controllerName, $overwri * @param string $viewName The name of the view * @param string $templateName The name of the view * @param boolean $overwrite Overwrite any existing files? - * @return array An array of generated filenames + * @return array An array of generated filenames */ public function generateView($packageKey, $subpackage, $controllerName, $viewName, $templateName, $overwrite = false) { @@ -199,7 +199,9 @@ public function generateView($packageKey, $subpackage, $controllerName, $viewNam $contextVariables['modelFullClassName'] = '\\' . trim($baseNamespace, '\\') . ($subpackage != '' ? '\\' . $subpackage : '') . '\Domain\Model\\' . $controllerName; $contextVariables['modelClassName'] = ucfirst($contextVariables['modelName']); - $modelClassSchema = $this->reflectionService->getClassSchema($contextVariables['modelFullClassName']); + $modelClassSchema = class_exists($contextVariables['modelFullClassName']) + ? $this->reflectionService->getClassSchema($contextVariables['modelFullClassName']) + : null; if ($modelClassSchema !== null) { $contextVariables['properties'] = $modelClassSchema->getProperties(); if (isset($contextVariables['properties']['Persistence_Object_Identifier'])) { @@ -229,7 +231,7 @@ public function generateView($packageKey, $subpackage, $controllerName, $viewNam * @param string $packageKey The package key of the controller's package * @param string $layoutName The name of the layout * @param boolean $overwrite Overwrite any existing files? - * @return array An array of generated filenames + * @return array An array of generated filenames */ public function generateLayout($packageKey, $layoutName, $overwrite = false) { @@ -261,7 +263,7 @@ public function generateLayout($packageKey, $layoutName, $overwrite = false) * @param string $viewName The name of the view * @param string $templateName The name of the view * @param boolean $overwrite Overwrite any existing files? - * @return array An array of generated filenames + * @return array An array of generated filenames */ public function generateFusion(string $packageKey, string $subpackage, string $controllerName, string $viewName, string $templateName, bool $overwrite = false): array { @@ -281,7 +283,9 @@ public function generateFusion(string $packageKey, string $subpackage, string $c $contextVariables['modelFullClassName'] = '\\' . trim($baseNamespace, '\\') . ($subpackage != '' ? '\\' . $subpackage : '') . '\Domain\Model\\' . $controllerName; $contextVariables['modelClassName'] = ucfirst($contextVariables['modelName']); - $modelClassSchema = $this->reflectionService->getClassSchema($contextVariables['modelFullClassName']); + $modelClassSchema = class_exists($contextVariables['modelFullClassName']) + ? $this->reflectionService->getClassSchema($contextVariables['modelFullClassName']) + : null; if ($modelClassSchema !== null) { $contextVariables['properties'] = $modelClassSchema->getProperties(); if (isset($contextVariables['properties']['Persistence_Object_Identifier'])) { @@ -311,7 +315,7 @@ public function generateFusion(string $packageKey, string $subpackage, string $c * @param string $packageKey The package key of the controller's package * @param string $layoutName The name of the layout * @param boolean $overwrite Overwrite any existing files? - * @return array An array of generated filenames + * @return array An array of generated filenames */ public function generatePrototype(string $packageKey, string $layoutName, bool $overwrite = false): array { @@ -340,9 +344,9 @@ public function generatePrototype(string $packageKey, string $layoutName, bool $ * * @param string $packageKey The package key of the controller's package * @param string $modelName The name of the new model - * @param array $fieldDefinitions The field definitions + * @param array $fieldDefinitions The field definitions * @param boolean $overwrite Overwrite any existing files? - * @return array An array of generated filenames + * @return array An array of generated filenames */ public function generateModel($packageKey, $modelName, array $fieldDefinitions, $overwrite = false) { @@ -378,7 +382,7 @@ public function generateModel($packageKey, $modelName, array $fieldDefinitions, * @param string $packageKey The package key of the controller's package * @param string $modelName The name of the new model fpr which to generate the test * @param boolean $overwrite Overwrite any existing files? - * @return array An array of generated filenames + * @return array An array of generated filenames */ public function generateTestsForModel($packageKey, $modelName, $overwrite = false) { @@ -411,7 +415,7 @@ public function generateTestsForModel($packageKey, $modelName, $overwrite = fals * @param string $packageKey The package key * @param string $modelName The name of the model * @param boolean $overwrite Overwrite any existing files? - * @return array An array of generated filenames + * @return array An array of generated filenames */ public function generateRepository($packageKey, $modelName, $overwrite = false) { @@ -443,7 +447,7 @@ public function generateRepository($packageKey, $modelName, $overwrite = false) * Generate a documentation skeleton for the package key * * @param string $packageKey The package key - * @return array An array of generated filenames + * @return array An array of generated filenames */ public function generateDocumentation($packageKey) { @@ -459,7 +463,7 @@ public function generateDocumentation($packageKey) $templatePathAndFilename = 'resource://Neos.Kickstarter/Private/Generator/Documentation/Makefile'; $fileContent = file_get_contents($templatePathAndFilename); $targetPathAndFilename = $documentationPath . '/Makefile'; - $this->generateFile($targetPathAndFilename, $fileContent); + $this->generateFile($targetPathAndFilename, $fileContent ?: ''); $templatePathAndFilename = 'resource://Neos.Kickstarter/Private/Generator/Documentation/index.rst'; $fileContent = $this->renderTemplate($templatePathAndFilename, $contextVariables); @@ -481,8 +485,8 @@ public function generateDocumentation($packageKey) * * @param string $packageKey * @param string $sourceLanguageKey - * @param array $targetLanguageKeys - * @return array An array of generated filenames + * @param array $targetLanguageKeys + * @return array An array of generated filenames */ public function generateTranslation($packageKey, $sourceLanguageKey, array $targetLanguageKeys = []) { @@ -516,9 +520,9 @@ public function generateTranslation($packageKey, $sourceLanguageKey, array $targ /** * Normalize types and prefix types with namespaces * - * @param array $fieldDefinitions The field definitions + * @param array $fieldDefinitions The field definitions * @param string $namespace The namespace - * @return array The normalized and type converted field definitions + * @return array The normalized and type converted field definitions */ protected function normalizeFieldDefinitions(array $fieldDefinitions, $namespace = '') { @@ -574,7 +578,7 @@ protected function generateFile($targetPathAndFilename, $fileContent, $force = f * Render the given template file with the given variables * * @param string $templatePathAndFilename - * @param array $contextVariables + * @param array $contextVariables * @return string * @throws \Neos\FluidAdaptor\Core\Exception */ @@ -588,7 +592,7 @@ protected function renderTemplate($templatePathAndFilename, array $contextVariab /** * @param PackageInterface $package - * @return array + * @return array */ protected function getPrimaryNamespaceAndEntryPath(PackageInterface $package) { diff --git a/Neos.Kickstarter/Classes/Utility/Inflector.php b/Neos.Kickstarter/Classes/Utility/Inflector.php index ed402234f7..49fa0eb222 100644 --- a/Neos.Kickstarter/Classes/Utility/Inflector.php +++ b/Neos.Kickstarter/Classes/Utility/Inflector.php @@ -56,6 +56,6 @@ public function humanizeCamelCase($camelCased, $lowercase = false) */ protected function spacify($camelCased, $glue = ' ') { - return preg_replace('/([a-z0-9])([A-Z])/', '$1' . $glue . '$2', $camelCased); + return preg_replace('/([a-z0-9])([A-Z])/', '$1' . $glue . '$2', $camelCased) ?: ''; } } diff --git a/Neos.Utility.Arrays/Classes/Arrays.php b/Neos.Utility.Arrays/Classes/Arrays.php index 71a44f2aeb..dc765df08b 100644 --- a/Neos.Utility.Arrays/Classes/Arrays.php +++ b/Neos.Utility.Arrays/Classes/Arrays.php @@ -22,9 +22,9 @@ abstract class Arrays * Explodes a $string delimited by $delimiter and passes each item in the array through intval(). * Corresponds to explode(), but with conversion to integers for all values. * - * @param string $delimiter Delimiter string to explode with + * @param non-empty-string $delimiter Delimiter string to explode with * @param string $string The string to explode - * @return array Exploded values, all converted to integers + * @return array Exploded values, all converted to integers */ public static function integerExplode(string $delimiter, string $string): array { @@ -41,10 +41,10 @@ public static function integerExplode(string $delimiter, string $string): array * Explodes a string and trims all values for whitespace in the ends. * If $onlyNonEmptyValues is set, then all blank ('') values are removed. * - * @param string $delimiter Delimiter string to explode with + * @param non-empty-string $delimiter Delimiter string to explode with * @param string $string The string to explode * @param boolean $onlyNonEmptyValues If disabled, even empty values (='') will be set in output - * @return array Exploded values + * @return array Exploded values */ public static function trimExplode(string $delimiter, string $string, bool $onlyNonEmptyValues = true): array { @@ -64,11 +64,11 @@ public static function trimExplode(string $delimiter, string $string, bool $only * in the first array ($firstArray) with the values of the second array ($secondArray) in case of identical keys, * ie. keeping the values of the second. * - * @param array $firstArray First array - * @param array $secondArray Second array, overruling the first array + * @param array $firstArray First array + * @param array $secondArray Second array, overruling the first array * @param boolean $dontAddNewKeys If set, keys that are NOT found in $firstArray (first array) will not be set. Thus only existing value can/will be overruled from second array. * @param boolean $emptyValuesOverride If set (which is the default), values from $secondArray will overrule if they are empty (according to PHP's empty() function) - * @return array Resulting array where $secondArray values has overruled $firstArray values + * @return array Resulting array where $secondArray values has overruled $firstArray values */ public static function arrayMergeRecursiveOverrule(array $firstArray, array $secondArray, bool $dontAddNewKeys = false, bool $emptyValuesOverride = true): array { @@ -109,13 +109,13 @@ public static function arrayMergeRecursiveOverrule(array $firstArray, array $sec * Merges two arrays recursively and "binary safe" (integer keys are overridden as well), overruling similar values in the first array ($firstArray) with the values of the second array ($secondArray) * In case of identical keys, ie. keeping the values of the second. The given $toArray closure will be used if one of the two array keys contains an array and the other not. It should return an array. * - * @param array $firstArray First array - * @param array $secondArray Second array, overruling the first array + * @param array $firstArray First array + * @param array $secondArray Second array, overruling the first array * @param \Closure $toArray The given callable will get a value that is not an array and has to return an array. * This is to allow custom merging of simple types with (sub) arrays * @param \Closure|null $overrideFirst The given callable will determine whether the value of the first array should be overridden. * It should have the following signature $callable($key, ?array $firstValue = null, ?array $secondValue = null): bool - * @return array Resulting array where $secondArray values has overruled $firstArray values + * @return array Resulting array where $secondArray values has overruled $firstArray values */ public static function arrayMergeRecursiveOverruleWithCallback(array $firstArray, array $secondArray, \Closure $toArray, ?\Closure $overrideFirst = null): array { @@ -157,8 +157,7 @@ public static function arrayMergeRecursiveOverruleWithCallback(array $firstArray /** * Returns true if the given array contains elements of varying types * - * @param array $array - * @return boolean + * @param array $array */ public static function containsMultipleTypes(array $array): bool { @@ -179,12 +178,12 @@ public static function containsMultipleTypes(array $array): bool * Replacement for array_reduce that allows any type for $initial (instead * of only integer) * - * @param array $array the array to reduce - * @param string $function the reduce function with the same order of parameters as in the native array_reduce (i.e. accumulator first, then current array element) + * @param array $array the array to reduce + * @param callable $function the reduce function with the same order of parameters as in the native array_reduce (i.e. accumulator first, then current array element) * @param mixed $initial the initial accumulator value * @return mixed */ - public static function array_reduce(array $array, string $function, $initial = null) + public static function array_reduce(array $array, callable $function, $initial = null) { $accumulator = $initial; foreach ($array as $value) { @@ -196,8 +195,8 @@ public static function array_reduce(array $array, string $function, $initial = n /** * Returns the value of a nested array by following the specifed path. * - * @param array $array The array to traverse - * @param array|string $path The path to follow. Either a simple array of keys or a string in the format 'foo.bar.baz' + * @param array $array The array to traverse + * @param array|string $path The path to follow. Either a simple array of keys or a string in the format 'foo.bar.baz' * @return mixed The value found, NULL if the path didn't exist (note there is no way to distinguish between a found NULL value and "path not found") */ public static function getValueByPath(array $array, array|string $path): mixed @@ -222,8 +221,8 @@ public static function getValueByPath(array $array, array|string $path): mixed * See {@see ValueAccessor} * * @internal experimental feature, not stable API - * @param array $array The array to traverse - * @param array|string $path The path to follow. Either a simple array of keys or a string in the format 'foo.bar.baz' + * @param array $array The array to traverse + * @param array|string $path The path to follow. Either a simple array of keys or a string in the format 'foo.bar.baz' * @return ValueAccessor */ public static function getAccessorByPath(array $array, array|string $path): ValueAccessor @@ -235,21 +234,16 @@ public static function getAccessorByPath(array $array, array|string $path): Valu /** * Sets the given value in a nested array or object by following the specified path. * - * @param array|\ArrayAccess $subject The array or ArrayAccess instance to work on - * @param array|string $path The path to follow. Either a simple array of keys or a string in the format 'foo.bar.baz' + * @param array|\ArrayAccess $subject The array or ArrayAccess instance to work on + * @param array|string $path The path to follow. Either a simple array of keys or a string in the format 'foo.bar.baz' * @param mixed $value The value to set - * @return array|\ArrayAccess The modified array or object + * @return array|\ArrayAccess The modified array or object * @throws \InvalidArgumentException */ - public static function setValueByPath($subject, $path, $value) + public static function setValueByPath(array|\ArrayAccess $subject, array|string $path, $value) { - if (!is_array($subject) && !($subject instanceof \ArrayAccess)) { - throw new \InvalidArgumentException('setValueByPath() expects $subject to be array or an object implementing \ArrayAccess, "' . (is_object($subject) ? get_class($subject) : gettype($subject)) . '" given.', 1306424308); - } if (is_string($path)) { $path = explode('.', $path); - } elseif (!is_array($path)) { - throw new \InvalidArgumentException('setValueByPath() expects $path to be string or array, "' . gettype($path) . '" given.', 1305111499); } $key = array_shift($path); if (count($path) === 0) { @@ -266,17 +260,15 @@ public static function setValueByPath($subject, $path, $value) /** * Unsets an element/part of a nested array by following the specified path. * - * @param array $array The array - * @param array|string $path The path to follow. Either a simple array of keys or a string in the format 'foo.bar.baz' - * @return array The modified array + * @param array $array The array + * @param array|string $path The path to follow. Either a simple array of keys or a string in the format 'foo.bar.baz' + * @return array The modified array * @throws \InvalidArgumentException */ - public static function unsetValueByPath(array $array, $path): array + public static function unsetValueByPath(array $array, array|string $path): array { if (is_string($path)) { $path = explode('.', $path); - } elseif (!is_array($path)) { - throw new \InvalidArgumentException('unsetValueByPath() expects $path to be string or array, "' . gettype($path) . '" given.', 1305111513); } $key = array_shift($path); if (count($path) === 0) { @@ -293,7 +285,7 @@ public static function unsetValueByPath(array $array, $path): array /** * Sorts multidimensional arrays by recursively calling ksort on its elements. * - * @param array $array the array to sort + * @param array $array the array to sort * @param integer $sortFlags may be used to modify the sorting behavior using these values (see http://www.php.net/manual/en/function.sort.php) * @return boolean true on success, false on failure * @see asort() @@ -314,7 +306,7 @@ public static function sortKeysRecursively(array &$array, int $sortFlags = \SORT * Recursively convert an object hierarchy into an associative array. * * @param mixed $subject An object or array of objects - * @return array The subject represented as an array + * @return array The subject represented as an array * @throws \InvalidArgumentException */ public static function convertObjectToArray($subject): array @@ -336,8 +328,8 @@ public static function convertObjectToArray($subject): array /** * Recursively removes empty array elements. * - * @param array $array - * @return array the modified array + * @param array $array + * @return array the modified array */ public static function removeEmptyElementsRecursively(array $array): array { diff --git a/Neos.Utility.Arrays/Classes/PositionalArraySorter.php b/Neos.Utility.Arrays/Classes/PositionalArraySorter.php index e900ddd58d..fd05be60ed 100644 --- a/Neos.Utility.Arrays/Classes/PositionalArraySorter.php +++ b/Neos.Utility.Arrays/Classes/PositionalArraySorter.php @@ -44,18 +44,33 @@ */ final class PositionalArraySorter { + /** + * @var array + */ private array $startKeys; + /** + * @var array + */ private array $middleKeys; + /** + * @var array + */ private array $endKeys; + /** + * @var array + */ private array $beforeKeys; + /** + * @var array + */ private array $afterKeys; /** - * @param array $subject The source array to sort + * @param array $subject The source array to sort * @param string $positionPropertyPath optional property path to the string that contains the position * @param bool $removeNullValues if set to TRUE (default), null-values of the subject are removed */ @@ -69,7 +84,7 @@ public function __construct( /** * Returns a sorted copy of the subject array * - * @return array + * @return array * @throws Exception\InvalidPositionException */ public function toArray(): array @@ -87,7 +102,7 @@ public function toArray(): array * * TODO Detect circles in after / before dependencies (#52185) * - * @return array an ordered list of keys + * @return array an ordered list of keys * @throws Exception\InvalidPositionException if the positional string has an unsupported format */ public function getSortedKeys(): array @@ -112,6 +127,8 @@ public function getSortedKeys(): array * Extracts all "middle" keys from $arrayKeysWithPosition. Those are all keys with a numeric position. * The result is a multidimensional arrays where the KEY of each array is a PRIORITY and the VALUE is an array of matching KEYS * This also removes matching keys from the given $arrayKeysWithPosition + * + * @param array $arrayKeysWithPosition */ private function extractMiddleKeys(array &$arrayKeysWithPosition): void { @@ -130,6 +147,8 @@ private function extractMiddleKeys(array &$arrayKeysWithPosition): void * Extracts all "start" keys from $arrayKeysWithPosition. Those are all keys with a position starting with "start" * The result is a multidimensional arrays where the KEY of each array is a PRIORITY and the VALUE is an array of matching KEYS * This also removes matching keys from the given $arrayKeysWithPosition + * + * @param array $arrayKeysWithPosition */ private function extractStartKeys(array &$arrayKeysWithPosition): void { @@ -152,6 +171,8 @@ private function extractStartKeys(array &$arrayKeysWithPosition): void * Extracts all "end" keys from $arrayKeysWithPosition. Those are all keys with a position starting with "end" * The result is a multidimensional arrays where the KEY of each array is a PRIORITY and the VALUE is an array of matching KEYS * This also removes matching keys from the given $arrayKeysWithPosition + * + * @param array $arrayKeysWithPosition */ private function extractEndKeys(array &$arrayKeysWithPosition): void { @@ -174,6 +195,9 @@ private function extractEndKeys(array &$arrayKeysWithPosition): void * Extracts all "before" keys from $arrayKeysWithPosition. Those are all keys with a position starting with "before" * The result is a multidimensional arrays where the KEY of each array is a PRIORITY and the VALUE is an array of matching KEYS * This also removes matching keys from the given $arrayKeysWithPosition + * + * @param array $arrayKeysWithPosition + * @param array $existingKeys */ private function extractBeforeKeys(array &$arrayKeysWithPosition, array $existingKeys): void { @@ -201,6 +225,9 @@ private function extractBeforeKeys(array &$arrayKeysWithPosition, array $existin * Extracts all "after" keys from $arrayKeysWithPosition. Those are all keys with a position starting with "after" * The result is a multidimensional arrays where the KEY of each array is a PRIORITY and the VALUE is an array of matching KEYS * This also removes matching keys from the given $arrayKeysWithPosition + * + * @param array $arrayKeysWithPosition + * @param array $existingKeys */ private function extractAfterKeys(array &$arrayKeysWithPosition, array $existingKeys): void { @@ -228,7 +255,7 @@ private function extractAfterKeys(array &$arrayKeysWithPosition, array $existing * Collect the array keys inside $this->subject with each position meta-argument. * If there is no position but the array is numerically ordered, we use the array index as position. * - * @return array an associative array where each key of $subject has a position string assigned + * @return array an associative array where each key of $subject has a position string assigned */ private function collectArrayKeysAndPositions(): array { @@ -253,6 +280,8 @@ private function collectArrayKeysAndPositions(): array /** * Flattens start-, middle-, end-, before- and afterKeys to a single dimension and merges them together to a single array + * + * @return array */ private function generateSortedKeysMap(): array { diff --git a/Neos.Utility.Arrays/Classes/ValueAccessor.php b/Neos.Utility.Arrays/Classes/ValueAccessor.php index 060ee0c8d7..5bfb0e7852 100644 --- a/Neos.Utility.Arrays/Classes/ValueAccessor.php +++ b/Neos.Utility.Arrays/Classes/ValueAccessor.php @@ -47,6 +47,7 @@ public static function forValue(mixed $value): self /** * @internal You should use {@see ValueAccessor::forValue} instead + * @param array $path */ public static function forValueInPath(mixed $value, array|string $path): self { @@ -97,6 +98,9 @@ public function classString(): string throw $this->createTypeError("is not a class-string"); } + /** + * @return array + */ public function array(): array { if (is_array($this->value)) { @@ -161,6 +165,9 @@ public function classStringOrNull(): ?string throw $this->createTypeError("is not a class-string"); } + /** + * @return array|null + */ public function arrayOrNull(): ?array { if (is_array($this->value) || is_null($this->value)) { @@ -182,7 +189,7 @@ public function instanceOfOrNull(string $className): ?object throw $this->createTypeError(sprintf('is not an instance of %s or null', $className)); } - private function createTypeError($message): \UnexpectedValueException + private function createTypeError(string $message): \UnexpectedValueException { return new \UnexpectedValueException(get_debug_type($this->value) . ' ' . $message . ($this->additionalErrorMessage ? ' ' . $this->additionalErrorMessage : '')); } diff --git a/Neos.Utility.Files/Classes/Files.php b/Neos.Utility.Files/Classes/Files.php index b0ff9b5b91..136bbed8e4 100644 --- a/Neos.Utility.Files/Classes/Files.php +++ b/Neos.Utility.Files/Classes/Files.php @@ -32,7 +32,7 @@ public static function getUnixStylePath(string $path): string if (strpos($path, ':') === false) { return str_replace(['//', '\\'], '/', $path); } - return preg_replace('/^([a-z]{2,}):\//', '$1://', str_replace(['//', '\\'], '/', $path)); + return preg_replace('/^([a-z]{2,}):\//', '$1://', str_replace(['//', '\\'], '/', $path)) ?: ''; } /** @@ -53,7 +53,7 @@ public static function getNormalizedPath(string $path): string * Note: trailing slashes will be removed, leading slashes won't. * Usage: concatenatePaths(array('dir1/dir2', 'dir3', 'file')) * - * @param array $paths the file paths to be combined. Last array element may include the filename. + * @param array $paths the file paths to be combined. Last array element may include the filename. * @return string concatenated path without trailing slash. * @see getUnixStylePath() * @api @@ -80,10 +80,10 @@ public static function concatenatePaths(array $paths): string * directories. * * @param string $path Path to the directory which shall be read - * @param string $suffix If specified, only filenames with this extension are returned (eg. ".php" or "foo.bar") + * @param ?string $suffix If specified, only filenames with this extension are returned (eg. ".php" or "foo.bar") * @param boolean $returnRealPath If turned on, all paths are resolved by calling realpath() * @param boolean $returnDotFiles If turned on, also files beginning with a dot will be returned - * @return array Filenames including full path + * @return array Filenames including full path * @api */ public static function readDirectoryRecursively(string $path, ?string $suffix = null, bool $returnRealPath = false, bool $returnDotFiles = false): array @@ -122,7 +122,7 @@ public static function getRecursiveDirectoryGenerator(string $path, ?string $suf if (is_dir($pathAndFilename)) { array_push($directories, self::getNormalizedPath($pathAndFilename)); } elseif ($suffix === null || strpos(strrev($filename), strrev($suffix)) === 0) { - yield static::getUnixStylePath(($returnRealPath === true) ? realpath($pathAndFilename) : $pathAndFilename); + yield static::getUnixStylePath(($returnRealPath === true) ? realpath($pathAndFilename) ?: '' : $pathAndFilename); } } closedir($handle); @@ -153,6 +153,9 @@ public static function emptyDirectoryRecursively(string $path) } else { $directoryIterator = new \RecursiveDirectoryIterator($path); foreach ($directoryIterator as $fileInfo) { + if (is_string($fileInfo)) { + continue; + } if (!$fileInfo->isDir()) { if (self::unlink($fileInfo->getPathname()) !== true) { throw new FilesException('Could not unlink file "' . $fileInfo->getPathname() . '".', 1169047619); @@ -322,14 +325,12 @@ public static function copyDirectoryRecursively(string $sourceDirectory, string */ public static function getFileContents(string $pathAndFilename, int $flags = 0, $context = null, int $offset = 0, int $maximumLength = -1) { - if ($flags === true) { - $flags = FILE_USE_INCLUDE_PATH; - } + $useIncludePath = ($flags & FILE_USE_INCLUDE_PATH) === 1; try { if ($maximumLength > -1) { - $content = file_get_contents($pathAndFilename, $flags, $context, $offset, $maximumLength); + $content = file_get_contents($pathAndFilename, $useIncludePath, $context, $offset, $maximumLength); } else { - $content = file_get_contents($pathAndFilename, $flags, $context, $offset); + $content = file_get_contents($pathAndFilename, $useIncludePath, $context, $offset); } } catch (ErrorException $ignoredException) { $content = false; @@ -386,7 +387,7 @@ public static function is_link(string $pathAndFilename): bool return false; } $normalizedPathAndFilename = strtolower(rtrim(self::getUnixStylePath($pathAndFilename), '/')); - $normalizedTargetPathAndFilename = strtolower(self::getUnixStylePath(realpath($pathAndFilename))); + $normalizedTargetPathAndFilename = strtolower(self::getUnixStylePath(realpath($pathAndFilename) ?: '')); if ($normalizedTargetPathAndFilename === '') { return false; } @@ -431,20 +432,19 @@ public static function unlink(string $pathAndFilename): bool /** * Supported file size units for the byte conversion functions below * - * @var array + * @var array */ - protected static $sizeUnits = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + protected static array $sizeUnits = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; /** * Converts an integer with a byte count into human-readable form * - * @param float|integer $bytes - * @param integer $decimals number of decimal places in the resulting string - * @param string $decimalSeparator decimal separator of the resulting string - * @param string $thousandsSeparator thousands separator of the resulting string + * @param integer|null $decimals number of decimal places in the resulting string + * @param string|null $decimalSeparator decimal separator of the resulting string + * @param string|null $thousandsSeparator thousands separator of the resulting string * @return string the size string, e.g. "1,024 MB" */ - public static function bytesToSizeString($bytes, ?int $decimals = null, ?string $decimalSeparator = null, ?string $thousandsSeparator = null): string + public static function bytesToSizeString(float|int|string $bytes, ?int $decimals = null, ?string $decimalSeparator = null, ?string $thousandsSeparator = null): string { if (!is_int($bytes) && !is_float($bytes)) { if (is_numeric($bytes)) { @@ -502,7 +502,7 @@ public static function sizeStringToBytes(string $sizeString): float if ($pow === false) { throw new FilesException(sprintf('Unknown file size unit "%s"', $matches['unit']), 1417695299); } - return $size * pow(2, (10 * $pow)); + return $size * pow(2, (10 * (int)$pow)); } /** diff --git a/Neos.Utility.MediaTypes/Classes/MediaTypes.php b/Neos.Utility.MediaTypes/Classes/MediaTypes.php index 249d21ced8..65c0db4f0c 100644 --- a/Neos.Utility.MediaTypes/Classes/MediaTypes.php +++ b/Neos.Utility.MediaTypes/Classes/MediaTypes.php @@ -33,7 +33,7 @@ abstract class MediaTypes /** * A map of file extensions to Internet Media Types * - * @var array + * @var array */ private static $extensionToMediaType = [ '3dml' => 'text/vnd.in3d.3dml', @@ -1027,7 +1027,7 @@ abstract class MediaTypes /** * A map of Internet Media Types to file extensions * - * @var array + * @var array> */ private static $mediaTypeToFileExtension = [ 'application/andrew-inset' => ['ez'], @@ -1821,13 +1821,13 @@ public static function getMediaTypeFromFilename(string $filename): string * Returns a Media Type based on the file content * * @param string $fileContent The file content do determine the media type from - * @return string The IANA Internet Media Type + * @return string|null The IANA Internet Media Type */ - public static function getMediaTypeFromFileContent(string $fileContent): string + public static function getMediaTypeFromFileContent(string $fileContent): ?string { $fileInfo = new \finfo(FILEINFO_MIME); - $mediaType = self::trimMediaType($fileInfo->buffer($fileContent)); - return isset(self::$mediaTypeToFileExtension[$mediaType]) ? $mediaType : 'application/octet-stream'; + $mediaType = self::trimMediaType($fileInfo->buffer($fileContent) ?: ''); + return ($mediaType === null || isset(self::$mediaTypeToFileExtension[$mediaType])) ? $mediaType : 'application/octet-stream'; } /** @@ -1846,7 +1846,7 @@ public static function getFilenameExtensionFromMediaType(string $mediaType): str * Returns all possible filename extensions based on the given Media Type. * * @param string $mediaType The IANA Internet Media Type, for example "text/html" - * @return array The corresponding filename extensions, for example ("html", "htm") + * @return array The corresponding filename extensions, for example ("html", "htm") * @api */ public static function getFilenameExtensionsFromMediaType(string $mediaType): array @@ -1867,7 +1867,7 @@ public static function getFilenameExtensionsFromMediaType(string $mediaType): ar * "parameters" => an array of parameter names and values, array("charset" => "UTF-8") * * @param string $rawMediaType The raw media type, for example "application/json; charset=UTF-8" - * @return array An associative array with parsed information + * @return array{type: string, subtype: string, parameters: array} An associative array with parsed information */ public static function parseMediaType(string $rawMediaType): array { @@ -1921,9 +1921,9 @@ public static function mediaRangeMatches(string $mediaRange, string $mediaType): * and subtype in the format "type/subtype". * * @param string $rawMediaType The full media type, for example "application/json; charset=UTF-8" - * @return string Just the type and subtype, for example "application/json" + * @return string|null Just the type and subtype, for example "application/json" */ - public static function trimMediaType(string $rawMediaType) + public static function trimMediaType(string $rawMediaType): ?string { $pieces = self::parseMediaType($rawMediaType); return trim(sprintf('%s/%s', $pieces['type'], $pieces['subtype']), '/') ?: null; diff --git a/Neos.Utility.ObjectHandling/Classes/ObjectAccess.php b/Neos.Utility.ObjectHandling/Classes/ObjectAccess.php index 89decda676..cb15a28b43 100644 --- a/Neos.Utility.ObjectHandling/Classes/ObjectAccess.php +++ b/Neos.Utility.ObjectHandling/Classes/ObjectAccess.php @@ -31,15 +31,15 @@ abstract class ObjectAccess { /** * Internal RuntimeCache for getGettablePropertyNames() - * @var array + * @var array,array> */ - protected static $gettablePropertyNamesCache = []; + protected static array $gettablePropertyNamesCache = []; /** * Internal RuntimeCache for getPropertyInternal() - * @var array + * @var array */ - protected static $propertyGetterCache = []; + protected static array $propertyGetterCache = []; const ACCESS_GET = 0; const ACCESS_SET = 1; @@ -58,22 +58,14 @@ abstract class ObjectAccess * - if public property exists, return the value of it. * - else, throw exception * - * @param mixed $subject Object or array to get the property from + * @param object|array $subject Object or array to get the property from * @param string|integer $propertyName Name or index of the property to retrieve * @param boolean $forceDirectAccess Directly access property using reflection(!) * @return mixed Value of the property - * @throws \InvalidArgumentException in case $subject was not an object or $propertyName was not a string * @throws PropertyNotAccessibleException if the property was not accessible */ - public static function getProperty($subject, $propertyName, bool $forceDirectAccess = false) + public static function getProperty(object|array $subject, string|int $propertyName, bool $forceDirectAccess = false) { - if (!is_object($subject) && !is_array($subject)) { - throw new \InvalidArgumentException('$subject must be an object or array, ' . gettype($subject) . ' given.', 1237301367); - } - if (!is_string($propertyName) && !is_int($propertyName)) { - throw new \InvalidArgumentException('Given property name/index is not of type string or integer.', 1231178303); - } - $propertyExists = false; $propertyValue = self::getPropertyInternal($subject, $propertyName, $forceDirectAccess, $propertyExists); if ($propertyExists === true) { @@ -91,14 +83,14 @@ public static function getProperty($subject, $propertyName, bool $forceDirectAcc * of type string you should use getProperty() instead. * * @param mixed $subject Object or array to get the property from - * @param string $propertyName name of the property to retrieve + * @param string|int $propertyName name of the property to retrieve * @param boolean $forceDirectAccess directly access property using reflection(!) * @param boolean $propertyExists (by reference) will be set to true if the specified property exists and is gettable * @return mixed Value of the property * @throws PropertyNotAccessibleException * @see getProperty() */ - protected static function getPropertyInternal($subject, string $propertyName, bool $forceDirectAccess, bool &$propertyExists) + protected static function getPropertyInternal($subject, string|int $propertyName, bool $forceDirectAccess, bool &$propertyExists) { if ($subject === null) { return null; @@ -111,7 +103,12 @@ protected static function getPropertyInternal($subject, string $propertyName, bo return null; } + if (is_int($propertyName)) { + throw new \InvalidArgumentException('Cannot use integer property names for objects', 1743799241); + } + $propertyExists = true; + /** @var string $className safe for objects */ $className = TypeHandling::getTypeForValue($subject); if ($forceDirectAccess === true) { @@ -230,39 +227,33 @@ public static function getPropertyPath($subject, string $propertyPath) * on it without checking if it existed. * - else, return false * - * @param mixed $subject The target object or array + * @template T of object + * @param array|T &$subject The target object or array * @param string|integer $propertyName Name or index of the property to set * @param mixed $propertyValue Value of the property * @param boolean $forceDirectAccess directly access property using reflection(!) + * @param-out ($subject is array ? array : T) $subject * @return boolean true if the property could be set, false otherwise - * @throws \InvalidArgumentException in case $object was not an object or $propertyName was not a string */ - public static function setProperty(&$subject, $propertyName, $propertyValue, bool $forceDirectAccess = false): bool + public static function setProperty(array|object &$subject, string|int $propertyName, $propertyValue, bool $forceDirectAccess = false): bool { - if (!is_string($propertyName) && !is_int($propertyName)) { - throw new \InvalidArgumentException('Given property name/index is not of type string or integer.', 1231178878); - } - if (is_array($subject)) { $subject[$propertyName] = $propertyValue; return true; } - if (!is_object($subject)) { - throw new \InvalidArgumentException('subject must be an object or array, ' . gettype($subject) . ' given.', 1237301368); - } - if ($forceDirectAccess === true) { if (!is_string($propertyName)) { throw new \InvalidArgumentException('Given property name is not of type string.', 1648244846); } + /** @var string $className safe for objects */ $className = TypeHandling::getTypeForValue($subject); if (property_exists($className, $propertyName)) { $propertyReflection = new \ReflectionProperty($className, $propertyName); $propertyReflection->setAccessible(true); $propertyReflection->setValue($subject, $propertyValue); - } elseif ($subject instanceof ProxyInterface && property_exists(get_parent_class($className), $propertyName)) { - $propertyReflection = new \ReflectionProperty(get_parent_class($className), $propertyName); + } elseif ($subject instanceof ProxyInterface && property_exists(get_parent_class($className) ?: $className, $propertyName)) { + $propertyReflection = new \ReflectionProperty(get_parent_class($className) ?: $className, $propertyName); $propertyReflection->setAccessible(true); $propertyReflection->setValue($subject, $propertyValue); } else { @@ -290,19 +281,17 @@ public static function setProperty(&$subject, $propertyName, $propertyValue, boo * - public properties which can be directly get. * * @param object $object Object to receive property names for - * @return array Array of all gettable property names + * @return array Array of all gettable property names * @throws \InvalidArgumentException */ - public static function getGettablePropertyNames($object): array + public static function getGettablePropertyNames(object $object): array { - if (!is_object($object)) { - throw new \InvalidArgumentException('$object must be an object, ' . gettype($object) . ' given.', 1237301369); - } if ($object instanceof \stdClass) { $declaredPropertyNames = array_keys(get_object_vars($object)); $className = 'stdClass'; unset(self::$gettablePropertyNamesCache[$className]); } else { + /** @var string $className safe for objects */ $className = TypeHandling::getTypeForValue($object); $declaredPropertyNames = array_keys(get_class_vars($className)); } @@ -336,17 +325,15 @@ public static function getGettablePropertyNames($object): array * - public properties which can be directly set. * * @param object $object Object to receive property names for - * @return array Array of all settable property names + * @return array Array of all settable property names * @throws \InvalidArgumentException */ - public static function getSettablePropertyNames($object): array + public static function getSettablePropertyNames(object $object): array { - if (!is_object($object)) { - throw new \InvalidArgumentException('$object must be an object, ' . gettype($object) . ' given.', 1264022994); - } if ($object instanceof \stdClass) { $declaredPropertyNames = array_keys(get_object_vars($object)); } else { + /** @var string $className safe for object */ $className = TypeHandling::getTypeForValue($object); $declaredPropertyNames = array_keys(get_class_vars($className)); } @@ -370,12 +357,9 @@ public static function getSettablePropertyNames($object): array * @return boolean * @throws \InvalidArgumentException */ - public static function isPropertySettable($object, string $propertyName): bool + public static function isPropertySettable(object $object, string $propertyName): bool { - if (!is_object($object)) { - throw new \InvalidArgumentException('$object must be an object, ' . gettype($object) . ' given.', 1259828920); - } - + /** @var string $className safe for objects */ $className = TypeHandling::getTypeForValue($object); if (($object instanceof \stdClass && array_key_exists($propertyName, get_object_vars($object))) || array_key_exists($propertyName, get_class_vars($className))) { return true; @@ -391,18 +375,19 @@ public static function isPropertySettable($object, string $propertyName): bool * @return boolean * @throws \InvalidArgumentException */ - public static function isPropertyGettable($object, string $propertyName): bool + public static function isPropertyGettable(object $object, string $propertyName): bool { - if (!is_object($object)) { - throw new \InvalidArgumentException('$object must be an object, ' . gettype($object) . ' given.', 1259828921); - } - if (($object instanceof \ArrayAccess && $object->offsetExists($propertyName)) || ($object instanceof \stdClass && array_key_exists($propertyName, get_object_vars($object)))) { + if ( + $object instanceof \ArrayAccess && $object->offsetExists($propertyName) + || $object instanceof \stdClass && array_key_exists($propertyName, get_object_vars($object)) + ) { return true; } $uppercasePropertyName = ucfirst($propertyName); if (is_callable([$object, 'get' . $uppercasePropertyName]) || is_callable([$object, 'is' . $uppercasePropertyName]) || is_callable([$object, 'has' . $uppercasePropertyName])) { return true; } + /** @var string $className safe for objects */ $className = TypeHandling::getTypeForValue($object); return array_key_exists($propertyName, get_class_vars($className)); } @@ -412,15 +397,11 @@ public static function isPropertyGettable($object, string $propertyName): bool * $object that are accessible through this class. * * @param object $object Object to get all properties from. - * @return array Associative array of all properties. - * @throws \InvalidArgumentException + * @return array Associative array of all properties. * @todo What to do with ArrayAccess */ - public static function getGettableProperties($object): array + public static function getGettableProperties(object $object): array { - if (!is_object($object)) { - throw new \InvalidArgumentException('$object must be an object, ' . gettype($object) . ' given.', 1237301370); - } $properties = []; foreach (self::getGettablePropertyNames($object) as $propertyName) { $propertyExists = false; diff --git a/Neos.Utility.ObjectHandling/Classes/TypeHandling.php b/Neos.Utility.ObjectHandling/Classes/TypeHandling.php index c078b86b71..f858586827 100644 --- a/Neos.Utility.ObjectHandling/Classes/TypeHandling.php +++ b/Neos.Utility.ObjectHandling/Classes/TypeHandling.php @@ -31,16 +31,16 @@ abstract class TypeHandling const LITERAL_TYPE_PATTERN = '/^(?:integer|int|float|double|boolean|bool|string)$/'; /** - * @var array + * @var array>> */ - protected static $collectionTypes = ['array', \Traversable::class]; + protected static array $collectionTypes = ['array', \Traversable::class]; /** * Returns an array with type information, including element type for * collection types (array, SplObjectStorage, ...) * * @param string $type Type of the property (see PARSE_TYPE_PATTERN) - * @return array An array with information about the type + * @return array{type: string, elementType: ?string, nullable: bool} An array with information about the type * @throws InvalidTypeException */ public static function parseType(string $type): array @@ -52,7 +52,7 @@ public static function parseType(string $type): array $typeWithoutNull = self::stripNullableType($type); $isNullable = $typeWithoutNull !== $type || $type === 'null'; - $type = self::normalizeType($matches['type']); + $type = self::normalizeType($matches['type'] ?? ''); $elementType = isset($matches['elementType']) ? self::normalizeType($matches['elementType']) : null; if ($elementType !== null && !self::isCollectionType($type)) { @@ -116,7 +116,7 @@ public static function isSimpleType(string $type): bool /** * Returns true if the $type is a collection type. * - * @param string $type + * @param string|class-string $type * @return boolean */ public static function isCollectionType(string $type): bool @@ -127,8 +127,11 @@ public static function isCollectionType(string $type): bool if (class_exists($type) === true || interface_exists($type) === true) { foreach (self::$collectionTypes as $collectionType) { - if (is_subclass_of($type, $collectionType) === true) { - return true; + if (class_exists($collectionType)) { + /** @var class-string<\Traversable> $collectionType */ + if (is_subclass_of($type, $collectionType) === true) { + return true; + } } } } @@ -185,21 +188,23 @@ public static function stripNullableType($type) if (stripos($type, 'null') === false) { return $type; } - return preg_replace('/(\\|null|null\\|)/i', '', $type); + return preg_replace('/(\\|null|null\\|)/i', '', $type) ?: ''; } /** * Return simple type or class for object * * @param mixed $value - * @return string + * @return ($value is object ? class-string : ?string) */ - public static function getTypeForValue($value): string + public static function getTypeForValue($value): ?string { if (is_object($value)) { if ($value instanceof Proxy) { + /** @var class-string $type */ $type = get_parent_class($value); } else { + /** @var class-string $type */ $type = get_class($value); } } else { diff --git a/Neos.Utility.Pdo/Classes/PdoHelper.php b/Neos.Utility.Pdo/Classes/PdoHelper.php index 873ef34621..40ac1dac76 100644 --- a/Neos.Utility.Pdo/Classes/PdoHelper.php +++ b/Neos.Utility.Pdo/Classes/PdoHelper.php @@ -33,7 +33,7 @@ abstract class PdoHelper * @param \PDO $databaseHandle * @param string $pdoDriver * @param string $pathAndFilename - * @param array $replacePairs every key in this array will be replaced with the corresponding value in the loaded SQL (example: ['###CACHE_TABLE_NAME###' => 'caches', '###TAGS_TABLE_NAME###' => 'tags']) + * @param array $replacePairs every key in this array will be replaced with the corresponding value in the loaded SQL (example: ['###CACHE_TABLE_NAME###' => 'caches', '###TAGS_TABLE_NAME###' => 'tags']) * @return void */ public static function importSql(\PDO $databaseHandle, string $pdoDriver, string $pathAndFilename, array $replacePairs = []) @@ -46,13 +46,13 @@ public static function importSql(\PDO $databaseHandle, string $pdoDriver, string } else { $sql = file($pathAndFilename, FILE_IGNORE_NEW_LINES & FILE_SKIP_EMPTY_LINES); // Remove MySQL style key length delimiters (yuck!) if we are not setting up a MySQL db - if ($pdoDriver !== 'mysql') { + if ($sql !== false && $pdoDriver !== 'mysql') { $sql = preg_replace('/"\([0-9]+\)/', '"', $sql); } } $statement = ''; - foreach ($sql as $line) { + foreach ($sql ?: [] as $line) { $statement .= ' ' . trim(strtr($line, $replacePairs)); if (substr($statement, -1) === ';') { $databaseHandle->exec($statement); diff --git a/Neos.Utility.Schema/Classes/SchemaGenerator.php b/Neos.Utility.Schema/Classes/SchemaGenerator.php index 07a977c120..5ac82327d2 100644 --- a/Neos.Utility.Schema/Classes/SchemaGenerator.php +++ b/Neos.Utility.Schema/Classes/SchemaGenerator.php @@ -26,7 +26,7 @@ class SchemaGenerator * Generate a schema for the given value * * @param mixed $value value to create a schema for - * @return array schema as array structure + * @return array schema as array structure */ public function generate($value): array { @@ -62,8 +62,8 @@ public function generate($value): array /** * Create a schema for a dictionary * - * @param array $dictionaryValue - * @return array + * @param array $dictionaryValue + * @return array{type: string, properties: array} */ protected function generateDictionarySchema(array $dictionaryValue): array { @@ -78,8 +78,8 @@ protected function generateDictionarySchema(array $dictionaryValue): array /** * Create a schema for an array structure * - * @param array $arrayValue - * @return array schema + * @param array $arrayValue + * @return array schema */ protected function generateArraySchema(array $arrayValue): array { @@ -96,7 +96,7 @@ protected function generateArraySchema(array $arrayValue): array * Create a schema for a given string * * @param string $stringValue - * @return array + * @return array */ protected function generateStringSchema(string $stringValue): array { @@ -122,7 +122,7 @@ protected function generateStringSchema(string $stringValue): array * Compact an array of items to avoid adding the same value more than once. * If the result contains only one item, that item is returned directly. * - * @param array $values array of values + * @param array $values array of values * @return mixed */ protected function filterDuplicatesFromArray(array $values) diff --git a/Neos.Utility.Schema/Classes/SchemaValidator.php b/Neos.Utility.Schema/Classes/SchemaValidator.php index 64fec01df2..82427d9827 100644 --- a/Neos.Utility.Schema/Classes/SchemaValidator.php +++ b/Neos.Utility.Schema/Classes/SchemaValidator.php @@ -69,7 +69,7 @@ class SchemaValidator * * @param mixed $value value to validate * @param mixed $schema type as string, schema or array of schemas - * @param array $types the additional type schemas + * @param array $types the additional type schemas * @return ErrorResult */ public function validate($value, $schema, array $types = []): ErrorResult @@ -147,8 +147,8 @@ public function validate($value, $schema, array $types = []): ErrorResult * Validate a value for a given type * * @param mixed $value - * @param array $schema - * @param array $types + * @param array $schema + * @param array $types * @return ErrorResult */ protected function validateType($value, array $schema, array $types = []): ErrorResult @@ -209,8 +209,8 @@ protected function validateType($value, array $schema, array $types = []): Error * Validate a value with a given list of allowed types * * @param mixed $value - * @param array $schema - * @param array $types + * @param array $schema + * @param array $types * @return ErrorResult */ protected function validateTypeArray($value, array $schema, array $types = []): ErrorResult @@ -254,7 +254,7 @@ protected function validateTypeArray($value, array $schema, array $types = []): * - divisibleBy : value is divisibleBy the given number * * @param mixed $value - * @param array $schema + * @param array $schema * @return ErrorResult */ protected function validateNumberType($value, array $schema): ErrorResult @@ -305,7 +305,7 @@ protected function validateNumberType($value, array $schema): ErrorResult * * @see SchemaValidator::validateNumberType * @param mixed $value - * @param array $schema + * @param array $schema * @return ErrorResult */ protected function validateIntegerType($value, array $schema): ErrorResult @@ -323,7 +323,7 @@ protected function validateIntegerType($value, array $schema): ErrorResult * Validate a boolean value with the given schema * * @param mixed $value - * @param array $schema + * @param array $schema * @return ErrorResult */ protected function validateBooleanType($value, array $schema): ErrorResult @@ -346,8 +346,8 @@ protected function validateBooleanType($value, array $schema): ErrorResult * - uniqueItems : allow only unique values * * @param mixed $value - * @param array $schema - * @param array $types + * @param array $schema + * @param array $types * @return ErrorResult */ protected function validateArrayType($value, array $schema, array $types = []): ErrorResult @@ -404,8 +404,8 @@ protected function validateArrayType($value, array $schema, array $types = []): * - additionalProperties : if false is given all additionalProperties are forbidden, if a schema is given all additional properties have to match the schema * * @param mixed $value - * @param array $schema - * @param array $types + * @param array $schema + * @param array $types * @return ErrorResult */ protected function validateDictionaryType($value, array $schema, array $types = []): ErrorResult @@ -496,8 +496,8 @@ protected function validateDictionaryType($value, array $schema, array $types = * [date-time|date|time|uri|email|ipv4|ipv6|ip-address|host-name|class-name|interface-name] * * @param mixed $value - * @param array $schema - * @param array $types + * @param array $schema + * @param array $types * @return ErrorResult */ protected function validateStringType($value, array $schema, array $types = []): ErrorResult @@ -530,6 +530,7 @@ protected function validateStringType($value, array $schema, array $types = []): switch ($schema['format']) { case 'date-time': // YYYY-MM-DDThh:mm:ssZ ISO8601 + /** @phpstan-ignore staticMethod.resultUnused (we only care about the error) */ \DateTime::createFromFormat(\DateTime::ISO8601, $value); $parseErrors = \DateTime::getLastErrors(); if ($parseErrors && $parseErrors['error_count'] > 0) { @@ -538,6 +539,7 @@ protected function validateStringType($value, array $schema, array $types = []): break; case 'date': // YYYY-MM-DD + /** @phpstan-ignore staticMethod.resultUnused (we only care about the error) */ \DateTime::createFromFormat('Y-m-d', $value); $parseErrors = \DateTime::getLastErrors(); if ($parseErrors && $parseErrors['error_count'] > 0) { @@ -546,6 +548,7 @@ protected function validateStringType($value, array $schema, array $types = []): break; case 'time': // hh:mm:ss + /** @phpstan-ignore staticMethod.resultUnused (we only care about the error) */ \DateTime::createFromFormat('H:i:s', $value); $parseErrors = \DateTime::getLastErrors(); if ($parseErrors && $parseErrors['error_count'] > 0) { @@ -604,7 +607,7 @@ protected function validateStringType($value, array $schema, array $types = []): * Validate a null value with the given schema * * @param mixed $value - * @param array $schema + * @param array $schema * @return ErrorResult */ protected function validateNullType($value, array $schema): ErrorResult @@ -620,7 +623,7 @@ protected function validateNullType($value, array $schema): ErrorResult * Validate any value with the given schema. Return always a valid Result. * * @param mixed $value - * @param array $schema + * @param array $schema * @return ErrorResult */ protected function validateAnyType($value, array $schema): ErrorResult @@ -665,7 +668,7 @@ protected function createError(string $expectation, $value = null): Error * Determine whether the given php array is a schema or not * * @todo there should be a more sophisticated way to detect schemas - * @param array $phpArray + * @param array $phpArray * @return boolean */ protected function isSchema(array $phpArray): bool @@ -677,7 +680,7 @@ protected function isSchema(array $phpArray): bool /** * Determine whether the given php array is a plain numerically indexed array * - * @param array $phpArray + * @param array $phpArray * @return boolean */ protected function isNumericallyIndexedArray(array $phpArray): bool @@ -693,7 +696,7 @@ protected function isNumericallyIndexedArray(array $phpArray): bool /** * Determine whether the given php array is a Dictionary (has no numeric identifiers) * - * @param array $phpArray + * @param array $phpArray * @return boolean */ protected function isDictionary(array $phpArray): bool diff --git a/Neos.Utility.Unicode/Classes/Functions.php b/Neos.Utility.Unicode/Classes/Functions.php index a13429ebb5..806227f83d 100644 --- a/Neos.Utility.Unicode/Classes/Functions.php +++ b/Neos.Utility.Unicode/Classes/Functions.php @@ -31,7 +31,7 @@ public static function strtotitle(string $string): string { $result = ''; $splitIntoLowerCaseWords = preg_split("/([\n\r\t ])/", self::strtolower($string), -1, PREG_SPLIT_DELIM_CAPTURE); - foreach ($splitIntoLowerCaseWords as $delimiterOrValue) { + foreach ($splitIntoLowerCaseWords ?: [] as $delimiterOrValue) { $result .= self::strtoupper(self::substr($delimiterOrValue, 0, 1)) . self::substr($delimiterOrValue, 1); } return $result; @@ -133,7 +133,7 @@ public static function lcfirst(string $string): string * @param string $haystack UTF-8 string to search in * @param string $needle UTF-8 string to search for * @param integer $offset Positition to start the search - * @return integer The character position + * @return integer|false The character position * @api */ public static function strpos(string $haystack, string $needle, int $offset = 0) @@ -154,16 +154,19 @@ public static function strpos(string $haystack, string $needle, int $offset = 0) * @see http://www.php.net/manual/en/function.pathinfo.php * * @param string $path - * @param integer $options Optional, one of PATHINFO_DIRNAME, PATHINFO_BASENAME, PATHINFO_EXTENSION or PATHINFO_FILENAME. - * @return string|array + * @param integer|null $options Optional, one of PATHINFO_DIRNAME, PATHINFO_BASENAME, PATHINFO_EXTENSION or PATHINFO_FILENAME. + * @return ($options is null ? array{dirname: string, basename: string, extension: string, filename: string} : string) * @api */ - public static function pathinfo(string $path, ?int $options = null) + public static function pathinfo(string $path, ?int $options = null): string|array { + /** @var string $currentLocale we assume the current locale to be properly set */ + /** @phpstan-ignore argument.type (int is allowed for locales) */ $currentLocale = setlocale(LC_CTYPE, 0); // Before we have a setting for setlocale, his should suffice for pathinfo // to work correctly on Unicode strings setlocale(LC_CTYPE, 'en_US.UTF-8'); + /** @var string|array{dirname: string, basename: string, extension: string, filename: string} $pathinfo */ $pathinfo = $options == null ? pathinfo($path) : pathinfo($path, $options); setlocale(LC_CTYPE, $currentLocale); return $pathinfo; @@ -188,8 +191,11 @@ public static function parse_url(string $url, int $component = -1) $encodedUrl = preg_replace_callback('%[^:@/?#&=\.]+%usD', function ($matches) { return urlencode($matches[0]); }, $url); - $components = parse_url($encodedUrl); + if ($encodedUrl === null) { + return false; + } + $components = parse_url($encodedUrl); if ($components === false) { return false; } diff --git a/Neos.Utility.Unicode/Classes/TextIterator.php b/Neos.Utility.Unicode/Classes/TextIterator.php index 862a05c204..2af09dd48d 100644 --- a/Neos.Utility.Unicode/Classes/TextIterator.php +++ b/Neos.Utility.Unicode/Classes/TextIterator.php @@ -19,6 +19,7 @@ /** * A UTF8-aware TextIterator * + * @implements \Iterator */ class TextIterator implements \Iterator { @@ -68,12 +69,12 @@ class TextIterator implements \Iterator protected $currentPosition; /** - * @var \ArrayObject + * @var \ArrayObject */ protected $iteratorCache; /** - * @var \ArrayIterator + * @var \ArrayIterator */ protected $iteratorCacheIterator; @@ -109,11 +110,11 @@ public function __construct(string $subject, int $iteratorType = self::CHARACTER /** * Returns the current element * - * @return string The value of the current element + * @return string|null The value of the current element */ - public function current(): string + public function current(): ?string { - return $this->getCurrentElement()->getValue(); + return $this->getCurrentElement()?->getValue(); } /** @@ -123,7 +124,7 @@ public function current(): string */ public function next(): void { - $this->previousElement = $this->getCurrentElement(); + $this->previousElement = $this->getCurrentElement() ?: $this->previousElement; $this->iteratorCacheIterator->next(); } @@ -164,11 +165,11 @@ public function rewind(): void /** * Returns the offset in the original given string of the current element * - * @return integer The offset of the current element + * @return int|null The offset of the current element, if any */ - public function offset(): int + public function offset(): ?int { - return $this->getCurrentElement()->getOffset(); + return $this->getCurrentElement()?->getOffset(); } /** @@ -194,7 +195,8 @@ public function last(): string $previousElement = $this->getCurrentElement(); $this->next(); } - return $previousElement->getValue(); + // can never be null because the iterator cannot be empty + return $previousElement?->getValue() ?: ''; } /** @@ -202,15 +204,15 @@ public function last(): string * given by its offset * * @param integer $offset The offset of the character - * @return int The offset of the element following this character + * @return int|null The offset of the element following this character or null if the offset is out of bounds */ - public function following(int $offset): int + public function following(int $offset): ?int { $this->rewind(); while ($this->valid()) { $this->next(); $nextElement = $this->getCurrentElement(); - if ($nextElement->getOffset() >= $offset) { + if ($nextElement && $nextElement->getOffset() >= $offset) { return $nextElement->getOffset(); } } @@ -231,7 +233,7 @@ public function preceding(int $offset): int $previousElement = $this->getCurrentElement(); $this->next(); $currentElement = $this->getCurrentElement(); - if (($currentElement->getOffset() + $currentElement->getLength()) >= $offset) { + if ($previousElement && $currentElement && ($currentElement->getOffset() + $currentElement->getLength()) >= $offset) { return $previousElement->getOffset() + $previousElement->getLength(); } } @@ -254,20 +256,21 @@ public function preceding(int $offset): int */ public function isBoundary(): bool { - return $this->getCurrentElement()->isBoundary(); + return $this->getCurrentElement()?->isBoundary() ?: false; } /** * Returns all elements of the iterator in an array * - * @return array All elements of the iterator + * @return array All elements of the iterator */ public function getAll(): array { $this->rewind(); $allValues = []; while ($this->valid()) { - $allValues[] = $this->getCurrentElement()->getValue(); + // will never be null while valid + $allValues[] = $this->getCurrentElement()?->getValue() ?: ''; $this->next(); } return $allValues; @@ -276,7 +279,7 @@ public function getAll(): array /** * @throws UnsupportedFeatureException */ - public function getRuleStatus() + public function getRuleStatus(): never { throw new UnsupportedFeatureException('getRuleStatus() is not supported.', 1210849057); } @@ -284,7 +287,7 @@ public function getRuleStatus() /** * @throws UnsupportedFeatureException */ - public function getRuleStatusArray() + public function getRuleStatusArray(): never { throw new UnsupportedFeatureException('getRuleStatusArray() is not supported.', 1210849076); } @@ -292,7 +295,7 @@ public function getRuleStatusArray() /** * @throws UnsupportedFeatureException */ - public function getAvailableLocales() + public function getAvailableLocales(): never { throw new UnsupportedFeatureException('getAvailableLocales() is not supported.', 1210849105); } @@ -305,7 +308,8 @@ public function getAvailableLocales() public function first(): string { $this->rewind(); - return $this->getCurrentElement()->getValue(); + // can never be empty, will at least contain empty string + return $this->getCurrentElement()?->getValue() ?: ''; } /** @@ -346,7 +350,7 @@ private function generateIteratorElements() private function parseSubjectByCharacter(): void { $i = 0; - foreach (preg_split('//u', $this->subject) as $currentCharacter) { + foreach (preg_split('//u', $this->subject) ?: [] as $currentCharacter) { if ($currentCharacter === '') { continue; } @@ -373,7 +377,7 @@ private function parseSubjectByWord() $this->iteratorCache->append(new TextIteratorElement(' ', $i, 1, true)); $j = 0; - $splittedWord = preg_split('/' . self::REGEXP_SENTENCE_DELIMITERS . '/', $currentWord); + $splittedWord = preg_split('/' . self::REGEXP_SENTENCE_DELIMITERS . '/', $currentWord) ?: []; foreach ($splittedWord as $currentPart) { if ($currentPart !== '') { $this->iteratorCache->append(new TextIteratorElement($currentPart, $i, Unicode\Functions::strlen($currentPart), false)); @@ -442,7 +446,7 @@ private function parseSubjectBySentence() $count = 0; $delimitersMatches = []; preg_match_all('/' . self::REGEXP_SENTENCE_DELIMITERS . '/', $this->subject, $delimitersMatches); - $splittedSentence = preg_split('/' . self::REGEXP_SENTENCE_DELIMITERS . '/', $this->subject); + $splittedSentence = preg_split('/' . self::REGEXP_SENTENCE_DELIMITERS . '/', $this->subject) ?: []; if (count($splittedSentence) == 1) { $this->iteratorCache->append(new TextIteratorElement($splittedSentence[0], 0, Unicode\Functions::strlen($splittedSentence[0]), false)); @@ -480,6 +484,7 @@ private function parseSubjectBySentence() * Helper function to get the current element from the cache. * * @return TextIteratorElement|null The current element of the cache + * @phpstan-ignore return.unusedType (can be null if next() leaves the valid range of the iterator) */ private function getCurrentElement() { diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 9b730bf93b..357d31969e 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,5 +1,5 @@ parameters: - level: 3 + level: 8 paths: - Neos.Cache/Classes - Neos.Eel/Classes