Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add virtual IsEntity/IsLoaded interfaces for phpstan #932

Draft
wants to merge 15 commits into
base: develop
Choose a base branch
from
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"ergebnis/phpunit-slow-test-detector": "^2.9",
"friendsofphp/php-cs-fixer": "^3.0",
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan": "^1.6",
"phpstan/phpstan": "^1.10.6",
"phpstan/phpstan-deprecation-rules": "^1.0",
"phpstan/phpstan-strict-rules": "^1.3",
"phpunit/phpunit": "^9.5.25 || ^10.0"
Expand Down
17 changes: 17 additions & 0 deletions phpstan-ext.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
services:
-
factory: Atk4\Data\Model\Phpstan\RemoveVirtualInterfacesFromStaticReturnTypeDmrtExtension(Atk4\Data\Model, Atk4\Data\Model\Phpstan\PhpdocTypeInterface)
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
factory: Atk4\Data\Model\Phpstan\RemoveVirtualInterfacesFromStaticReturnTypeDmrtExtension(Atk4\Data\Model, Atk4\Data\Model\Phpstan\PhpdocTypeInterface)
tags:
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
-
factory: Atk4\Data\Model\Phpstan\RemoveVirtualInterfacesFromStaticReturnTypeDmrtExtension(Atk4\Data\Model, Atk4\Data\Model\Phpstan\PhpdocTypeInterface)
tags:
- phpstan.typeSpecifier.methodTypeSpecifyingExtension
-
factory: Atk4\Data\Model\Phpstan\RemoveVirtualInterfacesFromStaticReturnTypeDmrtExtension(Atk4\Data\Model, Atk4\Data\Model\Phpstan\PhpdocTypeInterface)
tags:
- phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension
23 changes: 23 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
includes:
- phar://phpstan.phar/conf/bleedingEdge.neon
# remove once https://github.com/phpstan/extension-installer/issues/36 is fixed
- phpstan-ext.neon

parameters:
level: 6
Expand Down Expand Up @@ -44,6 +46,27 @@ parameters:
path: '*'
count: 24

-
message: '~^Creating new PHPStan\\Reflection\\Type\\UnionTypeMethodReflection is not covered by backward compatibility promise\. The class might change in a minor PHPStan version\.$~'
path: 'src/Model/Phpstan/RemoveVirtualInterfacesFromStaticReturnTypeDmrtExtension.php'
count: 1
-
message: '~^Calling PHPStan\\Reflection\\Type\\UnionTypeMethodReflection::getName\(\) is not covered by backward compatibility promise\. The method might change in a minor PHPStan version\.$~'
path: 'src/Model/Phpstan/RemoveVirtualInterfacesFromStaticReturnTypeDmrtExtension.php'
count: 1
-
message: '~^Creating new PHPStan\\Reflection\\Type\\CalledOnTypeUnresolvedMethodPrototypeReflection is not covered by backward compatibility promise\. The class might change in a minor PHPStan version\.$~'
path: 'src/Model/Phpstan/RemoveVirtualInterfacesFromStaticReturnTypeDmrtExtension.php'
count: 1
-
message: '~^Calling PHPStan\\Reflection\\Type\\CalledOnTypeUnresolvedMethodPrototypeReflection::getTransformedMethod\(\) is not covered by backward compatibility promise\. The method might change in a minor PHPStan version\.$~'
path: 'src/Model/Phpstan/RemoveVirtualInterfacesFromStaticReturnTypeDmrtExtension.php'
count: 1
-
message: '~^Parameter #1 \$methodReflection of method Atk4\\Data\\Model\\Phpstan\\RemoveVirtualInterfacesFromStaticReturnTypeDmrtExtension::unresolveMethodReflection\(\) expects PHPStan\\Reflection\\ResolvedMethodReflection, PHPStan\\Reflection\\MethodReflection given\.$~'
path: 'src/Model/Phpstan/RemoveVirtualInterfacesFromStaticReturnTypeDmrtExtension.php'
count: 1

# TODO these rules are generated, this ignores should be fixed in the code
# for src/Schema/TestCase.php
- '~^Call to an undefined method Atk4\\Data\\Persistence::dsql\(\)\.$~'
Expand Down
52 changes: 38 additions & 14 deletions src/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
use Atk4\Core\ReadableCaptionTrait;
use Atk4\Data\Field\CallbackField;
use Atk4\Data\Field\SqlExpressionField;
use Atk4\Data\Model\Phpstan\IsEntity;
use Atk4\Data\Model\Phpstan\IsLoaded;
use Atk4\Data\Model\Scope\AbstractScope;
use Atk4\Data\Model\Scope\RootScope;
use Mvorisek\Atk4\Hintable\Data\HintableModelTrait;
Expand All @@ -25,7 +27,7 @@
* If the value is null then the record is considered to be new.
* @property array<string, Field|Reference|Model\Join> $elements
*
* @phpstan-implements \IteratorAggregate<static>
* @phpstan-implements \IteratorAggregate<static&IsLoaded>
*/
class Model implements \IteratorAggregate
{
Expand Down Expand Up @@ -348,6 +350,8 @@

/**
* @return static
*
* @phpstan-return static&IsEntity
*/
public function createEntity(): self
{
Expand All @@ -372,7 +376,7 @@
$entity->data = [];
$entity->dirty = [];

return $entity;
return $entity; // @phpstan-ignore-line
}

/**
Expand Down Expand Up @@ -1202,7 +1206,7 @@
* @param ($fromTryLoad is true ? false : bool) $fromReload
* @param mixed $id
*
* @return ($fromTryLoad is true ? static|null : static)
* @return ($fromTryLoad is true ? (static&IsLoaded)|null : static&IsLoaded)
*/
private function _load(bool $fromReload, bool $fromTryLoad, $id)
{
Expand All @@ -1217,15 +1221,15 @@
if ($fromReload) {
$this->unload();

return $this;
return $this; // @phpstan-ignore-line
}

return null;
} elseif (is_object($res)) {
$res = (static::class)::assertInstanceOf($res);
(static::class)::assertInstanceOf($res);
$res->assertIsEntity();

return $res;
return $res; // @phpstan-ignore-line
}

$data = $this->getModel()->getPersistence()->{$fromTryLoad ? 'tryLoad' : 'load'}($this->getModel(), $this->remapIdLoadToPersistence($id));
Expand All @@ -1245,18 +1249,18 @@
if ($fromReload) {
$this->unload();

return $this;
return $this; // @phpstan-ignore-line
}

return null;
} elseif (is_object($res)) {
$res = (static::class)::assertInstanceOf($res);
(static::class)::assertInstanceOf($res);
$res->assertIsEntity();

return $res;
return $res; // @phpstan-ignore-line
}

return $this;
return $this; // @phpstan-ignore-line
}

/**
Expand All @@ -1265,6 +1269,8 @@
* @param mixed $id
*
* @return static|null
*
* @phpstan-return (static&IsLoaded)|null
*/
public function tryLoad($id)
{
Expand All @@ -1279,6 +1285,8 @@
* @param mixed $id
*
* @return static
*
* @phpstan-return static&IsLoaded
*/
public function load($id)
{
Expand All @@ -1291,6 +1299,8 @@
* Try to load one record. Will throw if more than one record exists, but not if there is no record.
*
* @return static|null
*
* @phpstan-return (static&IsLoaded)|null
*/
public function tryLoadOne()
{
Expand All @@ -1301,6 +1311,8 @@
* Load one record. Will throw if more than one record exists.
*
* @return static
*
* @phpstan-return static&IsLoaded
*/
public function loadOne()
{
Expand All @@ -1313,6 +1325,8 @@
* If only one record should match, use checked "tryLoadOne" method.
*
* @return static|null
*
* @phpstan-return (static&IsLoaded)|null
*/
public function tryLoadAny()
{
Expand All @@ -1325,6 +1339,8 @@
* If only one record should match, use checked "loadOne" method.
*
* @return static
*
* @phpstan-return static&IsLoaded
*/
public function loadAny()
{
Expand All @@ -1334,7 +1350,7 @@
/**
* Reload model by taking its current ID.
*
* @return $this
* @return $this&IsLoaded
*/
public function reload()
{
Expand All @@ -1343,11 +1359,11 @@
$this->unload();

$res = $this->_load(true, false, $id);
if ($res !== $this) {

Check failure on line 1362 in src/Model.php

View workflow job for this annotation

GitHub Actions / Smoke (latest, StaticAnalysis)

Strict comparison using !== between *NEVER* and $this(Atk4\Data\Model)&Atk4\Data\Model\Phpstan\IsEntity will always evaluate to true.

Check failure on line 1362 in src/Model.php

View workflow job for this annotation

GitHub Actions / Smoke (latest, StaticAnalysis)

Strict comparison using !== between *NEVER* and $this(Atk4\Data\Model)&Atk4\Data\Model\Phpstan\IsEntity will always evaluate to true.
throw new Exception('Entity instance does not match');
}

return $this;

Check failure on line 1366 in src/Model.php

View workflow job for this annotation

GitHub Actions / Smoke (latest, StaticAnalysis)

Unreachable statement - code above always terminates.

Check failure on line 1366 in src/Model.php

View workflow job for this annotation

GitHub Actions / Smoke (latest, StaticAnalysis)

Unreachable statement - code above always terminates.
}

/**
Expand All @@ -1356,6 +1372,8 @@
* record in the database.
*
* @return static
*
* @phpstan-return static&IsEntity
*/
public function duplicate()
{
Expand All @@ -1368,7 +1386,7 @@
$duplicateDirtyRef = $data;
$duplicate->setId(null);

return $duplicate;
return $duplicate; // @phpstan-ignore-line

Check failure on line 1389 in src/Model.php

View workflow job for this annotation

GitHub Actions / Smoke (latest, StaticAnalysis)

No error to ignore is reported on line 1389.

Check failure on line 1389 in src/Model.php

View workflow job for this annotation

GitHub Actions / Smoke (latest, StaticAnalysis)

No error to ignore is reported on line 1389.
}

/**
Expand Down Expand Up @@ -1455,7 +1473,7 @@
* @param ($field is string|Persistence\Sql\Expressionable ? ($value is null ? mixed : string) : never) $operator
* @param ($operator is string ? mixed : never) $value
*
* @return ($fromTryLoad is true ? static|null : static)
* @return ($fromTryLoad is true ? (static&IsLoaded)|null : static&IsLoaded)
*/
private function _loadBy(bool $fromTryLoad, $field, $operator = null, $value = null)
{
Expand All @@ -1482,6 +1500,8 @@
* @param ($operator is string ? mixed : never) $value
*
* @return static
*
* @phpstan-return static&IsLoaded
*/
public function loadBy($field, $operator = null, $value = null)
{
Expand All @@ -1496,6 +1516,8 @@
* @param ($operator is string ? mixed : never) $value
*
* @return static|null
*
* @phpstan-return (static&IsLoaded)|null
*/
public function tryLoadBy($field, $operator = null, $value = null)
{
Expand Down Expand Up @@ -1791,6 +1813,8 @@
* as a next iterator value.
*
* @return \Traversable<static>
*
* @phpstan-return \Traversable<static&IsLoaded>
*/
#[\Override]
final public function getIterator(): \Traversable
Expand Down Expand Up @@ -1841,16 +1865,16 @@
if ($res === false) {
continue;
} elseif (is_object($res)) {
$res = (static::class)::assertInstanceOf($res);
(static::class)::assertInstanceOf($res);
$res->assertIsEntity();
} else {
$res = $entity;
}

if ($res->getModel()->idField) {
yield $res->getId() => $res;

Check failure on line 1875 in src/Model.php

View workflow job for this annotation

GitHub Actions / Smoke (latest, StaticAnalysis)

Generator expects value type static(Atk4\Data\Model), object given.

Check failure on line 1875 in src/Model.php

View workflow job for this annotation

GitHub Actions / Smoke (latest, StaticAnalysis)

Generator expects value type static(Atk4\Data\Model), object given.
} else {
yield $res;

Check failure on line 1877 in src/Model.php

View workflow job for this annotation

GitHub Actions / Smoke (latest, StaticAnalysis)

Generator expects value type static(Atk4\Data\Model), object given.

Check failure on line 1877 in src/Model.php

View workflow job for this annotation

GitHub Actions / Smoke (latest, StaticAnalysis)

Generator expects value type static(Atk4\Data\Model), object given.
}
}
} finally {
Expand Down
7 changes: 4 additions & 3 deletions src/Model/EntityFieldPair.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Atk4\Core\WarnDynamicPropertyTrait;
use Atk4\Data\Field;
use Atk4\Data\Model;
use Atk4\Data\Model\Phpstan\IsEntity;

/**
* @template-covariant TModel of Model
Expand All @@ -16,13 +17,13 @@ class EntityFieldPair
{
use WarnDynamicPropertyTrait;

/** @var TModel */
/** @var TModel&IsEntity */
private $entity;
/** @var string */
private $fieldName;

/**
* @param TModel $entity
* @param TModel&IsEntity $entity
*/
public function __construct(Model $entity, string $fieldName)
{
Expand All @@ -41,7 +42,7 @@ public function getModel(): Model
}

/**
* @return TModel
* @return TModel&IsEntity
*/
public function getEntity(): Model
{
Expand Down
9 changes: 9 additions & 0 deletions src/Model/Phpstan/IsEntity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace Atk4\Data\Model\Phpstan;

interface IsEntity extends PhpdocTypeInterface
{
}
9 changes: 9 additions & 0 deletions src/Model/Phpstan/IsLoaded.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace Atk4\Data\Model\Phpstan;

interface IsLoaded extends IsEntity
{
}
15 changes: 15 additions & 0 deletions src/Model/Phpstan/PhpdocTypeInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Atk4\Data\Model\Phpstan;

interface PhpdocTypeInterface
{
/**
* @deprecated Interface for phpdoc type for Phpstan only
*
* @return never
*/
public function neverImplement(): void;
}
Loading
Loading