Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions src/Rules/Symfony/NoNullableServiceInConstructorRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use PHPStan\Rules\IdentifierRuleError;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use ReflectionProperty;
use Symplify\PHPStanRules\Enum\RuleIdentifier;
use Symplify\PHPStanRules\NodeAnalyzer\LaravelPresenceResolver;
use Throwable;
Expand All @@ -31,9 +32,9 @@
* that it is really required. Nullable is allowed on an abstract class, whose optional dependency is filled by a child.
* A nullable scalar, array, exception ("$previous" is nullable by PHP convention), date value object, enum or closure
* is left alone, as those are values, not services. An exception class is skipped whole - its constructor carries
* error context, not services. Data-holder classes in an Entity, Event, DTO, Dto, Message, DAO, Dao, Token,
* Exception, Helper, ValueObject, Form\Type or Badge namespace are skipped whole - their constructors carry values,
* not services.
* error context, not services - and so is a class with public properties, which holds data. Data-holder classes in
* an Entity, Event, DTO, Dto, Message, DAO, Dao, Token, Exception, Helper, ValueObject, Form\Type or Badge namespace
* are skipped whole - their constructors carry values, not services.
*
* @see \Symplify\PHPStanRules\Tests\Rules\Symfony\NoNullableServiceInConstructorRule\NoNullableServiceInConstructorRuleTest
*
Expand Down Expand Up @@ -111,6 +112,11 @@ public function processNode(Node $node, Scope $scope): array
return [];
}

// a class with public properties is a data holder - a service does not expose its dependencies
if ($this->hasPublicProperty($classReflection)) {
return [];
}

$paramTypes = $this->resolveParamClassTypes($node, $scope);

$ruleErrors = [];
Expand Down Expand Up @@ -208,6 +214,20 @@ private function matchNullableServiceName(Identifier|Name|ComplexType|null $type
return null;
}

/**
* Own, promoted and inherited public instance properties count.
*/
private function hasPublicProperty(ClassReflection $classReflection): bool
{
foreach ($classReflection->getNativeReflection()->getProperties(ReflectionProperty::IS_PUBLIC) as $reflectionProperty) {
if (! $reflectionProperty->isStatic()) {
return true;
}
}

return false;
}

private function isSkippedNamespace(string $className): bool
{
return array_any(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Symplify\PHPStanRules\Tests\Rules\Symfony\NoNullableServiceInConstructorRule\Fixture;

use Symplify\PHPStanRules\Tests\Rules\Symfony\NoNullableServiceInConstructorRule\Source\SomeDataHolder;
use Symplify\PHPStanRules\Tests\Rules\Symfony\NoNullableServiceInConstructorRule\Source\SomeService;

final class SkipChildOfPublicPropertyDataHolder extends SomeDataHolder
{
public function __construct(?SomeService $minPrice)
{
parent::__construct($minPrice);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Symplify\PHPStanRules\Tests\Rules\Symfony\NoNullableServiceInConstructorRule\Fixture;

use Symplify\PHPStanRules\Tests\Rules\Symfony\NoNullableServiceInConstructorRule\Source\SomeService;

final class SkipPublicPropertyDataHolder
{
public function __construct(
public readonly ?SomeService $price,
) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ public static function provideData(): Iterator
yield [__DIR__ . '/Fixture/Event/SkipEventDataHolder.php', []];
yield [__DIR__ . '/Fixture/SkipDuplicateType.php', []];
yield [__DIR__ . '/Fixture/SkipAnonymousClass.php', []];
yield [__DIR__ . '/Fixture/SkipPublicPropertyDataHolder.php', []];
yield [__DIR__ . '/Fixture/SkipChildOfPublicPropertyDataHolder.php', []];
yield [__DIR__ . '/Fixture/SkipNullableEnum.php', []];
yield [__DIR__ . '/Fixture/SkipNullableClosure.php', []];
yield [__DIR__ . '/Fixture/SkipExceptionClass.php', []];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Symplify\PHPStanRules\Tests\Rules\Symfony\NoNullableServiceInConstructorRule\Source;

abstract class SomeDataHolder
{
public function __construct(
public readonly ?SomeService $minPrice,
) {
}
}
Loading