Skip to content
Merged
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@
"mockery/mockery": "^1.6.12",
"phpunit/phpunit": "^10.5.41",
"ramsey/collection": "^1.3",
"rector/rector": "~2.0.9",
"rector/rector": "~2.4.5",
"spiral/code-style": "^2.2.2",
"spiral/nyholm-bridge": "^1.3",
"spiral/testing": "^2.12",
Expand Down
44 changes: 43 additions & 1 deletion psalm-baseline.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="6.10.1@f9fd6bc117e9ce1e854c2ed6777e7135aaa4966b">
<files psalm-version="6.16.1@f1f5de594dc76faf8784e02d3dc4716c91c6f6ac">
<file src="src/Boot/src/AbstractKernel.php">
<InvalidArgument>
<code><![CDATA[$reflection]]></code>
</InvalidArgument>
</file>
<file src="src/Boot/src/Injector/EnumInjector.php">
<InvalidArgument>
<code><![CDATA[$class]]></code>
<code><![CDATA[$class]]></code>
</InvalidArgument>
</file>
<file src="src/Cache/src/Core/CacheInjector.php">
<InvalidArgument>
<code><![CDATA[$class]]></code>
</InvalidArgument>
</file>
<file src="src/Console/src/Command.php">
<InvalidArgument>
<code><![CDATA[new \ReflectionClass($this)]]></code>
</InvalidArgument>
</file>
<file src="src/Console/src/Configurator/AttributeBasedConfigurator.php">
<InvalidArgument>
<code><![CDATA[$reflection]]></code>
<code><![CDATA[$reflection]]></code>
</InvalidArgument>
</file>
<file src="src/Console/src/PromptArguments.php">
<InvalidArgument>
<code><![CDATA[$reflection]]></code>
</InvalidArgument>
</file>
<file src="src/Core/src/Internal/Actor.php">
<InvalidReturnStatement>
<code><![CDATA[$actor->registerInstance($ctx, $instance)]]></code>
Expand All @@ -20,10 +52,20 @@
<code><![CDATA[$this->key]]></code>
</UndefinedThisPropertyFetch>
</file>
<file src="src/Prototype/src/Command/DumpCommand.php">
<InvalidArgument>
<code><![CDATA[$ref]]></code>
</InvalidArgument>
</file>
<file src="src/Queue/src/RetryPolicy.php">
<InvalidOperand>
<code><![CDATA[$this->delay * $this->multiplier ** $attempts]]></code>
<code><![CDATA[$this->multiplier ** $attempts]]></code>
</InvalidOperand>
</file>
<file src="src/Tokenizer/src/Listener/AbstractCachedLoader.php">
<InvalidArgument>
<code><![CDATA[$listener]]></code>
</InvalidArgument>
</file>
</files>
32 changes: 25 additions & 7 deletions rector.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,10 @@
use Rector\Php70\Rector\StmtsAwareInterface\IfIssetToCoalescingRector;
use Rector\Php71\Rector\FuncCall\RemoveExtraParametersRector;
use Rector\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector;
use Rector\Php81\Rector\Array_\FirstClassCallableRector;
use Rector\Php81\Rector\ClassMethod\NewInInitializerRector;
use Rector\Php81\Rector\Array_\ArrayToFirstClassCallableRector;
use Rector\Php81\Rector\Property\ReadOnlyPropertyRector;
use Rector\PHPUnit\CodeQuality\Rector\Class_\PreferPHPUnitSelfCallRector;
use Rector\PHPUnit\CodeQuality\Rector\Class_\PreferPHPUnitThisCallRector;
use Rector\PHPUnit\CodeQuality\Rector\MethodCall\AssertCountWithZeroToAssertEmptyRector;
use Rector\TypeDeclaration\Rector\Closure\AddClosureNeverReturnTypeRector;
use Rector\TypeDeclaration\Rector\Closure\ClosureReturnTypeRector;
use Rector\TypeDeclaration\Rector\Property\TypedPropertyFromAssignsRector;
Expand Down Expand Up @@ -71,6 +69,9 @@
__DIR__ . '/src/Logger/src/ListenerRegistry.php',
__DIR__ . '/src/Stempler/src/Transform/Merge/ExtendsParent.php',
__DIR__ . '/src/Bridge/Stempler/src/StemplerEngine.php',
// Keep the is_string() guard: getBindings() reads user config, so a
// malformed (non-string) binding must be skipped, not crash class_exists().
__DIR__ . '/src/Prototype/src/Bootloader/PrototypeBootloader.php',
],
RemoveExtraParametersRector::class => [
__DIR__ . '/src/Boot/src/BootloadManager/AbstractBootloadManager.php',
Expand All @@ -93,7 +94,6 @@
RemoveUnusedPublicMethodParameterRector::class,
RemoveEmptyClassMethodRector::class,
RemoveUnusedPromotedPropertyRector::class,
NewInInitializerRector::class,

// start with short open tag
__DIR__ . '/src/Views/tests/fixtures/other/var.php',
Expand All @@ -117,8 +117,10 @@
__DIR__ . '/src/Scaffolder/src/Command',
],

FirstClassCallableRector::class => [
ArrayToFirstClassCallableRector::class => [
__DIR__ . '/src/Core/tests/Scope/UseCaseTest.php',
__DIR__ . '/src/Core/tests/InjectableTest.php',
__DIR__ . '/src/Core/tests/InvokerTest.php',
],

PreferPHPUnitThisCallRector::class,
Expand All @@ -127,8 +129,24 @@
__DIR__ . '/src/Models/tests/PublicEntity.php',
],

// Explicit behavior is more preferable
AssertCountWithZeroToAssertEmptyRector::class,
// to be enabled later for easier to review
\Rector\Privatization\Rector\Class_\FinalizeTestCaseClassRector::class,
\Rector\PHPUnit\PHPUnit120\Rector\CallLike\CreateStubOverCreateMockArgRector::class,
\Rector\Php55\Rector\ClassConstFetch\StaticToSelfOnFinalClassRector::class,
\Rector\PHPUnit\CodeQuality\Rector\MethodCall\StringCastAssertStringContainsStringRector::class,
\Rector\PHPUnit\CodeQuality\Rector\Class_\PreferPHPUnitSelfCallRector::class,
\Rector\PHPUnit\CodeQuality\Rector\StmtsAwareInterface\DeclareStrictTypesTestsRector::class,
\Rector\DeadCode\Rector\MethodCall\RemoveNullArgOnNullDefaultParamRector::class,
\Rector\CodingStyle\Rector\ArrowFunction\ArrowFunctionDelegatingCallToFirstClassCallableRector::class,
\Rector\PHPUnit\PHPUnit120\Rector\ClassMethod\ExpressionCreateMockToCreateStubRector::class,
\Rector\PHPUnit\CodeQuality\Rector\ClassMethod\AddInstanceofAssertForNullableInstanceRector::class,
\Rector\PHPUnit\CodeQuality\Rector\ClassMethod\BareCreateMockAssignToDirectUseRector::class,
\Rector\TypeDeclaration\Rector\Class_\TypedPropertyFromCreateMockAssignRector::class,
\Rector\DeadCode\Rector\FunctionLike\NarrowWideUnionReturnTypeRector::class,
\Rector\PHPUnit\CodeQuality\Rector\MethodCall\WithCallbackIdenticalToStandaloneAssertsRector::class,
\Rector\PHPUnit\CodeQuality\Rector\MethodCall\SimplerWithIsInstanceOfRector::class,
\Rector\DeadCode\Rector\ClassMethod\RemoveUnusedConstructorParamRector::class,
\Rector\PHPUnit\AnnotationsToAttributes\Rector\Class_\CoversAnnotationWithValueToAttributeRector::class,
])
->withPhpSets(php81: true)
->withPreparedSets(deadCode: true, phpunitCodeQuality: true)
Expand Down
1 change: 0 additions & 1 deletion src/Boot/src/AbstractKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,6 @@ final public static function create(
$container->bind(BootloadManagerInterface::class, $bootloadManager);

if (!$container->has(BootloaderRegistryInterface::class)) {
/** @psalm-suppress InvalidArgument */
$container->bindSingleton(BootloaderRegistryInterface::class, [self::class, 'initBootloaderRegistry']);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Console/src/Configurator/Signature/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ private function extractDescription(string $token): array
{
$token = \trim($token);

$parts = \array_map('trim', \explode(':', $token, 2));
$parts = \array_map(trim(...), \explode(':', $token, 2));

return \count($parts) === 2 ? $parts : [$token, ''];
}
Expand Down
9 changes: 2 additions & 7 deletions src/Core/src/Internal/Actor.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,6 @@ public function resolveBinding(
Config\Injectable::class => $this->resolveInjector(
$binding,
new Ctx(alias: $alias, class: $alias, context: $context),
$arguments,
$tracer,
),
Config\Scalar::class => $binding->value,
Expand Down Expand Up @@ -194,11 +193,7 @@ public function autowire(Ctx $ctx, array $arguments, ?Actor $fallbackActor, Trac
return $this->createInstance($ctx, $arguments, $fallbackActor, $tracer);
}

/**
* @psalm-suppress UnusedParam
* todo wat should we do with $arguments?
*/
private function resolveInjector(Config\Injectable $binding, Ctx $ctx, array $arguments, Tracer $tracer)
private function resolveInjector(Config\Injectable $binding, Ctx $ctx, Tracer $tracer)
{
$context = $ctx->context;
try {
Expand Down Expand Up @@ -511,7 +506,7 @@ private function createInstance(

// We have to construct class using external injector when we know the exact context
if ($arguments === [] && $actor->binder->hasInjector($class)) {
return $actor->resolveInjector($actor->state->bindings[$ctx->class], $ctx, $arguments, $tracer);
return $actor->resolveInjector($actor->state->bindings[$ctx->class], $ctx, $tracer);
}

if (!$reflection->isInstantiable()) {
Expand Down
3 changes: 3 additions & 0 deletions src/Core/tests/InvokerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ public function testCallValidCallableString(): void
*/
public function testCallStaticMethodWithoutInstantiation(): void
{
// Keep the [class, method] array callable: this test verifies the Invoker resolves
// it without instantiating the class. Do not convert it to a first-class callable
// (PrivateConstructor::publicMethod(...)) — that bypasses the resolution path under test.
$result = $this->container->invoke([PrivateConstructor::class, 'publicMethod'], [42]);

self::assertSame(42, $result);
Expand Down
2 changes: 1 addition & 1 deletion src/Core/tests/Scope/ProxyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ public function testImplementationWithWiderTypes(): void

$root->runScope(
new Scope('http'),
static function () use ($root, $proxy): void {
static function () use ($proxy): void {
self::assertSame('Foo', $proxy->getName());
$proxy->setName(new class implements \Stringable {
public function __toString(): string
Expand Down
2 changes: 1 addition & 1 deletion src/Core/tests/ScopesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public function testContainerScopeException(): void
self::assertTrue($c->runScope([
'bucket' => new Bucket('b'),
'other' => new SampleClass(),
], static function () use ($c): void {
], static function (): void {
throw new RuntimeException('exception');
}));
} catch (\Throwable) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@

namespace Spiral\Tests\Distribution;

use PHPUnit\Framework\Attributes\Group;
use Spiral\Distribution\Manager;
use Spiral\Distribution\Resolver\StaticResolver;

#[\PHPUnit\Framework\Attributes\Group('unit')]
class ManagerTestCase extends TestCase
#[Group('unit')]
class ManagerTest extends TestCase
{
private StaticResolver $resolver;
private Manager $manager;
Expand Down
21 changes: 16 additions & 5 deletions src/Files/src/Files.php
Original file line number Diff line number Diff line change
Expand Up @@ -280,13 +280,13 @@ public function getFiles(string $location, ?string $pattern = null): array
{
$result = [];
foreach ($this->filesIterator($location, $pattern) as $filename) {
if ($this->isDirectory($filename->getPathname())) {
$result = \array_merge($result, $this->getFiles(((string) $filename) . DIRECTORY_SEPARATOR));
if ($this->isDirectory($filename)) {
$result = \array_merge($result, $this->getFiles($filename . DIRECTORY_SEPARATOR));

continue;
}

$result[] = $this->normalizePath((string) $filename);
$result[] = $this->normalizePath($filename);
}

return $result;
Expand Down Expand Up @@ -368,11 +368,22 @@ public function __destruct()
}
}

private function filesIterator(string $location, ?string $pattern = null): \GlobIterator
/**
* CURRENT_AS_PATHNAME makes the iterator yield each match as its non-empty pathname
* string; psalm's GlobIterator stub still types the value as SplFileInfo|string.
*
* @param non-empty-string $location Location for search.
* @param non-empty-string|null $pattern Extension pattern.
*
* @return iterable<non-empty-string>
*
* @psalm-suppress all
*/
private function filesIterator(string $location, ?string $pattern = null): iterable
{
$pattern ??= '*';
$regexp = \rtrim($location, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . \ltrim($pattern, DIRECTORY_SEPARATOR);

return new \GlobIterator($regexp);
return new \GlobIterator($regexp, \FilesystemIterator::CURRENT_AS_PATHNAME);
}
}
1 change: 0 additions & 1 deletion src/Files/src/FilesInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,6 @@ public function setPermissions(string $filename, int $mode): bool;
* @param non-empty-string|null $pattern Extension pattern.
*
* @return list<non-empty-string>
* /
*/
public function getFiles(string $location, ?string $pattern = null): array;

Expand Down
1 change: 0 additions & 1 deletion src/Pagination/src/Paginator.php
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,6 @@ public function previousPage(): ?int
* Non-Immutable version of withCount.
*
* @param int<0, max> $count
* @return static
*/
private function setCount(int $count): self
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
use Spiral\Queue\Exception\JobException;
use Spiral\Queue\HandlerInterface;

final class ContainerRegistryT extends TestCase
final class ContainerRegistryTest extends TestCase
{
private ContainerInterface|m\MockInterface $container;
private ContainerRegistry $registry;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
use Spiral\Tests\Router\Stub\InArrayPattern;
use Mockery as m;

class PatternsTests extends TestCase
class PatternsTest extends TestCase
{
public function testDigitWithZeroValue(): void
{
Expand Down
6 changes: 3 additions & 3 deletions src/Security/tests/GuardTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ class GuardTest extends TestCase
public const CONTEXT = [];

private MockObject&PermissionsInterface $permission;
private MockObject&ActorInterface $actor;
private \PHPUnit\Framework\MockObject\Stub&ActorInterface $actor;
private array $roles = ['user', 'admin'];

public function testAllows(): void
{
$this->permission->method('hasRole')
->willReturnCallback(static function (...$args) {
->willReturnCallback(static function (...$args): bool {
static $series = [
[['user'], false],
[['admin'], true],
Expand Down Expand Up @@ -85,6 +85,6 @@ public function testWithRoles(): void
protected function setUp(): void
{
$this->permission = $this->createMock(PermissionsInterface::class);
$this->actor = $this->createMock(ActorInterface::class);
$this->actor = $this->createStub(ActorInterface::class);
}
}
5 changes: 4 additions & 1 deletion src/Security/tests/RuleManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
*/
class RuleManagerTest extends TestCase
{
public const RULE_NAME = 'test';
// Not a valid/loadable class name on purpose: RuleManager::has() calls
// class_exists() (case-insensitive, with autoload), so a plain word like
// "test" could collide with a global class loaded elsewhere in the suite.
public const RULE_NAME = 'test-rule';

/** @var ContainerInterface */
private $container;
Expand Down
4 changes: 2 additions & 2 deletions src/Security/tests/RuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class RuleTest extends TestCase
public const OPERATION = 'test';
public const CONTEXT = [];

private MockObject&ActorInterface $actor;
private \PHPUnit\Framework\MockObject\Stub&ActorInterface $actor;
private MockObject&ResolverInterface $resolver;
private MockObject&Rule $rule;

Expand Down Expand Up @@ -68,7 +68,7 @@ public function testAllowsException(): void

protected function setUp(): void
{
$this->actor = $this->createMock(ActorInterface::class);
$this->actor = $this->createStub(ActorInterface::class);
$this->resolver = $this->createMock(ResolverInterface::class);
$this->rule = $this->getMockBuilder(Rule::class)
->setConstructorArgs([$this->resolver])
Expand Down
4 changes: 2 additions & 2 deletions src/Security/tests/Rules/CompositeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class CompositeRuleTest extends TestCase
public const OPERATION = 'test';
public const CONTEXT = [];

private MockObject&ActorInterface $actor;
private \PHPUnit\Framework\MockObject\Stub&ActorInterface $actor;

public static function allowsProvider(): \Traversable
{
Expand All @@ -45,7 +45,7 @@ public function testAllow(bool $expected, string $compositeRuleClass, array $rul

protected function setUp(): void
{
$this->actor = $this->createMock(ActorInterface::class);
$this->actor = $this->createStub(ActorInterface::class);
}

private static function allowRule(): RuleInterface
Expand Down
2 changes: 1 addition & 1 deletion src/Serializer/src/Bootloader/SerializerBootloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ private function initSerializerManager(

private function initSerializerRegistry(SerializerConfig $config): SerializerRegistryInterface
{
return new SerializerRegistry(\array_map([$this, 'wire'], $config->getSerializers()));
return new SerializerRegistry(\array_map($this->wire(...), $config->getSerializers()));
}

private function initConfig(EnvironmentInterface $env): void
Expand Down
2 changes: 0 additions & 2 deletions src/Stempler/src/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,6 @@ public function load(string $path): Template
return $this->process($tpl);
} catch (ContextExceptionInterface $e) {
throw $this->mapException($e);
} catch (\Throwable $e) {
throw $e;
}
}

Expand Down
Loading
Loading