Repository: symfony/ux
Pull request #2928 (2025-07-17 06:51:16)

[Autocomplete] Upgrade Foundry to ^2.0
| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | no <!-- please update src/**/CHANGELOG.md files -->
| Docs?         | no <!-- required for new features -->
| Issues        | Fix #... <!-- prefix each issue number with "Fix #", no need to create an issue if none exist, explain below instead -->
| License       | MIT

<!--
Replace this notice by a description of your feature/bugfix.
This will help reviewers and should be a good start for the documentation.

Additionally (see https://symfony.com/releases):
 - Always add tests and ensure they pass.
 - For new features, provide some code snippets to help understand usage.
 - Features and deprecations must be submitted against branch main.
 - Update/add documentation as required (we can help!)
 - Changelog entry should follow https://symfony.com/doc/current/contributing/code/conventions.html#writing-a-changelog-entry
 - Never break backward compatibility (see https://symfony.com/bc).
-->

Minor work to upgrade Foundry 1.37.* to ^2.0 on Autocomplete, and remove some Foundry deprecations (also applied to LiveComponent)


Patch:
diff --git a/src/Autocomplete/composer.json b/src/Autocomplete/composer.json
index 040fdf15..f8253d95 100644
--- a/src/Autocomplete/composer.json
+++ b/src/Autocomplete/composer.json
@@ -48,7 +48,7 @@
         "symfony/uid": "^6.3|^7.0",
         "twig/twig": "^2.14.7|^3.0.4",
         "zenstruck/browser": "^1.1",
-        "zenstruck/foundry": "1.37.*"
+        "zenstruck/foundry": "^2.0"
     },
     "conflict": {
         "doctrine/orm": "2.9.0 || 2.9.1"


Test patch:
diff --git a/src/Autocomplete/tests/Fixtures/Factory/CategoryFactory.php b/src/Autocomplete/tests/Fixtures/Factory/CategoryFactory.php
index 8e255cb2..71ef991a 100644
--- a/src/Autocomplete/tests/Fixtures/Factory/CategoryFactory.php
+++ b/src/Autocomplete/tests/Fixtures/Factory/CategoryFactory.php
@@ -11,33 +11,15 @@
 
 namespace Symfony\UX\Autocomplete\Tests\Fixtures\Factory;
 
-use Doctrine\ORM\EntityRepository;
 use Symfony\UX\Autocomplete\Tests\Fixtures\Entity\Category;
-use Zenstruck\Foundry\ModelFactory;
-use Zenstruck\Foundry\Proxy;
-use Zenstruck\Foundry\RepositoryProxy;
+use Zenstruck\Foundry\Persistence\PersistentObjectFactory;
 
 /**
- * @extends ModelFactory<Category>
- *
- * @method static Category|Proxy                   createOne(array $attributes = [])
- * @method static Category[]|Proxy[]               createMany(int $number, array|callable $attributes = [])
- * @method static Category|Proxy                   find(object|array|mixed $criteria)
- * @method static Category|Proxy                   findOrCreate(array $attributes)
- * @method static Category|Proxy                   first(string $sortedField = 'id')
- * @method static Category|Proxy                   last(string $sortedField = 'id')
- * @method static Category|Proxy                   random(array $attributes = [])
- * @method static Category|Proxy                   randomOrCreate(array $attributes = []))
- * @method static Category[]|Proxy[]               all()
- * @method static Category[]|Proxy[]               findBy(array $attributes)
- * @method static Category[]|Proxy[]               randomSet(int $number, array $attributes = []))
- * @method static Category[]|Proxy[]               randomRange(int $min, int $max, array $attributes = []))
- * @method static EntityRepository|RepositoryProxy repository()
- * @method        Category|Proxy                   create(array|callable $attributes = [])
+ * @extends PersistentObjectFactory<Category>
  */
-final class CategoryFactory extends ModelFactory
+final class CategoryFactory extends PersistentObjectFactory
 {
-    protected function getDefaults(): array
+    protected function defaults(): array
     {
         $name = self::faker()->name();
         return [
@@ -46,12 +28,7 @@ final class CategoryFactory extends ModelFactory
         ];
     }
 
-    protected function initialize(): self
-    {
-        return $this;
-    }
-
-    protected static function getClass(): string
+    public static function class(): string
     {
         return Category::class;
     }
diff --git a/src/Autocomplete/tests/Fixtures/Factory/CategoryTagFactory.php b/src/Autocomplete/tests/Fixtures/Factory/CategoryTagFactory.php
index 32eec81c..9234df82 100644
--- a/src/Autocomplete/tests/Fixtures/Factory/CategoryTagFactory.php
+++ b/src/Autocomplete/tests/Fixtures/Factory/CategoryTagFactory.php
@@ -11,46 +11,23 @@
 
 namespace Symfony\UX\Autocomplete\Tests\Fixtures\Factory;
 
-use Doctrine\ORM\EntityRepository;
 use Symfony\UX\Autocomplete\Tests\Fixtures\Entity\CategoryTag;
-use Zenstruck\Foundry\ModelFactory;
-use Zenstruck\Foundry\Proxy;
-use Zenstruck\Foundry\RepositoryProxy;
+use Zenstruck\Foundry\Persistence\PersistentObjectFactory;
 
 /**
- * @extends ModelFactory<CategoryTag>
- *
- * @method static CategoryTag|Proxy                createOne(array $attributes = [])
- * @method static CategoryTag[]|Proxy[]            createMany(int $number, array|callable $attributes = [])
- * @method static CategoryTag|Proxy                find(object|array|mixed $criteria)
- * @method static CategoryTag|Proxy                findOrCreate(array $attributes)
- * @method static CategoryTag|Proxy                first(string $sortedField = 'id')
- * @method static CategoryTag|Proxy                last(string $sortedField = 'id')
- * @method static CategoryTag|Proxy                random(array $attributes = [])
- * @method static CategoryTag|Proxy                randomOrCreate(array $attributes = [])
- * @method static CategoryTag[]|Proxy[]            all()
- * @method static CategoryTag[]|Proxy[]            findBy(array $attributes)
- * @method static CategoryTag[]|Proxy[]            randomSet(int $number, array $attributes = [])
- * @method static CategoryTag[]|Proxy[]            randomRange(int $min, int $max, array $attributes = [])
- * @method static EntityRepository|RepositoryProxy repository()
- * @method        CategoryTag|Proxy                create(array|callable $attributes = [])
+ * @extends PersistentObjectFactory<CategoryTag>
  */
-final class CategoryTagFactory extends ModelFactory
+final class CategoryTagFactory extends PersistentObjectFactory
 {
-    protected function getDefaults(): array
-    {
-        return [
-            'name' => self::faker()->word(),
-        ];
-    }
-
-    protected function initialize(): self
+    public static function class(): string
     {
-        return $this;
+        return CategoryTag::class;
     }
 
-    protected static function getClass(): string
+    protected function defaults(): array
     {
-        return CategoryTag::class;
+        return [
+            'name' => self::faker()->word(),
+        ];
     }
 }
diff --git a/src/Autocomplete/tests/Fixtures/Factory/IngredientFactory.php b/src/Autocomplete/tests/Fixtures/Factory/IngredientFactory.php
index 4669fa98..6f12a56a 100644
--- a/src/Autocomplete/tests/Fixtures/Factory/IngredientFactory.php
+++ b/src/Autocomplete/tests/Fixtures/Factory/IngredientFactory.php
@@ -11,48 +11,25 @@
 
 namespace Symfony\UX\Autocomplete\Tests\Fixtures\Factory;
 
-use Doctrine\ORM\EntityRepository;
 use Symfony\Component\Uid\UuidV4;
 use Symfony\UX\Autocomplete\Tests\Fixtures\Entity\Ingredient;
-use Zenstruck\Foundry\ModelFactory;
-use Zenstruck\Foundry\Proxy;
-use Zenstruck\Foundry\RepositoryProxy;
+use Zenstruck\Foundry\Persistence\PersistentObjectFactory;
 
 /**
- * @extends ModelFactory<Ingredient>
- *
- * @method static Ingredient|Proxy                 createOne(array $attributes = [])
- * @method static Ingredient[]|Proxy[]             createMany(int $number, array|callable $attributes = [])
- * @method static Ingredient|Proxy                 find(object|array|mixed $criteria)
- * @method static Ingredient|Proxy                 findOrCreate(array $attributes)
- * @method static Ingredient|Proxy                 first(string $sortedField = 'id')
- * @method static Ingredient|Proxy                 last(string $sortedField = 'id')
- * @method static Ingredient|Proxy                 random(array $attributes = [])
- * @method static Ingredient|Proxy                 randomOrCreate(array $attributes = []))
- * @method static Ingredient[]|Proxy[]             all()
- * @method static Ingredient[]|Proxy[]             findBy(array $attributes)
- * @method static Ingredient[]|Proxy[]             randomSet(int $number, array $attributes = []))
- * @method static Ingredient[]|Proxy[]             randomRange(int $min, int $max, array $attributes = []))
- * @method static EntityRepository|RepositoryProxy repository()
- * @method        Ingredient|Proxy                 create(array|callable $attributes = [])
+ * @extends PersistentObjectFactory<Ingredient>
  */
-final class IngredientFactory extends ModelFactory
+final class IngredientFactory extends PersistentObjectFactory
 {
-    protected function getDefaults(): array
+    public static function class(): string
+    {
+        return Ingredient::class;
+    }
+
+    protected function defaults(): array
     {
         return [
             'id' => new UuidV4(),
             'name' => self::faker()->text(),
         ];
     }
-
-    protected function initialize(): self
-    {
-        return $this;
-    }
-
-    protected static function getClass(): string
-    {
-        return Ingredient::class;
-    }
 }
diff --git a/src/Autocomplete/tests/Fixtures/Factory/ProductFactory.php b/src/Autocomplete/tests/Fixtures/Factory/ProductFactory.php
index 975c10be..264486d1 100644
--- a/src/Autocomplete/tests/Fixtures/Factory/ProductFactory.php
+++ b/src/Autocomplete/tests/Fixtures/Factory/ProductFactory.php
@@ -13,31 +13,19 @@ namespace Symfony\UX\Autocomplete\Tests\Fixtures\Factory;
 
 use Doctrine\ORM\EntityRepository;
 use Symfony\UX\Autocomplete\Tests\Fixtures\Entity\Product;
-use Zenstruck\Foundry\ModelFactory;
-use Zenstruck\Foundry\Proxy;
-use Zenstruck\Foundry\RepositoryProxy;
+use Zenstruck\Foundry\Persistence\PersistentObjectFactory;
 
 /**
- * @extends ModelFactory<Product>
- *
- * @method static Product|Proxy                    createOne(array $attributes = [])
- * @method static Product[]|Proxy[]                createMany(int $number, array|callable $attributes = [])
- * @method static Product|Proxy                    find(object|array|mixed $criteria)
- * @method static Product|Proxy                    findOrCreate(array $attributes)
- * @method static Product|Proxy                    first(string $sortedField = 'id')
- * @method static Product|Proxy                    last(string $sortedField = 'id')
- * @method static Product|Proxy                    random(array $attributes = [])
- * @method static Product|Proxy                    randomOrCreate(array $attributes = []))
- * @method static Product[]|Proxy[]                all()
- * @method static Product[]|Proxy[]                findBy(array $attributes)
- * @method static Product[]|Proxy[]                randomSet(int $number, array $attributes = []))
- * @method static Product[]|Proxy[]                randomRange(int $min, int $max, array $attributes = []))
- * @method static EntityRepository|RepositoryProxy repository()
- * @method        Product|Proxy                    create(array|callable $attributes = [])
+ * @extends PersistentObjectFactory<Product>
  */
-final class ProductFactory extends ModelFactory
+final class ProductFactory extends PersistentObjectFactory
 {
-    protected function getDefaults(): array
+    public static function class(): string
+    {
+        return Product::class;
+    }
+
+    protected function defaults(): array
     {
         return [
             'name' => self::faker()->text(),
@@ -47,18 +35,8 @@ final class ProductFactory extends ModelFactory
         ];
     }
 
-    protected function initialize(): self
-    {
-        return $this;
-    }
-
     public function disable(): self
     {
-        return $this->addState(['isEnabled' => false]);
-    }
-
-    protected static function getClass(): string
-    {
-        return Product::class;
+        return $this->with(['isEnabled' => false]);
     }
 }
diff --git a/src/Autocomplete/tests/Fixtures/Factory/ProductTagFactory.php b/src/Autocomplete/tests/Fixtures/Factory/ProductTagFactory.php
index 868d61fc..424f76f5 100644
--- a/src/Autocomplete/tests/Fixtures/Factory/ProductTagFactory.php
+++ b/src/Autocomplete/tests/Fixtures/Factory/ProductTagFactory.php
@@ -14,43 +14,24 @@ namespace Symfony\UX\Autocomplete\Tests\Fixtures\Factory;
 use Doctrine\ORM\EntityRepository;
 use Symfony\UX\Autocomplete\Tests\Fixtures\Entity\ProductTag;
 use Zenstruck\Foundry\ModelFactory;
+use Zenstruck\Foundry\Persistence\PersistentObjectFactory;
 use Zenstruck\Foundry\Proxy;
 use Zenstruck\Foundry\RepositoryProxy;
 
 /**
- * @extends ModelFactory<ProductTag>
- *
- * @method static ProductTag|Proxy                createOne(array $attributes = [])
- * @method static ProductTag[]|Proxy[]            createMany(int $number, array|callable $attributes = [])
- * @method static ProductTag|Proxy                find(object|array|mixed $criteria)
- * @method static ProductTag|Proxy                findOrCreate(array $attributes)
- * @method static ProductTag|Proxy                first(string $sortedField = 'id')
- * @method static ProductTag|Proxy                last(string $sortedField = 'id')
- * @method static ProductTag|Proxy                random(array $attributes = [])
- * @method static ProductTag|Proxy                randomOrCreate(array $attributes = [])
- * @method static ProductTag[]|Proxy[]            all()
- * @method static ProductTag[]|Proxy[]            findBy(array $attributes)
- * @method static ProductTag[]|Proxy[]            randomSet(int $number, array $attributes = [])
- * @method static ProductTag[]|Proxy[]            randomRange(int $min, int $max, array $attributes = [])
- * @method static EntityRepository|RepositoryProxy repository()
- * @method        ProductTag|Proxy                create(array|callable $attributes = [])
+ * @extends PersistentObjectFactory<ProductTag>
  */
-final class ProductTagFactory extends ModelFactory
+final class ProductTagFactory extends PersistentObjectFactory
 {
-    protected function getDefaults(): array
-    {
-        return [
-            'name' => self::faker()->word(),
-        ];
-    }
-
-    protected function initialize(): self
+    public static function class(): string
     {
-        return $this;
+        return ProductTag::class;
     }
 
-    protected static function getClass(): string
+    protected function defaults(): array
     {
-        return ProductTag::class;
+        return [
+            'name' => self::faker()->word(),
+        ];
     }
 }
diff --git a/src/Autocomplete/tests/Fixtures/Kernel.php b/src/Autocomplete/tests/Fixtures/Kernel.php
index 7fc8adcf..b8439ae4 100644
--- a/src/Autocomplete/tests/Fixtures/Kernel.php
+++ b/src/Autocomplete/tests/Fixtures/Kernel.php
@@ -101,9 +101,7 @@ final class Kernel extends BaseKernel
             'default_path' => '%kernel.project_dir%/tests/Fixtures/templates',
         ]);
 
-        $c->extension('zenstruck_foundry', [
-            'auto_refresh_proxies' => false,
-        ]);
+        $c->extension('zenstruck_foundry', []);
 
         $doctrineConfig = [
             'dbal' => ['url' => '%env(resolve:DATABASE_URL)%'],
@@ -155,10 +153,6 @@ final class Kernel extends BaseKernel
             ],
         ]);
 
-        $c->extension('zenstruck_foundry', [
-            'auto_refresh_proxies' => false,
-        ]);
-
         $services = $c->services();
         $services
             ->defaults()
diff --git a/src/Autocomplete/tests/Integration/Doctrine/EntityMetadataTest.php b/src/Autocomplete/tests/Integration/Doctrine/EntityMetadataTest.php
index 40bed6f7..0d6a73df 100644
--- a/src/Autocomplete/tests/Integration/Doctrine/EntityMetadataTest.php
+++ b/src/Autocomplete/tests/Integration/Doctrine/EntityMetadataTest.php
@@ -43,7 +43,7 @@ class EntityMetadataTest extends KernelTestCase
     public function testGetIdValue(): void
     {
         $product = ProductFactory::createOne();
-        $this->assertEquals($product->getId(), $this->getMetadata()->getIdValue($product->object()));
+        $this->assertEquals($product->getId(), $this->getMetadata()->getIdValue($product));
     }
 
     public function testGetPropertyDataType(): void
diff --git a/src/Autocomplete/tests/Integration/Doctrine/EntitySearchUtilTest.php b/src/Autocomplete/tests/Integration/Doctrine/EntitySearchUtilTest.php
index e10560fc..90dd33b3 100644
--- a/src/Autocomplete/tests/Integration/Doctrine/EntitySearchUtilTest.php
+++ b/src/Autocomplete/tests/Integration/Doctrine/EntitySearchUtilTest.php
@@ -34,7 +34,7 @@ class EntitySearchUtilTest extends KernelTestCase
         $prod4 = ProductFactory::createOne(['description' => 'all about prod 4']);
 
         $results = $this->callAddSearchClass('prod');
-        $this->assertSame([$prod1->object(), $prod2->object(), $prod4->object()], $results);
+        $this->assertSame([$prod1, $prod2, $prod4], $results);
     }
 
     public function testItSearchesOnCorrectFields(): void
@@ -43,7 +43,7 @@ class EntitySearchUtilTest extends KernelTestCase
         ProductFactory::createOne(['description' => 'foo prod2']);
 
         $results = $this->callAddSearchClass('prod', ['name']);
-        $this->assertSame([$prod1->object()], $results);
+        $this->assertSame([$prod1], $results);
     }
 
     public function testItCanSearchOnRelationFields(): void
@@ -55,7 +55,7 @@ class EntitySearchUtilTest extends KernelTestCase
         ProductFactory::createOne(['name' => 'puzzle', 'category' => $category2]);
 
         $results = $this->callAddSearchClass('food', ['name', 'category.name']);
-        $this->assertSame([$prod1->object(), $prod2->object()], $results);
+        $this->assertSame([$prod1, $prod2], $results);
     }
 
     /**
diff --git a/src/LiveComponent/tests/Fixtures/Kernel.php b/src/LiveComponent/tests/Fixtures/Kernel.php
index 4b0cd90d..9b39acb9 100644
--- a/src/LiveComponent/tests/Fixtures/Kernel.php
+++ b/src/LiveComponent/tests/Fixtures/Kernel.php
@@ -143,9 +143,7 @@ final class Kernel extends BaseKernel
             'anonymous_template_directory' => 'components/',
         ]);
 
-        $c->extension('zenstruck_foundry', [
-            'auto_refresh_proxies' => false,
-        ]);
+        $c->extension('zenstruck_foundry', []);
 
         $doctrineConfig = [
             'dbal' => [