Skip to content

Commit bbcef6a

Browse files
authored
refactor: add a BC layer for 2.x (zenstruck#515)
* 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 (zenstruck#518) * refactor: introduce PersistentObjectFactory and deprecate PersistentProxyObjectFactory with final * refactor: introduce persistent_factory() bot: fix cs [skip ci] refactor: deprecate config database_resetter (zenstruck#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 (zenstruck#522) bot: fix cs [skip ci] refactor: add BC layer for methods in Instantiator (zenstruck#535) refactor: change phpstan FactoryCollection return type (zenstruck#530) bot: fix cs [skip ci] refactor: remove not working BC layer on Factory::__construct() (zenstruck#529) refactor(bc layer): introduce Proxy interface (zenstruck#528) bot: fix cs [skip ci] refactor: deprecate config auto_refresh_proxies (zenstruck#524) bot: fix cs [skip ci] refactor: better deprecations (zenstruck#541) bot: sync with template [skip ci] bot: fix cs [skip ci] feat: introduce ObjectFactory (zenstruck#543) bot: fix cs [skip ci] feat: introduce rector rules 🎉 (zenstruck#544) refactor: create ObjectFactory with make:factory --no-persistence (zenstruck#546) refactor: more deprecations (zenstruck#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 (zenstruck#549) fix(rector): don't remove ->object() call for create() or instantiate() refctor: deprecate FactoryCollection::factory() (zenstruck#550) refactor: deprecate using proxy with anonymous class bot: fix cs [skip ci] docs(bc layer): document known BC breaks (zenstruck#554) refactor: deprecate $orderBy argument in RepositoryDecorator::findOneBy() (zenstruck#551) fix(rector): bump rector/rector 1.0 fix(bc layer): install php cs fixer for maker bundle (zenstruck#560) refactor(bc layer): deprecate passing states as string to Factory::new() (zenstruck#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 (zenstruck#571) * fix(rector): repository method is static * fix(rector) second argument for many() is optional
1 parent e01d77f commit bbcef6a

File tree

214 files changed

+10112
-2376
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

214 files changed

+10112
-2376
lines changed

.gitattributes

+3
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,6 @@
1616
/phpunit-dama-doctrine.xml.dist export-ignore
1717
/phpunit.xml.dist export-ignore
1818
/tests export-ignore
19+
/utils/rector/tests export-ignore
20+
/utils/rector/composer.json export-ignore
21+
/utils/rector/phpunit.xml.dist export-ignore

.github/workflows/ci.yml

+35-5
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ jobs:
168168
- name: Setup PHP
169169
uses: shivammathur/setup-php@v2
170170
with:
171-
php-version: 8.0
171+
php-version: 8.1
172172
coverage: none
173173

174174
- name: Install dependencies
@@ -203,11 +203,41 @@ jobs:
203203
- name: Run static analysis
204204
run: bin/tools/phpstan/vendor/phpstan/phpstan/phpstan analyse
205205

206-
- name: Install Psalm
207-
run: composer bin psalm install
206+
# there are some problem with psalm and bc layer, around proxy
207+
# - name: Install Psalm
208+
# run: composer bin psalm install
209+
#
210+
# - name: Run Psalm on factories generated with maker
211+
# run: bin/tools/psalm/vendor/vimeo/psalm/psalm
212+
213+
test-rector-rules:
214+
name: Test rector rules
215+
runs-on: ubuntu-latest
216+
steps:
217+
- name: Checkout code
218+
uses: actions/checkout@v3
219+
220+
- name: Setup PHP
221+
uses: shivammathur/setup-php@v2
222+
with:
223+
php-version: 8.3
224+
coverage: none
225+
226+
- name: Install dependencies
227+
uses: ramsey/composer-install@v2
228+
with:
229+
composer-options: --prefer-dist
230+
working-directory: "./utils/rector"
231+
232+
- name: Test
233+
run: vendor/bin/phpunit
234+
shell: bash
235+
working-directory: "./utils/rector"
208236

209-
- name: Run Psalm on factories generated with maker
210-
run: bin/tools/psalm/vendor/vimeo/psalm/psalm
237+
- name: Static analysis
238+
run: vendor/bin/phpstan
239+
shell: bash
240+
working-directory: "./utils/rector"
211241

212242
fixcs:
213243
name: Run php-cs-fixer

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
/phpunit-dama-doctrine.xml
44
/vendor/
55
/bin/tools/*/vendor/
6+
/bin/tools/php-cs-fixer/composer.lock
67
/build/
78
/.php-cs-fixer.cache
89
/.phpunit.result.cache
@@ -13,3 +14,7 @@
1314
/.env.local
1415
/docker-compose.override.yaml
1516
/tests/Fixtures/Migrations/
17+
18+
/utils/rector/vendor
19+
/utils/rector/.phpunit.result.cache
20+
/utils/rector/composer.lock

UPGRADE-2.0.md

+301
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
# Migration guide from Foundry 1.x to 2.0
2+
3+
Foundry 2 has changed some of its API.
4+
The global philosophy is still the same.
5+
The main change is that we've introduced a separation between "object" factories,
6+
"persistence" factories and "persistence with proxy" factories.
7+
8+
When Foundry 1.x was "persistence first", Foundry 2 is "object first".
9+
This would allow more decoupling from the persistence layer.
10+
11+
## How to
12+
13+
Every modification needed for a 1.x to 2.0 migration is covered by a deprecation.
14+
You'll to upgrade to the latest 1.x version, and to activate the deprecation helper, make the tests run, and
15+
fix all the deprecations reported.
16+
17+
Here is an example of how the deprecation helper can be activated.
18+
You should set the `SYMFONY_DEPRECATIONS_HELPER` variable in `phpunit.xml` or `.env.local` file:
19+
```shell
20+
SYMFONY_DEPRECATIONS_HELPER="max[self]=0&amp;max[direct]=0&amp;quiet[]=indirect&amp;quiet[]=other"
21+
```
22+
23+
> [!IMPORTANT]
24+
> Some deprecations can also be sent during compilation step.
25+
> These deprecations can be displayed by using the command: `$ bin/console debug:container --deprecations`
26+
27+
## Rector rules
28+
29+
In the latest 1.x version, you'll find [rector rules](https://getrector.org/) which will help with the migration path.
30+
31+
First, you'll need `rector/rector` and `phpstan/phpstan-doctrine`:
32+
```shell
33+
composer require --dev rector/rector phpstan/phpstan-doctrine
34+
```
35+
36+
Then, create a `rector.php` file:
37+
38+
```php
39+
<?php
40+
41+
use Rector\Config\RectorConfig;
42+
use Zenstruck\Foundry\Utils\Rector\FoundrySetList;
43+
44+
return RectorConfig::configure()
45+
->withPaths(['tests']) // add all paths where Foundry is used
46+
->withSets([FoundrySetList::UP_TO_FOUNDRY_2])
47+
;
48+
```
49+
50+
And finally, run Rector:
51+
```shell
52+
# you can run Rector in "dry run" mode, in order to see which files will be modified
53+
vendor/bin/rector process --dry-run
54+
55+
# actually modify files
56+
vendor/bin/rector process
57+
```
58+
59+
> [!IMPORTANT]
60+
> Rector rules may not totally cover all deprecations (some complex cases may not be handled)
61+
> You'd still need to run the tests with deprecation helper enabled to ensure everything is fixed
62+
> and then fix all deprecations left.
63+
64+
> [!TIP]
65+
> You can try to run twice these rules. Sometimes, the second run will find some difference that it could not spot on
66+
> the first run.
67+
68+
> [!NOTE]
69+
> Once you've finished the migration to 2.0, it is not necessary anymore to keep the Foundry's rule set in your Rector
70+
> config.
71+
72+
### Doctrine's mapping
73+
74+
Rector rules need to understand your Doctrine mapping to guess which one of `PersistentProxyObjectFactory` or
75+
`ObjectFactory` it should use.
76+
77+
If your mapping is defined in the code thanks to attributes or annotations, everything is OK.
78+
If the mapping is defined outside the code, with xml, yaml or php configuration, some extra work is needed:
79+
80+
1. Create a `tests/object-manager.php` file which will expose your doctrine config. Here is an example:
81+
```php
82+
use App\Kernel;
83+
use Symfony\Component\Dotenv\Dotenv;
84+
85+
require __DIR__ . '/../vendor/autoload.php';
86+
87+
(new Dotenv())->bootEnv(__DIR__ . '/../.env');
88+
89+
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
90+
$kernel->boot();
91+
return $kernel->getContainer()->get('doctrine')->getManager();
92+
```
93+
94+
2. Provide this file path to Rector's config:
95+
96+
```php
97+
<?php
98+
99+
use Rector\Config\RectorConfig;
100+
use Zenstruck\Foundry\Utils\Rector\PersistenceResolver;
101+
use Zenstruck\Foundry\Utils\Rector\FoundrySetList;
102+
103+
return static function (RectorConfig $rectorConfig): void {
104+
$rectorConfig->paths(['tests']); // add all paths where Foundry is used
105+
$rectorConfig->sets([FoundrySetList::UP_TO_FOUNDRY_2]);
106+
$rectorConfig->singleton(
107+
PersistenceResolver::class,
108+
static fn() => new PersistenceResolver(__DIR__ . '/tests/object-manager.php')
109+
);
110+
};
111+
```
112+
113+
## Known BC breaks
114+
115+
The following error cannot be covered by Rector rules nor by the deprecation layer.
116+
```
117+
RefreshObjectFailed: Cannot auto refresh "[Entity FQCN]" as there are unsaved changes.
118+
Be sure to call ->_save() or disable auto refreshing.
119+
```
120+
121+
This is an auto-refresh problem, where the "proxified" object is being accessed after modification.
122+
You'll find some [documentation](https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#auto-refresh)
123+
about it.
124+
125+
To mitigate this problem, you should either stop using a proxy object, or wrap the modifications in the method
126+
`->_withoutAutoRefresh()`.
127+
128+
```diff
129+
$proxyPost = PostProxyFactory::createOne();
130+
- $proxyPost->setTitle();
131+
- $proxyPost->setBody(); // 💥
132+
+$proxyPost->_withoutAutoRefresh(
133+
+ function(Post $object) {
134+
+ $proxyPost->setTitle();
135+
+ $proxyPost->setBody();
136+
+ }
137+
+);
138+
```
139+
140+
141+
## Deprecations list
142+
143+
Here is the full list of modifications needed:
144+
145+
### Factory
146+
147+
- `withAttributes()` and `addState()` are both deprecated in favor of `with()`
148+
- `sequence()` and `createSequence()` do not accept `callable` as a parameter anymore
149+
150+
#### Change factories' base class
151+
152+
`Zenstruck\Foundry\ModelFactory` is now deprecated.
153+
You should choose between:
154+
- `\Zenstruck\Foundry\ObjectFactory`: creates not-persistent plain objects,
155+
- `\Zenstruck\Foundry\Persistence\PersistentObjectFactory`: creates and stores persisted objects, and directly return them,
156+
- `\Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory`: same as above, but returns a "proxy" version of the object.
157+
This last class basically acts the same way as the old `ModelFactory`.
158+
159+
As a rule of thumb to help you to choose between these two new factory parent classes:
160+
- using `ObjectFactory` is straightforward: if the object cannot be persisted, you must use this one
161+
- only entities (ORM) or documents (ODM) should use `PersistentObjectFactory` or `PersistentProxyObjectFactory`
162+
- you should only use `PersistentProxyObjectFactory` if you want to leverage "auto refresh" behavior
163+
164+
> [!WARNING]
165+
> nor `PersistentObjectFactory` or `PersistentProxyObjectFactory` should be chosen to create not persistent objects.
166+
> This will throw a deprecation in 1.x and will create an error in 2.0
167+
168+
> [!IMPORTANT]
169+
> Since `PersistentObjectFactory` does not return a `Proxy` anymore, you'll have to remove all calls to `->object()`
170+
> or any other proxy method on object created by this type of factory.
171+
172+
> [!NOTE]
173+
> You will have to change some methods prototypes in your classes:
174+
175+
```php
176+
// before
177+
protected function getDefaults(): array
178+
{
179+
// ...
180+
}
181+
182+
// after
183+
protected function defaults(): array|callable
184+
{
185+
// ...
186+
}
187+
```
188+
189+
```php
190+
// before
191+
protected static function getClass(): string
192+
{
193+
// ...
194+
}
195+
196+
// after
197+
public static function class(): string
198+
{
199+
// ...
200+
}
201+
```
202+
203+
```php
204+
// before
205+
protected function initialize()
206+
{
207+
// ...
208+
}
209+
210+
// after
211+
protected function initialize(); static
212+
{
213+
// ...
214+
}
215+
```
216+
217+
### Proxy
218+
219+
Foundry 2.0 will completely change how `Proxy` system works, by leveraging Symfony's lazy proxy mechanism.
220+
`Proxy` won't be anymore a wrapper class, but a "real" proxy, meaning your objects will be of the desired class AND `Proxy` object.
221+
This implies that calling `->object()` (or, now, `_real()`) everywhere to satisfy the type system won't be needed anymore!
222+
223+
`Proxy` class comes with deprecations as well:
224+
- replace everywhere you're type-hinting `Zenstruck\Foundry\Proxy` to the interface `Zenstruck\Foundry\Persistence\Proxy`
225+
- most of `Proxy` methods are deprecated:
226+
- `object()` -> `_real()`
227+
- `save()` -> `_save()`
228+
- `remove()` -> `_delete()`
229+
- `refresh()` -> `_refresh()`
230+
- `forceSet()` -> `_set()`
231+
- `forceGet()` -> `_get()`
232+
- `repository()` -> `_repository()`
233+
- `enableAutoRefresh()` -> `_enableAutoRefresh()`
234+
- `disableAutoRefresh()` -> `_disableAutoRefresh()`
235+
- `withoutAutoRefresh()` -> `_withoutAutoRefresh()`
236+
- `isPersisted()` is removed without any replacement
237+
- `forceSetAll()` is removed without any replacement
238+
- `assertPersisted()` is removed without any replacement
239+
- `assertNotPersisted()` is removed without any replacement
240+
- Everywhere you've type-hinted `Zenstruck\Foundry\FactoryCollection<T>` which was coming from a `PersistentProxyObjectFactory`, replace to `Zenstruck\Foundry\FactoryCollection<Proxy<T>>`
241+
242+
### Instantiator
243+
244+
- `Zenstruck\Foundry\Instantiator` class is deprecated in favor of `\Zenstruck\Foundry\Object\Instantiator`. You should change them everywhere.
245+
- `new Instantiator()` is deprecated: use `Instantiator::withConstructor()` or `Instantiator::withoutConstructor()` depending on your needs.
246+
- `Instantiator::alwaysForceProperties()` is deprecated in favor of `Instantiator::alwaysForce()`. Be careful of the modification of the parameter which is now a variadic.
247+
- `Instantiator::allowExtraAttributes()` is deprecated in favor of `Instantiator::allowExtra()`. Be careful of the modification of the parameter which is now a variadic.
248+
- Configuration `zenstruck_foundry.without_constructor` is deprecated in favor of `zenstruck_foundry.use_constructor`
249+
250+
### Standalone functions
251+
252+
- `Zenstruck\Foundry\create()` -> `Zenstruck\Foundry\Persistence\persist()`
253+
- `Zenstruck\Foundry\instantiate()` -> `Zenstruck\Foundry\object()`
254+
- `Zenstruck\Foundry\repository()` -> `Zenstruck\Foundry\Persistence\repository()`
255+
- `Zenstruck\Foundry\Factory::delayFlush()` -> `Zenstruck\Foundry\Persistence\flush_after()`
256+
- Usage of any method in `Zenstruck\Foundry\Test\TestState` should be replaced by `Zenstruck\Foundry\Test\UnitTestConfig::configure()`
257+
- `Zenstruck\Foundry\instantiate_many()` is removed without any replacement
258+
- `Zenstruck\Foundry\create_many()` is removed without any replacement
259+
260+
### Trait `Factories`
261+
- `Factories::disablePersist()` -> `Zenstruck\Foundry\Persistence\disable_persisting()`
262+
- `Factories::enablePersist()` -> `Zenstruck\Foundry\Persistence\enable_persisting()`
263+
- both `disablePersist()` and `enable_persisting()` should not be called when Foundry is booted without Doctrine (ie: in a unit test)
264+
265+
### Bundle configuration
266+
267+
Here is a diff of the bundle's configuration, all configs in red should be migrated to the green ones:
268+
269+
```diff
270+
zenstruck_foundry:
271+
- auto_refresh_proxies: null
272+
instantiator:
273+
- without_constructor: false
274+
+ use_constructor: true
275+
+ orm:
276+
+ auto_persist: true
277+
+ reset:
278+
+ connections: [default]
279+
+ entity_managers: [default]
280+
+ mode: schema
281+
+ mongo:
282+
+ auto_persist: true
283+
+ reset:
284+
+ document_managers: [default]
285+
- database_resetter:
286+
- enabled: true
287+
- orm:
288+
- connections: []
289+
- object_managers: []
290+
- reset_mode: schema
291+
- odm:
292+
- object_managers: []
293+
```
294+
295+
### Misc.
296+
- type-hinting to `Zenstruck\Foundry\RepositoryProxy` should be replaced by `Zenstruck\Foundry\Persistence\RepositoryDecorator`
297+
- type-hinting to `Zenstruck\Foundry\RepositoryAssertions` should be replaced by `Zenstruck\Foundry\Persistence\RepositoryAssertions`
298+
- Methods in `Zenstruck\Foundry\RepositoryProxy` do not return `Proxy` anymore, but they return the actual object
299+
300+
301+

0 commit comments

Comments
 (0)