Skip to content

Commit

Permalink
refactor: add a BC layer for 2.x (#515)
Browse files Browse the repository at this point in the history
* refactor: Foundry 2 BC layer

refactor: Foundry 2 BC layer

refactor: deprecate ModelFactory

refactor: deprecate ModelFactory::getDefaults()

refactor: deprecate ModelFactory::getClass()

refactor: add #[\ReturnTypeWillChange] to PersistentProxyObjectFactory::initialize()

refactor: deprecate proxy class

refactor: deprecate stuff in functions.php

refactor: deprecate RepositoryProxy

refactor: deprecate stuff in Instantiator

bot: fix cs [skip ci]

minor: deprecate more stuff

* refactor: deprecate passing callable to sequence

* refactor: deprecate usage of disable/enable_persisting without doctrine

* refactor: deprecate TestState for UnitTestConfig

bot: fix cs [skip ci]

refactor: introduce PersistentObjectFactory and deprecate PersistentProxyObjectFactory with final (#518)

* refactor: introduce PersistentObjectFactory and deprecate PersistentProxyObjectFactory with final

* refactor: introduce persistent_factory()

bot: fix cs [skip ci]

refactor: deprecate config database_resetter (#523)

* refactor: deprecate config database_resetter.orm in favor of orm.reset

* refactor: deprecate config database_resetter.odm in favor of mongo.reset

bot: fix cs [skip ci]

refactor: deprecate config instantiator.without_constructor (#522)

bot: fix cs [skip ci]

refactor: add BC layer for methods in Instantiator (#535)

refactor: change phpstan FactoryCollection return type (#530)

bot: fix cs [skip ci]

refactor: remove not working BC layer on Factory::__construct() (#529)

refactor(bc layer): introduce Proxy interface (#528)

bot: fix cs [skip ci]

refactor: deprecate config auto_refresh_proxies (#524)

bot: fix cs [skip ci]

refactor: better deprecations (#541)

bot: sync with template [skip ci]

bot: fix cs [skip ci]

feat: introduce ObjectFactory (#543)

bot: fix cs [skip ci]

feat: introduce rector rules 🎉 (#544)

refactor: create ObjectFactory with make:factory --no-persistence (#546)

refactor: more deprecations (#547)

* refactor: deprecate Factory::faker() outisde of a factory

* refactor: deprecate FactoryCollection::set()

bot: fix cs [skip ci]

fix: remove directory utils/rector/vendor from git

fix: rename Proxy::get() into Proxy::forceGet()

fix: rollback BC layer on sequence() attributes

fix(rector): handle extending user-defined factory class

fix(rector): proxify create() and instantiate() transformation (#549)

fix(rector): don't remove ->object() call for create() or instantiate()

refctor: deprecate FactoryCollection::factory() (#550)

refactor: deprecate using proxy with anonymous class

bot: fix cs [skip ci]

docs(bc layer): document known BC breaks (#554)

refactor: deprecate $orderBy argument in RepositoryDecorator::findOneBy() (#551)

fix(rector): bump rector/rector 1.0

fix(bc layer): install php cs fixer for maker bundle (#560)

refactor(bc layer): deprecate passing states as string to Factory::new() (#559)

bot: fix cs [skip ci]

minor: fix some types for sca

rector: remove un-needed Factory<Proxy<>> php docs

rector: migrate old proxy phpdoc to generic ones

rector: use getName() instead of cast to string nodes

rector: fix @method parameters rendering

bc: deprecate config auto_refresh_proxies

bc: introduce ProxyRepositoryDecorator

bot: fix cs [skip ci]

fix(proxy): assert object is not scheduled for insert before throwing in _refresh

minor(rector): change doc to comply to rector 1.0

bc: throw deprecation when calling Configuration::disableDefaultProxyAutoRefresh()

bot: fix cs [skip ci]

bc: throw deprecation when calling Configuration::disableDefaultProxyAutoRefresh()

bc: add repo class in ProxyRepositoryDecorator @methods in rector + maker

bot: fix cs [skip ci]

bc: fix Proxy sca problem in ModelFactory

bot: fix cs [skip ci]

bc: fix Proxy sca problem in ModelFactory

bot: fix cs [skip ci]

bc: fix PersistentObjectFactory @methods

fix(bc): add missing import

bc(rector): remove useless "unproxify" array_map

bc(rector): fix factories @method phpdoc

bc(rector): clean rector

bot: fix cs [skip ci]

* rector: rewrite phpdoc (#571)

* fix(rector): repository method is static

* fix(rector) second argument for many() is optional
  • Loading branch information
nikophil committed Mar 25, 2024
1 parent e01d77f commit bbcef6a
Show file tree
Hide file tree
Showing 214 changed files with 10,112 additions and 2,376 deletions.
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@
/phpunit-dama-doctrine.xml.dist export-ignore
/phpunit.xml.dist export-ignore
/tests export-ignore
/utils/rector/tests export-ignore
/utils/rector/composer.json export-ignore
/utils/rector/phpunit.xml.dist export-ignore
40 changes: 35 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ jobs:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.0
php-version: 8.1
coverage: none

- name: Install dependencies
Expand Down Expand Up @@ -203,11 +203,41 @@ jobs:
- name: Run static analysis
run: bin/tools/phpstan/vendor/phpstan/phpstan/phpstan analyse

- name: Install Psalm
run: composer bin psalm install
# there are some problem with psalm and bc layer, around proxy
# - name: Install Psalm
# run: composer bin psalm install
#
# - name: Run Psalm on factories generated with maker
# run: bin/tools/psalm/vendor/vimeo/psalm/psalm

test-rector-rules:
name: Test rector rules
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.3
coverage: none

- name: Install dependencies
uses: ramsey/composer-install@v2
with:
composer-options: --prefer-dist
working-directory: "./utils/rector"

- name: Test
run: vendor/bin/phpunit
shell: bash
working-directory: "./utils/rector"

- name: Run Psalm on factories generated with maker
run: bin/tools/psalm/vendor/vimeo/psalm/psalm
- name: Static analysis
run: vendor/bin/phpstan
shell: bash
working-directory: "./utils/rector"

fixcs:
name: Run php-cs-fixer
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/phpunit-dama-doctrine.xml
/vendor/
/bin/tools/*/vendor/
/bin/tools/php-cs-fixer/composer.lock
/build/
/.php-cs-fixer.cache
/.phpunit.result.cache
Expand All @@ -13,3 +14,7 @@
/.env.local
/docker-compose.override.yaml
/tests/Fixtures/Migrations/

/utils/rector/vendor
/utils/rector/.phpunit.result.cache
/utils/rector/composer.lock
301 changes: 301 additions & 0 deletions UPGRADE-2.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
# Migration guide from Foundry 1.x to 2.0

Foundry 2 has changed some of its API.
The global philosophy is still the same.
The main change is that we've introduced a separation between "object" factories,
"persistence" factories and "persistence with proxy" factories.

When Foundry 1.x was "persistence first", Foundry 2 is "object first".
This would allow more decoupling from the persistence layer.

## How to

Every modification needed for a 1.x to 2.0 migration is covered by a deprecation.
You'll to upgrade to the latest 1.x version, and to activate the deprecation helper, make the tests run, and
fix all the deprecations reported.

Here is an example of how the deprecation helper can be activated.
You should set the `SYMFONY_DEPRECATIONS_HELPER` variable in `phpunit.xml` or `.env.local` file:
```shell
SYMFONY_DEPRECATIONS_HELPER="max[self]=0&amp;max[direct]=0&amp;quiet[]=indirect&amp;quiet[]=other"
```

> [!IMPORTANT]
> Some deprecations can also be sent during compilation step.
> These deprecations can be displayed by using the command: `$ bin/console debug:container --deprecations`
## Rector rules

In the latest 1.x version, you'll find [rector rules](https://getrector.org/) which will help with the migration path.

First, you'll need `rector/rector` and `phpstan/phpstan-doctrine`:
```shell
composer require --dev rector/rector phpstan/phpstan-doctrine
```

Then, create a `rector.php` file:

```php
<?php

use Rector\Config\RectorConfig;
use Zenstruck\Foundry\Utils\Rector\FoundrySetList;

return RectorConfig::configure()
->withPaths(['tests']) // add all paths where Foundry is used
->withSets([FoundrySetList::UP_TO_FOUNDRY_2])
;
```

And finally, run Rector:
```shell
# you can run Rector in "dry run" mode, in order to see which files will be modified
vendor/bin/rector process --dry-run

# actually modify files
vendor/bin/rector process
```

> [!IMPORTANT]
> Rector rules may not totally cover all deprecations (some complex cases may not be handled)
> You'd still need to run the tests with deprecation helper enabled to ensure everything is fixed
> and then fix all deprecations left.
> [!TIP]
> You can try to run twice these rules. Sometimes, the second run will find some difference that it could not spot on
> the first run.
> [!NOTE]
> Once you've finished the migration to 2.0, it is not necessary anymore to keep the Foundry's rule set in your Rector
> config.
### Doctrine's mapping

Rector rules need to understand your Doctrine mapping to guess which one of `PersistentProxyObjectFactory` or
`ObjectFactory` it should use.

If your mapping is defined in the code thanks to attributes or annotations, everything is OK.
If the mapping is defined outside the code, with xml, yaml or php configuration, some extra work is needed:

1. Create a `tests/object-manager.php` file which will expose your doctrine config. Here is an example:
```php
use App\Kernel;
use Symfony\Component\Dotenv\Dotenv;

require __DIR__ . '/../vendor/autoload.php';

(new Dotenv())->bootEnv(__DIR__ . '/../.env');

$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$kernel->boot();
return $kernel->getContainer()->get('doctrine')->getManager();
```

2. Provide this file path to Rector's config:

```php
<?php

use Rector\Config\RectorConfig;
use Zenstruck\Foundry\Utils\Rector\PersistenceResolver;
use Zenstruck\Foundry\Utils\Rector\FoundrySetList;

return static function (RectorConfig $rectorConfig): void {
$rectorConfig->paths(['tests']); // add all paths where Foundry is used
$rectorConfig->sets([FoundrySetList::UP_TO_FOUNDRY_2]);
$rectorConfig->singleton(
PersistenceResolver::class,
static fn() => new PersistenceResolver(__DIR__ . '/tests/object-manager.php')
);
};
```

## Known BC breaks

The following error cannot be covered by Rector rules nor by the deprecation layer.
```
RefreshObjectFailed: Cannot auto refresh "[Entity FQCN]" as there are unsaved changes.
Be sure to call ->_save() or disable auto refreshing.
```

This is an auto-refresh problem, where the "proxified" object is being accessed after modification.
You'll find some [documentation](https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#auto-refresh)
about it.

To mitigate this problem, you should either stop using a proxy object, or wrap the modifications in the method
`->_withoutAutoRefresh()`.

```diff
$proxyPost = PostProxyFactory::createOne();
- $proxyPost->setTitle();
- $proxyPost->setBody(); // 💥
+$proxyPost->_withoutAutoRefresh(
+ function(Post $object) {
+ $proxyPost->setTitle();
+ $proxyPost->setBody();
+ }
+);
```


## Deprecations list

Here is the full list of modifications needed:

### Factory

- `withAttributes()` and `addState()` are both deprecated in favor of `with()`
- `sequence()` and `createSequence()` do not accept `callable` as a parameter anymore

#### Change factories' base class

`Zenstruck\Foundry\ModelFactory` is now deprecated.
You should choose between:
- `\Zenstruck\Foundry\ObjectFactory`: creates not-persistent plain objects,
- `\Zenstruck\Foundry\Persistence\PersistentObjectFactory`: creates and stores persisted objects, and directly return them,
- `\Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory`: same as above, but returns a "proxy" version of the object.
This last class basically acts the same way as the old `ModelFactory`.

As a rule of thumb to help you to choose between these two new factory parent classes:
- using `ObjectFactory` is straightforward: if the object cannot be persisted, you must use this one
- only entities (ORM) or documents (ODM) should use `PersistentObjectFactory` or `PersistentProxyObjectFactory`
- you should only use `PersistentProxyObjectFactory` if you want to leverage "auto refresh" behavior

> [!WARNING]
> nor `PersistentObjectFactory` or `PersistentProxyObjectFactory` should be chosen to create not persistent objects.
> This will throw a deprecation in 1.x and will create an error in 2.0
> [!IMPORTANT]
> Since `PersistentObjectFactory` does not return a `Proxy` anymore, you'll have to remove all calls to `->object()`
> or any other proxy method on object created by this type of factory.
> [!NOTE]
> You will have to change some methods prototypes in your classes:
```php
// before
protected function getDefaults(): array
{
// ...
}

// after
protected function defaults(): array|callable
{
// ...
}
```

```php
// before
protected static function getClass(): string
{
// ...
}

// after
public static function class(): string
{
// ...
}
```

```php
// before
protected function initialize()
{
// ...
}

// after
protected function initialize(); static
{
// ...
}
```

### Proxy

Foundry 2.0 will completely change how `Proxy` system works, by leveraging Symfony's lazy proxy mechanism.
`Proxy` won't be anymore a wrapper class, but a "real" proxy, meaning your objects will be of the desired class AND `Proxy` object.
This implies that calling `->object()` (or, now, `_real()`) everywhere to satisfy the type system won't be needed anymore!

`Proxy` class comes with deprecations as well:
- replace everywhere you're type-hinting `Zenstruck\Foundry\Proxy` to the interface `Zenstruck\Foundry\Persistence\Proxy`
- most of `Proxy` methods are deprecated:
- `object()` -> `_real()`
- `save()` -> `_save()`
- `remove()` -> `_delete()`
- `refresh()` -> `_refresh()`
- `forceSet()` -> `_set()`
- `forceGet()` -> `_get()`
- `repository()` -> `_repository()`
- `enableAutoRefresh()` -> `_enableAutoRefresh()`
- `disableAutoRefresh()` -> `_disableAutoRefresh()`
- `withoutAutoRefresh()` -> `_withoutAutoRefresh()`
- `isPersisted()` is removed without any replacement
- `forceSetAll()` is removed without any replacement
- `assertPersisted()` is removed without any replacement
- `assertNotPersisted()` is removed without any replacement
- Everywhere you've type-hinted `Zenstruck\Foundry\FactoryCollection<T>` which was coming from a `PersistentProxyObjectFactory`, replace to `Zenstruck\Foundry\FactoryCollection<Proxy<T>>`

### Instantiator

- `Zenstruck\Foundry\Instantiator` class is deprecated in favor of `\Zenstruck\Foundry\Object\Instantiator`. You should change them everywhere.
- `new Instantiator()` is deprecated: use `Instantiator::withConstructor()` or `Instantiator::withoutConstructor()` depending on your needs.
- `Instantiator::alwaysForceProperties()` is deprecated in favor of `Instantiator::alwaysForce()`. Be careful of the modification of the parameter which is now a variadic.
- `Instantiator::allowExtraAttributes()` is deprecated in favor of `Instantiator::allowExtra()`. Be careful of the modification of the parameter which is now a variadic.
- Configuration `zenstruck_foundry.without_constructor` is deprecated in favor of `zenstruck_foundry.use_constructor`

### Standalone functions

- `Zenstruck\Foundry\create()` -> `Zenstruck\Foundry\Persistence\persist()`
- `Zenstruck\Foundry\instantiate()` -> `Zenstruck\Foundry\object()`
- `Zenstruck\Foundry\repository()` -> `Zenstruck\Foundry\Persistence\repository()`
- `Zenstruck\Foundry\Factory::delayFlush()` -> `Zenstruck\Foundry\Persistence\flush_after()`
- Usage of any method in `Zenstruck\Foundry\Test\TestState` should be replaced by `Zenstruck\Foundry\Test\UnitTestConfig::configure()`
- `Zenstruck\Foundry\instantiate_many()` is removed without any replacement
- `Zenstruck\Foundry\create_many()` is removed without any replacement

### Trait `Factories`
- `Factories::disablePersist()` -> `Zenstruck\Foundry\Persistence\disable_persisting()`
- `Factories::enablePersist()` -> `Zenstruck\Foundry\Persistence\enable_persisting()`
- both `disablePersist()` and `enable_persisting()` should not be called when Foundry is booted without Doctrine (ie: in a unit test)

### Bundle configuration

Here is a diff of the bundle's configuration, all configs in red should be migrated to the green ones:

```diff
zenstruck_foundry:
- auto_refresh_proxies: null
instantiator:
- without_constructor: false
+ use_constructor: true
+ orm:
+ auto_persist: true
+ reset:
+ connections: [default]
+ entity_managers: [default]
+ mode: schema
+ mongo:
+ auto_persist: true
+ reset:
+ document_managers: [default]
- database_resetter:
- enabled: true
- orm:
- connections: []
- object_managers: []
- reset_mode: schema
- odm:
- object_managers: []
```

### Misc.
- type-hinting to `Zenstruck\Foundry\RepositoryProxy` should be replaced by `Zenstruck\Foundry\Persistence\RepositoryDecorator`
- type-hinting to `Zenstruck\Foundry\RepositoryAssertions` should be replaced by `Zenstruck\Foundry\Persistence\RepositoryAssertions`
- Methods in `Zenstruck\Foundry\RepositoryProxy` do not return `Proxy` anymore, but they return the actual object



0 comments on commit bbcef6a

Please sign in to comment.