Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
nikophil committed Oct 19, 2024
1 parent ca1cc1a commit e3a9d51
Show file tree
Hide file tree
Showing 14 changed files with 661 additions and 304 deletions.
462 changes: 234 additions & 228 deletions bin/tools/psalm/composer.lock

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@
"symfony/yaml": "^6.4|^7.0"
},
"autoload": {
"psr-4": { "Zenstruck\\Foundry\\": "src/" },
"psr-4": {
"Zenstruck\\Foundry\\": "src/",
"Zenstruck\\Foundry\\Psalm\\": "utils/psalm"
},
"files": ["src/functions.php", "src/Persistence/functions.php", "src/phpunit_helper.php"]
},
"autoload-dev": {
Expand Down
2 changes: 1 addition & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ parameters:
paths:
- src
- tests
- stubs
- stubs/phpstan
ignoreErrors:
# suppress strange behavior of PHPStan where it considers proxy() return type as *NEVER*
- message: '#Return type of call to function Zenstruck\\Foundry\\Persistence\\proxy contains unresolvable type#'
Expand Down
11 changes: 5 additions & 6 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@
findUnusedCode="false"
>
<projectFiles>
<file name="tests/Fixtures/Maker/expected/can_create_factory_for_entity_with_repository_with_data_set_psalm.php" />
<file name="tests/Fixtures/Maker/expected/can_create_factory_with_static_analysis_annotations_with_data_set_psalm.php" />
<!-- <file name="tests/Fixture/Maker/expected/can_create_factory.php" />-->
<directory name="stubs/psalm" />
</projectFiles>

<issueHandlers>
<ImplementedReturnTypeMismatch errorLevel="suppress" />
<MethodSignatureMismatch errorLevel="suppress" />
</issueHandlers>
<plugins>
<pluginClass class="Zenstruck\Foundry\Psalm\FoundryPlugin"/>
</plugins>
</psalm>
44 changes: 11 additions & 33 deletions src/Persistence/PersistentObjectFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,27 +41,23 @@ abstract class PersistentObjectFactory extends ObjectFactory
private array $tempAfterPersist = [];

/**
* @final
*
* @param mixed|Parameters $criteriaOrId
*
* @return T
*
* @throws \RuntimeException If no object found
*/
public static function find(mixed $criteriaOrId): object
final public static function find(mixed $criteriaOrId): object
{
return static::repository()->findOrFail($criteriaOrId);
}

/**
* @final
*
* @param Parameters $criteria
*
* @return T
*/
public static function findOrCreate(array $criteria): object
final public static function findOrCreate(array $criteria): object
{
try {
$object = static::repository()->findOneBy($criteria);
Expand All @@ -73,13 +69,11 @@ public static function findOrCreate(array $criteria): object
}

/**
* @final
*
* @param Parameters $criteria
*
* @return T
*/
public static function randomOrCreate(array $criteria = []): object
final public static function randomOrCreate(array $criteria = []): object
{
try {
return static::repository()->random($criteria);
Expand All @@ -89,96 +83,80 @@ public static function randomOrCreate(array $criteria = []): object
}

/**
* @final
*
* @param positive-int $count
* @param Parameters $criteria
*
* @return T[]
*/
public static function randomSet(int $count, array $criteria = []): array
final public static function randomSet(int $count, array $criteria = []): array
{
return static::repository()->randomSet($count, $criteria);
}

/**
* @final
*
* @param int<0, max> $min
* @param int<0, max> $max
* @param Parameters $criteria
*
* @return T[]
*/
public static function randomRange(int $min, int $max, array $criteria = []): array
final public static function randomRange(int $min, int $max, array $criteria = []): array
{
return static::repository()->randomRange($min, $max, $criteria);
}

/**
* @final
*
* @param Parameters $criteria
*
* @return T[]
*/
public static function findBy(array $criteria): array
final public static function findBy(array $criteria): array
{
return static::repository()->findBy($criteria);
}

/**
* @final
*
* @param Parameters $criteria
*
* @return T
*/
public static function random(array $criteria = []): object
final public static function random(array $criteria = []): object
{
return static::repository()->random($criteria);
}

/**
* @final
*
* @return T
*
* @throws \RuntimeException If no objects exist
*/
public static function first(string $sortBy = 'id'): object
final public static function first(string $sortBy = 'id'): object
{
return static::repository()->firstOrFail($sortBy);
}

/**
* @final
*
* @return T
*
* @throws \RuntimeException If no objects exist
*/
public static function last(string $sortBy = 'id'): object
final public static function last(string $sortBy = 'id'): object
{
return static::repository()->lastOrFail($sortBy);
}

/**
* @final
*
* @return T[]
*/
public static function all(): array
final public static function all(): array
{
return static::repository()->findAll();
}

/**
* @final
*
* @return RepositoryDecorator<T,ObjectRepository<T>>
*/
public static function repository(): ObjectRepository
final public static function repository(): ObjectRepository
{
Configuration::instance()->assertPersistanceEnabled();

Expand Down
34 changes: 34 additions & 0 deletions src/Persistence/Proxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,32 +21,66 @@
*/
interface Proxy
{
/**
* @psalm-return T&Proxy<T>
* @phpstan-return static
*/
public function _enableAutoRefresh(): static;

/**
* @psalm-return T&Proxy<T>
* @phpstan-return static
*/
public function _disableAutoRefresh(): static;

/**
* @param callable(static):void $callback
* @psalm-return T&Proxy<T>
* @phpstan-return static
*/
public function _withoutAutoRefresh(callable $callback): static;

/**
* @psalm-return T&Proxy<T>
* @phpstan-return static
*/
public function _save(): static;

/**
* @psalm-return T&Proxy<T>
* @phpstan-return static
*/
public function _refresh(): static;

/**
* @psalm-return T&Proxy<T>
* @phpstan-return static
*/
public function _delete(): static;

public function _get(string $property): mixed;

/**
* @psalm-return T&Proxy<T>
* @phpstan-return static
*/
public function _set(string $property, mixed $value): static;

/**
* @return T
*/
public function _real(): object;

/**
* @psalm-return T&Proxy<T>
* @phpstan-return static
*/
public function _assertPersisted(string $message = '{entity} is not persisted.'): static;

/**
* @psalm-return T&Proxy<T>
* @phpstan-return static
*/
public function _assertNotPersisted(string $message = '{entity} is persisted but it should not be.'): static;

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<?php

use Doctrine\ORM\EntityRepository;
use Zenstruck\Foundry\Persistence\PersistentObjectFactory;
use Zenstruck\Foundry\Persistence\RepositoryDecorator;

use function PHPStan\Testing\assertType;
use function Zenstruck\Foundry\Persistence\proxy;
Expand All @@ -12,27 +10,13 @@ class User
public string $name;
}

/**
* @extends EntityRepository<User>
*/
class UserRepository extends EntityRepository
{
public function findByName(string $name): User
{
return new User();
}
}

/**
* The following method stubs are required for auto-completion in PhpStorm
* AND phpstan support.
*
* @extends PersistentObjectFactory<User>
*
* @method User create(array|callable $attributes = [])
* @method static RepositoryDecorator|UserRepository repository()
*
* @phpstan-method static RepositoryDecorator<User,UserRepository> repository()
*/
final class UserFactory extends PersistentObjectFactory
{
Expand All @@ -47,6 +31,26 @@ protected function defaults(): array|callable
}
}

assertType('User', UserFactory::new()->create());
assertType('User', UserFactory::createOne());
assertType('User', UserFactory::new()->many(2)->create()[0]);
assertType('User', UserFactory::new()->sequence([])->create()[0]);
assertType('User', UserFactory::first());
assertType('User', UserFactory::last());
assertType('User', UserFactory::find(1));
assertType('User', UserFactory::random());
assertType('User', UserFactory::findOrCreate([]));
assertType('User', UserFactory::randomOrCreate());
assertType('User|null', UserFactory::repository()->find(1));
assertType("array<User>", UserFactory::all());
assertType("array<User>", UserFactory::createMany(1));
assertType("array<User>", UserFactory::createSequence([]));
assertType("array<User>", UserFactory::randomRange(1, 2));
assertType("array<User>", UserFactory::randomSet(2));
assertType("array<User>", UserFactory::findBy(['name' => 'foo']));
assertType("array<User>", UserFactory::repository()->findAll());
assertType("Zenstruck\Foundry\Persistence\RepositoryDecorator<User, Doctrine\Persistence\ObjectRepository<User>>", UserFactory::repository());

// test autocomplete with phpstorm
assertType('string', UserFactory::new()->create()->name);
assertType('string', UserFactory::createOne()->name);
Expand All @@ -64,7 +68,6 @@ protected function defaults(): array|callable
assertType('string', UserFactory::randomOrCreate([])->name);
assertType('string|null', UserFactory::repository()->find(1)?->name);
assertType('string', UserFactory::repository()->findAll()[0]->name);
assertType('string', UserFactory::repository()->findByName('foo')->name);
assertType('int', UserFactory::repository()->count());
assertType('string', proxy(UserFactory::createOne())->name);
assertType('string', proxy(UserFactory::new()->create())->name);
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<?php

use Doctrine\ORM\EntityRepository;
use Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory;
use Zenstruck\Foundry\Persistence\RepositoryDecorator;

use function PHPStan\Testing\assertType;

Expand All @@ -11,26 +9,11 @@ class User1
public string $name;
}

/**
* @extends EntityRepository<User1>
*/
class UserRepository1 extends EntityRepository
{
public function findByName(string $name): User1
{
return new User1();
}
}

/**
* The following method stubs are required for auto-completion in PhpStorm
* AND phpstan support.
*
* @extends PersistentProxyObjectFactory<User1>
*
* @method static RepositoryDecorator|UserRepository1 repository()
*
* @phpstan-method static RepositoryDecorator<User1,UserRepository1> repository()
*/
final class User1Factory extends PersistentProxyObjectFactory
{
Expand All @@ -45,6 +28,29 @@ protected function defaults(): array|callable
}
}

$proxyType = 'User1&Zenstruck\Foundry\Persistence\Proxy<User1>';
assertType('User1', User1Factory::new()->create()->_real());
assertType($proxyType, User1Factory::new()->create());
assertType($proxyType, User1Factory::new()->create()->_refresh());
assertType($proxyType, User1Factory::createOne());
assertType($proxyType, User1Factory::new()->many(2)->create()[0]);
assertType($proxyType, User1Factory::new()->sequence([])->create()[0]);
assertType($proxyType, User1Factory::first());
assertType($proxyType, User1Factory::last());
assertType($proxyType, User1Factory::find(1));
assertType($proxyType, User1Factory::random());
assertType($proxyType, User1Factory::findOrCreate([]));
assertType($proxyType, User1Factory::randomOrCreate());
assertType("array<{$proxyType}>", User1Factory::all());
assertType("array<{$proxyType}>", User1Factory::createMany(1));
assertType("array<{$proxyType}>", User1Factory::createSequence([]));
assertType("array<{$proxyType}>", User1Factory::randomRange(1, 2));
assertType("array<{$proxyType}>", User1Factory::randomSet(2));
assertType("array<{$proxyType}>", User1Factory::findBy(['name' => 'foo']));
assertType("({$proxyType})|null", User1Factory::repository()->find(1));
assertType("array<{$proxyType}>", User1Factory::repository()->findAll());
assertType("Zenstruck\Foundry\Persistence\RepositoryDecorator<{$proxyType}, Doctrine\Persistence\ObjectRepository<{$proxyType}>>", User1Factory::repository());

// test autocomplete with phpstorm
assertType('string', User1Factory::new()->create()->_refresh()->name);
assertType('string', User1Factory::createOne()->_refresh()->name);
Expand All @@ -62,5 +68,4 @@ protected function defaults(): array|callable
assertType('string', User1Factory::randomOrCreate([])->_refresh()->name);
assertType('string|null', User1Factory::repository()->find(1)?->name);
assertType('string', User1Factory::repository()->findAll()[0]->name);
assertType('string', User1Factory::repository()->findByName('foo')->name);
assertType('int', User1Factory::repository()->count());
File renamed without changes.
Loading

0 comments on commit e3a9d51

Please sign in to comment.