diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md new file mode 100644 index 00000000..f1101dff --- /dev/null +++ b/UPGRADE-2.0.md @@ -0,0 +1,180 @@ +# 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. +All you have to do is to upgrade to the latest 1.x version, 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&max[direct]=0&quiet[]=indirect&quiet[]=other" +``` + +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\Object\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` which was coming from a `PersistentProxyObjectFactory`, replace to `Zenstruck\Foundry\FactoryCollection>` + +## 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` + + + diff --git a/composer.json b/composer.json index 89897213..8a9db416 100644 --- a/composer.json +++ b/composer.json @@ -53,7 +53,6 @@ "psr-4": {"Zenstruck\\Foundry\\": "src/"}, "files": [ "src/functions.php", - "src/deprecations.php", "src/Persistence/functions.php" ] }, diff --git a/src/Bundle/DependencyInjection/ZenstruckFoundryExtension.php b/src/Bundle/DependencyInjection/ZenstruckFoundryExtension.php index 02446ba4..ee32e9f8 100644 --- a/src/Bundle/DependencyInjection/ZenstruckFoundryExtension.php +++ b/src/Bundle/DependencyInjection/ZenstruckFoundryExtension.php @@ -22,7 +22,7 @@ use Symfony\Component\HttpKernel\DependencyInjection\ConfigurableExtension; use Zenstruck\Foundry\Bundle\Command\StubMakeFactory; use Zenstruck\Foundry\Bundle\Command\StubMakeStory; -use Zenstruck\Foundry\Instantiator; +use \Zenstruck\Foundry\Object\Instantiator; use Zenstruck\Foundry\Persistence\PersistentObjectFactory; use Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory; use Zenstruck\Foundry\Story; @@ -127,7 +127,7 @@ private function configureDatabaseResetter(array $config, ContainerBuilder $cont $legacyConfig = $config['database_resetter']; if (false === $legacyConfig['enabled']) { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Disabling database reset via bundle configuration is deprecated and will be removed in 2.0. Instead you should not use "%s" trait in your test.', ResetDatabase::class)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Disabling database reset via bundle configuration is deprecated and will be removed in 2.0. Instead you should not use "%s" trait in your test.', ResetDatabase::class); $configurationDefinition->addMethodCall('disableDatabaseReset'); } diff --git a/src/Bundle/Maker/Factory/NoPersistenceObjectsAutoCompleter.php b/src/Bundle/Maker/Factory/NoPersistenceObjectsAutoCompleter.php index c5469823..984ffba0 100644 --- a/src/Bundle/Maker/Factory/NoPersistenceObjectsAutoCompleter.php +++ b/src/Bundle/Maker/Factory/NoPersistenceObjectsAutoCompleter.php @@ -34,7 +34,7 @@ public function getAutocompleteValues(): array $class = $this->toPSR4($rootPath, $phpFile, $namespacePrefix); - if (\in_array($class, ['Zenstruck\Foundry\Proxy', 'Zenstruck\Foundry\RepositoryProxy'])) { + if (\in_array($class, ['Zenstruck\Foundry\Proxy', 'Zenstruck\Foundry\RepositoryProxy', 'Zenstruck\Foundry\RepositoryAssertions'])) { // do not load legacy Proxy: prevents deprecations in tests. continue; } diff --git a/src/Bundle/Resources/config/services.xml b/src/Bundle/Resources/config/services.xml index cf9e0931..6574a3ea 100644 --- a/src/Bundle/Resources/config/services.xml +++ b/src/Bundle/Resources/config/services.xml @@ -5,8 +5,8 @@ https://symfony.com/schema/dic/services/services-1.0.xsd"> - - + + diff --git a/src/Factory.php b/src/Factory.php index beba9ef6..b6b00267 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -99,7 +99,7 @@ public function create( bool $noProxy = false ): object { if (2 === \count(\func_get_args()) && !\str_starts_with(\debug_backtrace(options: \DEBUG_BACKTRACE_IGNORE_ARGS, limit: 1)[0]['class'] ?? '', 'Zenstruck\Foundry')) { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Parameter "$noProxy" of method "%s()" is deprecated and will be removed in Foundry 2.0.', __METHOD__)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Parameter "$noProxy" of method "%s()" is deprecated and will be removed in Foundry 2.0.', __METHOD__); } // merge the factory attribute set with the passed attributes @@ -203,7 +203,7 @@ final public function many(int $min, ?int $max = null): FactoryCollection final public function sequence(iterable|callable $sequence): FactoryCollection { if (\is_callable($sequence)) { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Passing a callable to method "%s()" is deprecated and will be removed in 2.0.', __METHOD__)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Passing a callable to method "%s()" is deprecated and will be removed in 2.0.', __METHOD__); $sequence = $sequence(); } @@ -231,7 +231,7 @@ public function withoutPersisting(): self */ final public function withAttributes($attributes = []): self { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Method "%s()" is deprecated and will be removed in 2.0. Use "%s::with()" instead.', __METHOD__, self::class)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Method "%s()" is deprecated and will be removed in 2.0. Use "%s::with()" instead.', __METHOD__, self::class); return $this->with($attributes); } @@ -352,7 +352,7 @@ final public static function faker(): Faker\Generator */ final public static function delayFlush(callable $callback): mixed { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Method "%s()" is deprecated and will be removed in Foundry 2.0. Use "Zenstruck\Foundry\Persistence\flush_after()" instead.', __METHOD__)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Method "%s()" is deprecated and will be removed in Foundry 2.0. Use "Zenstruck\Foundry\Persistence\flush_after()" instead.', __METHOD__); return self::configuration()->delayFlush($callback); } diff --git a/src/FactoryCollection.php b/src/FactoryCollection.php index fafacade..33646a83 100644 --- a/src/FactoryCollection.php +++ b/src/FactoryCollection.php @@ -76,7 +76,7 @@ public function create( bool $noProxy = false ): array { if (2 === \count(\func_get_args()) && !\str_starts_with(\debug_backtrace(options: \DEBUG_BACKTRACE_IGNORE_ARGS, limit: 1)[0]['class'] ?? '', 'Zenstruck\Foundry')) { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Parameter "$noProxy" of method "%s()" is deprecated and will be removed in Foundry 2.0.', __METHOD__)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Parameter "$noProxy" of method "%s()" is deprecated and will be removed in Foundry 2.0.', __METHOD__); } $objects = []; diff --git a/src/Instantiator.php b/src/Instantiator.php index d005c158..1fd51c76 100644 --- a/src/Instantiator.php +++ b/src/Instantiator.php @@ -11,322 +11,26 @@ namespace Zenstruck\Foundry; -use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException; -use Symfony\Component\PropertyAccess\PropertyAccess; -use Symfony\Component\PropertyAccess\PropertyAccessor; -use function Symfony\Component\String\u; +use Zenstruck\Foundry\Object\Instantiator as NewInstatiator; -/** - * @author Kevin Bond - * - * @method static self withoutConstructor() - * @method self withoutConstructor() - */ -final class Instantiator -{ - private static ?PropertyAccessor $propertyAccessor = null; - - private bool $useConstructor = true; - - private bool $allowExtraAttributes = false; - - private array $extraAttributes = []; - - private bool $alwaysForceProperties = false; - - /** @var string[] */ - private array $forceProperties = []; - - public function __construct(bool $calledInternally = false) - { - if (!$calledInternally) { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('%1$s constructor will be private in Foundry 2.0. Use either "%1$s::withConstructor()" or "%1$s::withoutConstructor()"', self::class)); - } - } - - /** - * @param class-string $class - */ - public function __invoke(array $attributes, string $class): object - { - $object = $this->instantiate($class, $attributes); - - foreach ($attributes as $attribute => $value) { - if (0 === \mb_strpos($attribute, 'optional:')) { - trigger_deprecation('zenstruck\foundry', '1.5.0', 'Using "optional:" attribute prefixes is deprecated, use Instantiator::allowExtra() instead (https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#instantiation).'); - continue; - } - - if (\in_array($attribute, $this->extraAttributes, true)) { - continue; - } - - if ($this->alwaysForceProperties || \in_array($attribute, $this->forceProperties, true)) { - try { - self::forceSet($object, $attribute, $value); - } catch (\InvalidArgumentException $e) { - if (!$this->allowExtraAttributes) { - throw $e; - } - } - - continue; - } - - if (0 === \mb_strpos($attribute, 'force:')) { - trigger_deprecation('zenstruck\foundry', '1.5.0', 'Using "force:" property prefixes is deprecated, use Instantiator::alwaysForce() instead (https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#instantiation).'); - - self::forceSet($object, \mb_substr($attribute, 6), $value); - - continue; - } - - try { - self::propertyAccessor()->setValue($object, $attribute, $value); - } catch (NoSuchPropertyException $e) { - // see if attribute was snake/kebab cased - try { - self::propertyAccessor()->setValue($object, self::camel($attribute), $value); - trigger_deprecation('zenstruck\foundry', '1.5.0', 'Using a differently cased attribute is deprecated, use the same case as the object property instead.'); - } catch (NoSuchPropertyException $e) { - if (!$this->allowExtraAttributes) { - throw new \InvalidArgumentException(\sprintf('Cannot set attribute "%s" for object "%s" (not public and no setter).', $attribute, $class), 0, $e); - } - } - } - } - - return $object; - } - - public function __call(string $name, array $arguments): self - { - if ('withoutConstructor' !== $name) { - throw new \BadMethodCallException(\sprintf('Call to undefined method "%s::%s".', static::class, $name)); - } - - trigger_deprecation('zenstruck/foundry', '1.37.0', 'Calling instance method "%1$s::withoutConstructor()" is deprecated and will be removed in 2.0. Use static call instead: "%1$s::withoutConstructor()" instead.', static::class); - - $this->useConstructor = false; - - return $this; - } - - public static function __callStatic(string $name, array $arguments): self - { - if ('withoutConstructor' !== $name) { - throw new \BadMethodCallException(\sprintf('Call to undefined method "%s::%s".', static::class, $name)); - } - - $instance = new self(calledInternally: true); - - $instance->useConstructor = false; - - return $instance; - } - - public static function withConstructor(): self - { - return new self(calledInternally: true); - } - - /** - * Ignore attributes that can't be set to object. - * - * @param string[] $attributes The attributes you'd like the instantiator to ignore (if empty, ignore any extra) - * - * @deprecated Use self::allowExtra() instead - */ - public function allowExtraAttributes(array $attributes = []): self - { - trigger_deprecation('zenstruck/foundry', '1.37.0', 'Method "Instantiator::allowExtraAttributes()" is deprecated. Please use "Instantiator::allowExtra()" instead.'); - - return $this->allowExtra(...$attributes); - } - - /** - * Ignore attributes that can't be set to object. - * - * @param string $parameters The attributes you'd like the instantiator to ignore (if empty, ignore any extra) - */ - public function allowExtra(string ...$parameters): self - { - if (empty($parameters)) { - $this->allowExtraAttributes = true; - } - - $this->extraAttributes = $parameters; - - return $this; - } - - /** - * Always force properties, never use setters (still uses constructor unless disabled). - * - * @param string[] $properties The properties you'd like the instantiator to "force set" (if empty, force set all) - * - * @deprecated Use self::alwaysForce() instead - */ - public function alwaysForceProperties(array $properties = []): self - { - trigger_deprecation('zenstruck/foundry', '1.37.0', 'Method "Instantiator::alwaysForceProperties()" is deprecated. Please use "Instantiator::alwaysForce()" instead.'); - - return $this->alwaysForce(...$properties); - } - - /** - * Always force properties, never use setters (still uses constructor unless disabled). - * - * @param string $properties The properties you'd like the instantiator to "force set" (if empty, force set all) - */ - public function alwaysForce(string ...$properties): self - { - if (empty($properties)) { - $this->alwaysForceProperties = true; - } - - $this->forceProperties = $properties; - - return $this; - } - - /** - * @throws \InvalidArgumentException if property does not exist for $object - */ - public static function forceSet(object $object, string $property, mixed $value): void - { - self::accessibleProperty($object, $property)->setValue($object, $value); - } - - /** - * @return mixed - */ - public static function forceGet(object $object, string $property) - { - return self::accessibleProperty($object, $property)->getValue($object); - } - - private static function propertyAccessor(): PropertyAccessor - { - return self::$propertyAccessor ?: self::$propertyAccessor = PropertyAccess::createPropertyAccessor(); - } - - private static function accessibleProperty(object $object, string $name): \ReflectionProperty - { - $class = new \ReflectionClass($object); - - // try fetching first by exact name, if not found, try camel-case - $property = self::reflectionProperty($class, $name); - - if (!$property && $property = self::reflectionProperty($class, self::camel($name))) { - trigger_deprecation('zenstruck\foundry', '1.5.0', 'Using a differently cased attribute is deprecated, use the same case as the object property instead.'); - } - - if (!$property) { - throw new \InvalidArgumentException(\sprintf('Class "%s" does not have property "%s".', $class->getName(), $name)); - } - - if (!$property->isPublic()) { - $property->setAccessible(true); - } - - return $property; - } - - private static function reflectionProperty(\ReflectionClass $class, string $name): ?\ReflectionProperty - { - try { - return $class->getProperty($name); - } catch (\ReflectionException) { - if ($class = $class->getParentClass()) { - return self::reflectionProperty($class, $name); - } - } - - return null; - } - - /** - * Check if parameter value was passed as the exact name - if not, try snake-cased, then kebab-cased versions. - */ - private static function attributeNameForParameter(\ReflectionParameter $parameter, array $attributes): ?string - { - // try exact - $name = $parameter->getName(); - - if (\array_key_exists($name, $attributes)) { - return $name; - } - - // try snake case - $name = self::snake($name); - - if (\array_key_exists($name, $attributes)) { - trigger_deprecation('zenstruck\foundry', '1.5.0', 'Using a differently cased attribute is deprecated, use the same case as the object property instead.'); - - return $name; - } - - // try kebab case - $name = \str_replace('_', '-', $name); - - if (\array_key_exists($name, $attributes)) { - trigger_deprecation('zenstruck\foundry', '1.5.0', 'Using a differently cased attribute is deprecated, use the same case as the object property instead.'); - - return $name; - } - - return null; - } - - private static function camel(string $string): string - { - return u($string)->camel(); - } +if (!class_exists(NewInstatiator::class, false)) { + trigger_deprecation( + 'zenstruck\foundry', + '1.37.0', + 'Class "%s" is deprecated and will be removed in version 2.0. Use "%s" instead.', + Instantiator::class, + NewInstatiator::class + ); +} - private static function snake(string $string): string - { - return u($string)->snake(); - } +\class_alias(NewInstatiator::class, Instantiator::class); +if (false) { /** - * @param class-string $class + * @deprecated */ - private function instantiate(string $class, array &$attributes): object + final class Instantiator extends NewInstatiator { - $class = new \ReflectionClass($class); - $constructor = $class->getConstructor(); - - if (!$this->useConstructor || !$constructor || !$constructor->isPublic()) { - if ($this->useConstructor && $constructor && !$constructor->isPublic()) { - trigger_deprecation('zenstruck\foundry', '1.37.0', 'Instantiator was created to instantiate "%s" by calling the constructor whereas the constructor is not public. This is deprecated and will throw an exception in Foundry 2.0. Use "%s::withoutConstructor()" instead or make constructor public.', $class->getName(), self::class); - } - - return $class->newInstanceWithoutConstructor(); - } - - $arguments = []; - - foreach ($constructor->getParameters() as $parameter) { - $name = self::attributeNameForParameter($parameter, $attributes); - - if ($name && \array_key_exists($name, $attributes)) { - if ($parameter->isVariadic()) { - $arguments = \array_merge($arguments, $attributes[$name]); - } else { - $arguments[] = $attributes[$name]; - } - } elseif ($parameter->isDefaultValueAvailable()) { - $arguments[] = $parameter->getDefaultValue(); - } else { - throw new \InvalidArgumentException(\sprintf('Missing constructor argument "%s" for "%s".', $parameter->getName(), $class->getName())); - } - - // unset attribute so it isn't used when setting object properties - unset($attributes[$name]); - } - - return $class->newInstance(...$arguments); } } diff --git a/src/ModelFactory.php b/src/ModelFactory.php index 8b7ec423..fbb2e904 100644 --- a/src/ModelFactory.php +++ b/src/ModelFactory.php @@ -11,6 +11,7 @@ namespace Zenstruck\Foundry; +use Zenstruck\Foundry\Object\ObjectFactory; use Zenstruck\Foundry\Persistence\PersistentObjectFactory; use Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory; use Zenstruck\Foundry\Persistence\Proxy; @@ -30,22 +31,22 @@ abstract class ModelFactory extends PersistentProxyObjectFactory { public function __construct() { - $newFactoryClass = (new \ReflectionClass(static::class()))->isFinal() ? PersistentObjectFactory::class : PersistentProxyObjectFactory::class; + $newFactoryClass = (new \ReflectionClass(static::class()))->isFinal() ? PersistentObjectFactory::class : ObjectFactory::class; trigger_deprecation( 'zenstruck\foundry', '1.37.0', - \sprintf( - << + * + * @final + * + * @method static self withoutConstructor() + */ +class Instantiator +{ + private static ?PropertyAccessor $propertyAccessor = null; + + private bool $useConstructor = true; + + private bool $allowExtraAttributes = false; + + private array $extraAttributes = []; + + private bool $alwaysForceProperties = false; + + /** @var string[] */ + private array $forceProperties = []; + + public function __construct(bool $calledInternally = false) + { + if (!$calledInternally) { + trigger_deprecation('zenstruck\foundry', '1.37.0', '%1$s constructor will be private in Foundry 2.0. Use either "%1$s::withConstructor()" or "%1$s::withoutConstructor()"', self::class); + } + } + + /** + * @param class-string $class + */ + public function __invoke(array $attributes, string $class): object + { + $object = $this->instantiate($class, $attributes); + + foreach ($attributes as $attribute => $value) { + if (0 === \mb_strpos($attribute, 'optional:')) { + trigger_deprecation('zenstruck\foundry', '1.5.0', 'Using "optional:" attribute prefixes is deprecated, use Instantiator::allowExtra() instead (https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#instantiation).'); + continue; + } + + if (\in_array($attribute, $this->extraAttributes, true)) { + continue; + } + + if ($this->alwaysForceProperties || \in_array($attribute, $this->forceProperties, true)) { + try { + self::forceSet($object, $attribute, $value); + } catch (\InvalidArgumentException $e) { + if (!$this->allowExtraAttributes) { + throw $e; + } + } + + continue; + } + + if (0 === \mb_strpos($attribute, 'force:')) { + trigger_deprecation('zenstruck\foundry', '1.5.0', 'Using "force:" property prefixes is deprecated, use Instantiator::alwaysForce() instead (https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#instantiation).'); + + self::forceSet($object, \mb_substr($attribute, 6), $value); + + continue; + } + + try { + self::propertyAccessor()->setValue($object, $attribute, $value); + } catch (NoSuchPropertyException $e) { + // see if attribute was snake/kebab cased + try { + self::propertyAccessor()->setValue($object, self::camel($attribute), $value); + trigger_deprecation('zenstruck\foundry', '1.5.0', 'Using a differently cased attribute is deprecated, use the same case as the object property instead.'); + } catch (NoSuchPropertyException $e) { + if (!$this->allowExtraAttributes) { + throw new \InvalidArgumentException(\sprintf('Cannot set attribute "%s" for object "%s" (not public and no setter).', $attribute, $class), 0, $e); + } + } + } + } + + return $object; + } + + public function __call(string $name, array $arguments): self + { + if ('withoutConstructor' !== $name) { + throw new \BadMethodCallException(\sprintf('Call to undefined method "%s::%s".', static::class, $name)); + } + + trigger_deprecation('zenstruck/foundry', '1.37.0', 'Calling instance method "%1$s::withoutConstructor()" is deprecated and will be removed in 2.0. Use static call instead: "%1$s::withoutConstructor()" instead.', static::class); + + $this->useConstructor = false; + + return $this; + } + + public static function __callStatic(string $name, array $arguments): self + { + if ('withoutConstructor' !== $name) { + throw new \BadMethodCallException(\sprintf('Call to undefined method "%s::%s".', static::class, $name)); + } + + $instance = new self(calledInternally: true); + + $instance->useConstructor = false; + + return $instance; + } + + public static function withConstructor(): self + { + return new self(calledInternally: true); + } + + /** + * Ignore attributes that can't be set to object. + * + * @param string[] $attributes The attributes you'd like the instantiator to ignore (if empty, ignore any extra) + * + * @deprecated Use self::allowExtra() instead + */ + public function allowExtraAttributes(array $attributes = []): self + { + trigger_deprecation('zenstruck/foundry', '1.37.0', 'Method "Instantiator::allowExtraAttributes()" is deprecated. Please use "Instantiator::allowExtra()" instead.'); + + return $this->allowExtra(...$attributes); + } + + /** + * Ignore attributes that can't be set to object. + * + * @param string $parameters The attributes you'd like the instantiator to ignore (if empty, ignore any extra) + */ + public function allowExtra(string ...$parameters): self + { + if (empty($parameters)) { + $this->allowExtraAttributes = true; + } + + $this->extraAttributes = $parameters; + + return $this; + } + + /** + * Always force properties, never use setters (still uses constructor unless disabled). + * + * @param string[] $properties The properties you'd like the instantiator to "force set" (if empty, force set all) + * + * @deprecated Use self::alwaysForce() instead + */ + public function alwaysForceProperties(array $properties = []): self + { + trigger_deprecation('zenstruck/foundry', '1.37.0', 'Method "Instantiator::alwaysForceProperties()" is deprecated. Please use "Instantiator::alwaysForce()" instead.'); + + return $this->alwaysForce(...$properties); + } + + /** + * Always force properties, never use setters (still uses constructor unless disabled). + * + * @param string $properties The properties you'd like the instantiator to "force set" (if empty, force set all) + */ + public function alwaysForce(string ...$properties): self + { + if (empty($properties)) { + $this->alwaysForceProperties = true; + } + + $this->forceProperties = $properties; + + return $this; + } + + /** + * @throws \InvalidArgumentException if property does not exist for $object + */ + public static function forceSet(object $object, string $property, mixed $value): void + { + self::accessibleProperty($object, $property)->setValue($object, $value); + } + + /** + * @return mixed + */ + public static function forceGet(object $object, string $property) + { + return self::accessibleProperty($object, $property)->getValue($object); + } + + private static function propertyAccessor(): PropertyAccessor + { + return self::$propertyAccessor ?: self::$propertyAccessor = PropertyAccess::createPropertyAccessor(); + } + + private static function accessibleProperty(object $object, string $name): \ReflectionProperty + { + $class = new \ReflectionClass($object); + + // try fetching first by exact name, if not found, try camel-case + $property = self::reflectionProperty($class, $name); + + if (!$property && $property = self::reflectionProperty($class, self::camel($name))) { + trigger_deprecation('zenstruck\foundry', '1.5.0', 'Using a differently cased attribute is deprecated, use the same case as the object property instead.'); + } + + if (!$property) { + throw new \InvalidArgumentException(\sprintf('Class "%s" does not have property "%s".', $class->getName(), $name)); + } + + if (!$property->isPublic()) { + $property->setAccessible(true); + } + + return $property; + } + + private static function reflectionProperty(\ReflectionClass $class, string $name): ?\ReflectionProperty + { + try { + return $class->getProperty($name); + } catch (\ReflectionException) { + if ($class = $class->getParentClass()) { + return self::reflectionProperty($class, $name); + } + } + + return null; + } + + /** + * Check if parameter value was passed as the exact name - if not, try snake-cased, then kebab-cased versions. + */ + private static function attributeNameForParameter(\ReflectionParameter $parameter, array $attributes): ?string + { + // try exact + $name = $parameter->getName(); + + if (\array_key_exists($name, $attributes)) { + return $name; + } + + // try snake case + $name = self::snake($name); + + if (\array_key_exists($name, $attributes)) { + trigger_deprecation('zenstruck\foundry', '1.5.0', 'Using a differently cased attribute is deprecated, use the same case as the object property instead.'); + + return $name; + } + + // try kebab case + $name = \str_replace('_', '-', $name); + + if (\array_key_exists($name, $attributes)) { + trigger_deprecation('zenstruck\foundry', '1.5.0', 'Using a differently cased attribute is deprecated, use the same case as the object property instead.'); + + return $name; + } + + return null; + } + + private static function camel(string $string): string + { + return u($string)->camel()->toString(); + } + + private static function snake(string $string): string + { + return u($string)->snake()->toString(); + } + + /** + * @param class-string $class + */ + private function instantiate(string $class, array &$attributes): object + { + $class = new \ReflectionClass($class); + $constructor = $class->getConstructor(); + + if (!$this->useConstructor || !$constructor || !$constructor->isPublic()) { + if ($this->useConstructor && $constructor && !$constructor->isPublic()) { + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Instantiator was created to instantiate "%s" by calling the constructor whereas the constructor is not public. This is deprecated and will throw an exception in Foundry 2.0. Use "%s::withoutConstructor()" instead or make constructor public.', $class->getName(), self::class); + } + + return $class->newInstanceWithoutConstructor(); + } + + $arguments = []; + + foreach ($constructor->getParameters() as $parameter) { + $name = self::attributeNameForParameter($parameter, $attributes); + + if ($name && \array_key_exists($name, $attributes)) { + if ($parameter->isVariadic()) { + $arguments = \array_merge($arguments, $attributes[$name]); + } else { + $arguments[] = $attributes[$name]; + } + } elseif ($parameter->isDefaultValueAvailable()) { + $arguments[] = $parameter->getDefaultValue(); + } else { + throw new \InvalidArgumentException(\sprintf('Missing constructor argument "%s" for "%s".', $parameter->getName(), $class->getName())); + } + + // unset attribute so it isn't used when setting object properties + unset($attributes[$name]); + } + + return $class->newInstance(...$arguments); + } +} + +class_exists(\Zenstruck\Foundry\Instantiator::class); diff --git a/src/Persistence/PersistentObjectFactory.php b/src/Persistence/PersistentObjectFactory.php index 257db864..85c3b0fe 100644 --- a/src/Persistence/PersistentObjectFactory.php +++ b/src/Persistence/PersistentObjectFactory.php @@ -101,7 +101,7 @@ public function create( bool $noProxy = false ): object { if (2 === \count(\func_get_args()) && !\str_starts_with(\debug_backtrace(options: \DEBUG_BACKTRACE_IGNORE_ARGS, limit: 1)[0]['class'] ?? '', 'Zenstruck\Foundry')) { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Parameter "$noProxy" of method "%s()" is deprecated and will be removed in Foundry 2.0.', __METHOD__)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Parameter "$noProxy" of method "%s()" is deprecated and will be removed in Foundry 2.0.', __METHOD__); } return parent::create( @@ -340,7 +340,7 @@ protected function initialize() */ final protected function addState(array|callable $attributes = []): static { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Method "%s()" is deprecated and will be removed in version 2.0. Use "%s::with()" instead.', __METHOD__, Factory::class)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Method "%s()" is deprecated and will be removed in version 2.0. Use "%s::with()" instead.', __METHOD__, Factory::class); return $this->with($attributes); } diff --git a/src/Persistence/PersistentProxyObjectFactory.php b/src/Persistence/PersistentProxyObjectFactory.php index 18a818bd..ef637076 100644 --- a/src/Persistence/PersistentProxyObjectFactory.php +++ b/src/Persistence/PersistentProxyObjectFactory.php @@ -44,7 +44,9 @@ final public static function new(array|callable|string $defaultAttributes = [], if ((new \ReflectionClass(static::class()))->isFinal()) { trigger_deprecation( 'zenstruck\foundry', '1.37.0', - \sprintf('Using a proxy factory with a final class is deprecated and will throw an error in Foundry 2.0. Use "%s" instead, or unfinalize "%s" class.', self::class, static::class()) + 'Using a proxy factory with a final class is deprecated and will throw an error in Foundry 2.0. Use "Zenstruck\Foundry\Object\ObjectFactory" instead (don\'t forget to remove all ->object() calls!).', + self::class, + static::class() ); } @@ -63,7 +65,7 @@ final public function create( bool $noProxy = false ): object { if (2 === \count(\func_get_args()) && !\str_starts_with(\debug_backtrace(options: \DEBUG_BACKTRACE_IGNORE_ARGS, limit: 1)[0]['class'] ?? '', 'Zenstruck\Foundry')) { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Parameter "$noProxy" of method "%s()" is deprecated and will be removed in Foundry 2.0.', __METHOD__)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Parameter "$noProxy" of method "%s()" is deprecated and will be removed in Foundry 2.0.', __METHOD__); } return Factory::create($attributes, noProxy: false); diff --git a/src/Persistence/Proxy.php b/src/Persistence/Proxy.php index 25ffc4e4..9bba6dff 100644 --- a/src/Persistence/Proxy.php +++ b/src/Persistence/Proxy.php @@ -14,6 +14,8 @@ /** * @template TProxiedObject of object * + * @mixin TProxiedObject + * * @author Kevin Bond * * @final @@ -51,4 +53,73 @@ public function _disableAutoRefresh(): static; * @param callable $callback (object|Proxy $object): void */ public function _withoutAutoRefresh(callable $callback): static; + + /** + * @return TProxiedObject + * + * @deprecated Use method "_real()" instead + */ + public function object(): object; + + /** + * @deprecated Use method "_save()" instead + */ + public function save(): static; + + /** + * @deprecated Use method "_delete()" instead + */ + public function remove(): static; + + /** + * @deprecated Use method "_refresh()" instead + */ + public function refresh(): static; + + /** + * @deprecated Use method "_set()" instead + */ + public function forceSet(string $property, mixed $value): static; + + /** + * @deprecated without replacement + */ + public function forceSetAll(array $properties): static; + + /** + * @deprecated Use method "_get()" instead + */ + public function get(string $property): mixed; + + /** + * @deprecated Use method "_repository()" instead + */ + public function repository(): RepositoryDecorator; + + /** + * @deprecated Use method "_enableAutoRefresh()" instead + */ + public function enableAutoRefresh(): static; + + /** + * @deprecated Use method "_disableAutoRefresh()" instead + */ + public function disableAutoRefresh(): static; + + /** + * @param callable $callback (object|Proxy $object): void + * + * @deprecated Use method "_withoutAutoRefresh()" instead + */ + public function withoutAutoRefresh(callable $callback): static; + + /** + * @deprecated without replacement + */ + public function assertPersisted(string $message = '{entity} is not persisted.'): self; + + /** + * @deprecated without replacement + */ + public function assertNotPersisted(string $message = '{entity} is persisted but it should not be.'): self; } diff --git a/src/Persistence/RepositoryAssertions.php b/src/Persistence/RepositoryAssertions.php index 3eb13724..f80d11c5 100644 --- a/src/Persistence/RepositoryAssertions.php +++ b/src/Persistence/RepositoryAssertions.php @@ -15,8 +15,10 @@ /** * @author Kevin Bond + * + * @final */ -final class RepositoryAssertions +class RepositoryAssertions { private static string $countWithoutCriteriaDeprecationMessagePattern = 'Passing the message to %s() as second parameter is deprecated. Use third parameter.'; @@ -136,3 +138,5 @@ public function notExists($criteria, string $message = 'Expected {entity} to not return $this; } } + +class_exists(\Zenstruck\Foundry\RepositoryAssertions::class); diff --git a/src/Persistence/RepositoryDecorator.php b/src/Persistence/RepositoryDecorator.php index 2d6ff5c4..1690514e 100644 --- a/src/Persistence/RepositoryDecorator.php +++ b/src/Persistence/RepositoryDecorator.php @@ -31,8 +31,10 @@ * @template TProxiedObject of object * * @author Kevin Bond + * + * @final */ -final class RepositoryDecorator implements ObjectRepository, \IteratorAggregate, \Countable +class RepositoryDecorator implements ObjectRepository, \IteratorAggregate, \Countable { /** * @param ObjectRepository $repository @@ -457,3 +459,5 @@ private function getObjectManager(): ObjectManager return Factory::configuration()->objectManagerFor($this->getClassName()); } } + +class_exists(\Zenstruck\Foundry\RepositoryProxy::class); diff --git a/src/Proxy.php b/src/Proxy.php index 60fb707b..2e076788 100644 --- a/src/Proxy.php +++ b/src/Proxy.php @@ -54,7 +54,9 @@ public function __construct( if ((new \ReflectionClass($object::class))->isFinal()) { trigger_deprecation( 'zenstruck\foundry', '1.37.0', - \sprintf('Using a proxy factory with a final class is deprecated and will throw an error in Foundry 2.0. Use "%s" instead, or unfinalize "%s" class.', PersistentProxyObjectFactory::class, $object::class) + 'Using a proxy factory with a final class is deprecated and will throw an error in Foundry 2.0. Use "%s" instead (don\'t forget to remember all ->object() calls!), or unfinalize "%s" class.', + PersistentProxyObjectFactory::class, + $object::class ); } @@ -114,12 +116,12 @@ public static function createFromPersisted(object $object): self } /** - * @deprecated + * @deprecated without replacement */ public function isPersisted(bool $calledInternally = false): bool { if (!$calledInternally) { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Method "%s()" is deprecated and will be removed in 2.0 without replacement.', __METHOD__)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Method "%s()" is deprecated and will be removed in 2.0 without replacement.', __METHOD__); } return $this->persisted; @@ -128,11 +130,11 @@ public function isPersisted(bool $calledInternally = false): bool /** * @return TProxiedObject * - * @deprecated + * @deprecated Use method "_real()" instead */ public function object(): object { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Method "%s()" is deprecated and will be removed in 2.0. Use "%s::_real()" instead.', __METHOD__, self::class)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Method "%s()" is deprecated and will be removed in 2.0. Use "%s::_real()" instead.', __METHOD__, self::class); return $this->_real(); } @@ -163,11 +165,11 @@ public function _real(): object } /** - * @deprecated + * @deprecated Use method "_save()" instead */ public function save(): static { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Method "%s()" is deprecated and will be removed in 2.0. Use "%s::_save()" instead.', __METHOD__, self::class)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Method "%s()" is deprecated and will be removed in 2.0. Use "%s::_save()" instead.', __METHOD__, self::class); return $this->_save(); } @@ -186,11 +188,11 @@ public function _save(): static } /** - * @deprecated + * @deprecated Use method "_delete()" instead */ public function remove(): static { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Method "%s()" is deprecated and will be removed in 2.0. Use "%s::_delete()" instead.', __METHOD__, self::class)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Method "%s()" is deprecated and will be removed in 2.0. Use "%s::_delete()" instead.', __METHOD__, self::class); return $this->_delete(); } @@ -206,11 +208,11 @@ public function _delete(): static } /** - * @deprecated + * @deprecated Use method "_refresh()" instead */ public function refresh(): static { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Method "%s()" is deprecated and will be removed in 2.0. Use "%s::_refresh()" instead.', __METHOD__, self::class)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Method "%s()" is deprecated and will be removed in 2.0. Use "%s::_refresh()" instead.', __METHOD__, self::class); return $this->_refresh(); } @@ -241,11 +243,11 @@ public function _refresh(): static } /** - * @deprecated + * @deprecated Use method "_set()" instead */ public function forceSet(string $property, mixed $value): static { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Method "%s()" is deprecated and will be removed in 2.0. Use "%s::_set()" instead.', __METHOD__, self::class)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Method "%s()" is deprecated and will be removed in 2.0. Use "%s::_set()" instead.', __METHOD__, self::class); return $this->_set($property, $value); } @@ -260,11 +262,11 @@ public function _set(string $property, mixed $value): static } /** - * @deprecated + * @deprecated without replacement */ public function forceSetAll(array $properties): static { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Method "%s()" is deprecated and will be removed in 2.0 without replacement.', __METHOD__)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Method "%s()" is deprecated and will be removed in 2.0 without replacement.', __METHOD__); $object = $this->_real(); @@ -276,11 +278,11 @@ public function forceSetAll(array $properties): static } /** - * @deprecated + * @deprecated Use method "_get()" instead */ public function get(string $property): mixed { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Method "%s()" is deprecated and will be removed in 2.0. Use "%s::_get()" instead.', __METHOD__, self::class)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Method "%s()" is deprecated and will be removed in 2.0. Use "%s::_get()" instead.', __METHOD__, self::class); return $this->_get($property); } @@ -291,11 +293,11 @@ public function _get(string $property): mixed } /** - * @deprecated + * @deprecated Use method "_repository()" instead */ public function repository(): RepositoryDecorator { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Method "%s()" is deprecated and will be removed in 2.0. Use "%s::_repository()" instead.', __METHOD__, self::class)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Method "%s()" is deprecated and will be removed in 2.0. Use "%s::_repository()" instead.', __METHOD__, self::class); return $this->_repository(); } @@ -306,11 +308,11 @@ public function _repository(): RepositoryDecorator } /** - * @deprecated + * @deprecated Use method "_enableAutoRefresh()" instead */ public function enableAutoRefresh(): static { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Method "%s()" is deprecated and will be removed in 2.0. Use "%s::_enableAutoRefresh()" instead.', __METHOD__, self::class)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Method "%s()" is deprecated and will be removed in 2.0. Use "%s::_enableAutoRefresh()" instead.', __METHOD__, self::class); return $this->_enableAutoRefresh(); } @@ -327,11 +329,11 @@ public function _enableAutoRefresh(): static } /** - * @deprecated + * @deprecated Use method "_disableAutoRefresh()" instead */ public function disableAutoRefresh(): static { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Method "%s()" is deprecated and will be removed in 2.0. Use "%s::_disableAutoRefresh()" instead.', __METHOD__, self::class)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Method "%s()" is deprecated and will be removed in 2.0. Use "%s::_disableAutoRefresh()" instead.', __METHOD__, self::class); return $this->_disableAutoRefresh(); } @@ -346,11 +348,11 @@ public function _disableAutoRefresh(): static /** * @param callable $callback (object|Proxy $object): void * - * @deprecated + * @deprecated Use method "_withoutAutoRefresh()" instead */ public function withoutAutoRefresh(callable $callback): static { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Method "%s()" is deprecated and will be removed in 2.0. Use "%s::_withoutAutoRefresh()" instead.', __METHOD__, self::class)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Method "%s()" is deprecated and will be removed in 2.0. Use "%s::_withoutAutoRefresh()" instead.', __METHOD__, self::class); return $this->_withoutAutoRefresh($callback); } @@ -368,11 +370,11 @@ public function _withoutAutoRefresh(callable $callback): static } /** - * @deprecated + * @deprecated without replacement */ public function assertPersisted(string $message = '{entity} is not persisted.'): self { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Method "%s()" is deprecated and will be removed in 2.0 without replacement.', __METHOD__)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Method "%s()" is deprecated and will be removed in 2.0 without replacement.', __METHOD__); Assert::that($this->fetchObject())->isNotEmpty($message, ['entity' => $this->class]); @@ -380,11 +382,11 @@ public function assertPersisted(string $message = '{entity} is not persisted.'): } /** - * @deprecated + * @deprecated without replacement */ public function assertNotPersisted(string $message = '{entity} is persisted but it should not be.'): self { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Method "%s()" is deprecated and will be removed in 2.0 without replacement.', __METHOD__)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Method "%s()" is deprecated and will be removed in 2.0 without replacement.', __METHOD__); Assert::that($this->fetchObject())->isEmpty($message, ['entity' => $this->class]); diff --git a/src/RepositoryAssertions.php b/src/RepositoryAssertions.php index 9e92ebe7..75314165 100644 --- a/src/RepositoryAssertions.php +++ b/src/RepositoryAssertions.php @@ -13,13 +13,23 @@ use Zenstruck\Foundry\Persistence\RepositoryAssertions as NewRepositoryAssertions; -trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Class "%s" is deprecated and will be removed in version 2.0. Use "%s" instead.', RepositoryAssertions::class, NewRepositoryAssertions::class)); +if (!class_exists(NewRepositoryAssertions::class, false)) { + trigger_deprecation( + 'zenstruck\foundry', + '1.37.0', + 'Class "%s" is deprecated and will be removed in version 2.0. Use "%s" instead.', + RepositoryAssertions::class, + NewRepositoryAssertions::class + ); +} + +\class_alias(NewRepositoryAssertions::class, RepositoryAssertions::class); if (false) { /** * @deprecated */ - final class RepositoryAssertions + final class RepositoryAssertions extends NewRepositoryAssertions { } } diff --git a/src/RepositoryProxy.php b/src/RepositoryProxy.php index 623034f9..6fe16b9d 100644 --- a/src/RepositoryProxy.php +++ b/src/RepositoryProxy.php @@ -14,11 +14,17 @@ use Doctrine\ORM\EntityRepository; use Zenstruck\Foundry\Persistence\RepositoryDecorator; -trigger_deprecation( - 'zenstruck\foundry', - '1.37.0', - \sprintf('Class "%s" is deprecated and will be removed in version 2.0. Use "%s" instead.', RepositoryProxy::class, RepositoryDecorator::class) -); +if (!class_exists(RepositoryDecorator::class, false)) { + trigger_deprecation( + 'zenstruck\foundry', + '1.37.0', + 'Class "%s" is deprecated and will be removed in version 2.0. Use "%s" instead.', + RepositoryProxy::class, + RepositoryDecorator::class + ); +} + +\class_alias(RepositoryDecorator::class, RepositoryProxy::class); if (false) { /** @@ -29,7 +35,7 @@ * * @author Kevin Bond */ - final class RepositoryProxy + final class RepositoryProxy extends RepositoryDecorator { } } diff --git a/src/Test/Factories.php b/src/Test/Factories.php index bf13ac90..191b4393 100644 --- a/src/Test/Factories.php +++ b/src/Test/Factories.php @@ -79,7 +79,7 @@ public static function _tearDownFactories(): void */ public function disablePersist(): void { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Method "%s()" is deprecated and will be removed in Foundry 2.0. Use "Zenstruck\Foundry\Persistence\disable_persisting()" instead.', __METHOD__)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Method "%s()" is deprecated and will be removed in Foundry 2.0. Use "Zenstruck\Foundry\Persistence\disable_persisting()" instead.', __METHOD__); disable_persisting(); } @@ -89,7 +89,7 @@ public function disablePersist(): void */ public function enablePersist(): void { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Method "%s()" is deprecated and will be removed in Foundry 2.0. Use "Zenstruck\Foundry\Persistence\enable_persisting()" instead.', __METHOD__)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Method "%s()" is deprecated and will be removed in Foundry 2.0. Use "Zenstruck\Foundry\Persistence\enable_persisting()" instead.', __METHOD__); enable_persisting(); } diff --git a/src/Test/TestState.php b/src/Test/TestState.php index 2e3b94e5..2a9942f4 100644 --- a/src/Test/TestState.php +++ b/src/Test/TestState.php @@ -18,7 +18,7 @@ use Zenstruck\Foundry\ChainManagerRegistry; use Zenstruck\Foundry\Configuration; use Zenstruck\Foundry\Factory; -use Zenstruck\Foundry\Instantiator; +use \Zenstruck\Foundry\Object\Instantiator; use Zenstruck\Foundry\StoryManager; /** @@ -41,7 +41,7 @@ final class TestState */ public static function setInstantiator(callable $instantiator): void { - trigger_deprecation('zenstruck/foundry', '1.37.0', \sprintf('Method "%s()" is deprecated and will be removed in Foundry 2.0. Use "%s::configure()" instead.', __METHOD__, UnitTestConfig::class)); + trigger_deprecation('zenstruck/foundry', '1.37.0', 'Method "%s()" is deprecated and will be removed in Foundry 2.0. Use "%s::configure()" instead.', __METHOD__, UnitTestConfig::class); self::$instantiator = $instantiator; } @@ -51,7 +51,7 @@ public static function setInstantiator(callable $instantiator): void */ public static function setFaker(Faker\Generator $faker): void { - trigger_deprecation('zenstruck/foundry', '1.37.0', \sprintf('Method "%s()" is deprecated and will be removed in Foundry 2.0. Use "%s::configure()" instead.', __METHOD__, UnitTestConfig::class)); + trigger_deprecation('zenstruck/foundry', '1.37.0', 'Method "%s()" is deprecated and will be removed in Foundry 2.0. Use "%s::configure()" instead.', __METHOD__, UnitTestConfig::class); self::$faker = $faker; } @@ -61,7 +61,7 @@ public static function setFaker(Faker\Generator $faker): void */ public static function enableDefaultProxyAutoRefresh(): void { - trigger_deprecation('zenstruck/foundry', '1.37.0', \sprintf('Method "%s()" is deprecated and will be removed in Foundry 2.0 without replacement.', __METHOD__)); + trigger_deprecation('zenstruck/foundry', '1.37.0', 'Method "%s()" is deprecated and will be removed in Foundry 2.0 without replacement.', __METHOD__); self::$defaultProxyAutoRefresh = true; } @@ -71,7 +71,7 @@ public static function enableDefaultProxyAutoRefresh(): void */ public static function disableDefaultProxyAutoRefresh(): void { - trigger_deprecation('zenstruck/foundry', '1.37.0', \sprintf('Method "%s()" is deprecated and will be removed in Foundry 2.0 without replacement.', __METHOD__)); + trigger_deprecation('zenstruck/foundry', '1.37.0', 'Method "%s()" is deprecated and will be removed in Foundry 2.0 without replacement.', __METHOD__); self::$defaultProxyAutoRefresh = false; } @@ -81,7 +81,7 @@ public static function disableDefaultProxyAutoRefresh(): void */ public static function alwaysAutoRefreshProxies(): void { - trigger_deprecation('zenstruck/foundry', '1.37.0', \sprintf('Method "%s()" is deprecated and will be removed in Foundry 2.0 without replacement.', __METHOD__)); + trigger_deprecation('zenstruck/foundry', '1.37.0', 'Method "%s()" is deprecated and will be removed in Foundry 2.0 without replacement.', __METHOD__); self::enableDefaultProxyAutoRefresh(); } @@ -150,7 +150,7 @@ public static function shutdownFoundry(): void */ public static function bootFactory(Configuration $configuration): Configuration { - trigger_deprecation('zenstruck/foundry', '1.37.0', \sprintf('Method "%s()" is deprecated and will be removed in Foundry 2.0 without replacement.', __METHOD__)); + trigger_deprecation('zenstruck/foundry', '1.37.0', 'Method "%s()" is deprecated and will be removed in Foundry 2.0 without replacement.', __METHOD__); self::bootFoundry($configuration); @@ -225,7 +225,7 @@ public static function flushGlobalState(?GlobalStateRegistry $globalStateRegistr */ public static function configure(?Instantiator $instantiator = null, ?Faker\Generator $faker = null): void { - trigger_deprecation('zenstruck/foundry', '1.37.0', \sprintf('Method "%s()" is deprecated and will be removed in Foundry 2.0. Use "%s::configure()" instead.', __METHOD__, UnitTestConfig::class)); + trigger_deprecation('zenstruck/foundry', '1.37.0', 'Method "%s()" is deprecated and will be removed in Foundry 2.0. Use "%s::configure()" instead.', __METHOD__, UnitTestConfig::class); self::$instantiator = $instantiator; self::$faker = $faker; diff --git a/src/Test/UnitTestConfig.php b/src/Test/UnitTestConfig.php index 70d0a0f8..43dc8d62 100644 --- a/src/Test/UnitTestConfig.php +++ b/src/Test/UnitTestConfig.php @@ -14,7 +14,7 @@ namespace Zenstruck\Foundry\Test; use Faker; -use Zenstruck\Foundry\Instantiator; +use \Zenstruck\Foundry\Object\Instantiator; final class UnitTestConfig { diff --git a/src/deprecations.php b/src/deprecations.php deleted file mode 100644 index a37e20a3..00000000 --- a/src/deprecations.php +++ /dev/null @@ -1,16 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -use Zenstruck\Foundry\Persistence\RepositoryDecorator; -use Zenstruck\Foundry\RepositoryProxy; - -\class_alias(\Zenstruck\Foundry\Persistence\RepositoryAssertions::class, \Zenstruck\Foundry\RepositoryAssertions::class); -\class_alias(RepositoryDecorator::class, RepositoryProxy::class); diff --git a/src/functions.php b/src/functions.php index 9499dba5..990d433a 100644 --- a/src/functions.php +++ b/src/functions.php @@ -49,7 +49,7 @@ function factory(string $class, array|callable $defaultAttributes = []): Anonymo */ function anonymous(string $class, array|callable $defaultAttributes = []): Factory { - trigger_deprecation('zenstruck\foundry', '1.37', \sprintf('Usage of "%s()" function is deprecated and will be removed in 2.0. Use the "Zenstruck\Foundry\Persistence\proxy_factory()" function instead.', __FUNCTION__)); + trigger_deprecation('zenstruck\foundry', '1.37', 'Usage of "%s()" function is deprecated and will be removed in 2.0. Use the "Zenstruck\Foundry\Persistence\proxy_factory()" function instead.', __FUNCTION__); return proxy_factory($class, $defaultAttributes); } @@ -67,7 +67,7 @@ function anonymous(string $class, array|callable $defaultAttributes = []): Facto */ function create(string $class, array|callable $attributes = []): Proxy { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Function "%s()" is deprecated and will be removed in Foundry 2.0. Use "Zenstruck\Foundry\Persistence\persist_proxy()" instead.', __FUNCTION__)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Function "%s()" is deprecated and will be removed in Foundry 2.0. Use "Zenstruck\Foundry\Persistence\persist_proxy()" instead.', __FUNCTION__); return persist_proxy($class, $attributes); } @@ -85,7 +85,7 @@ function create(string $class, array|callable $attributes = []): Proxy */ function create_many(int $number, string $class, array|callable $attributes = []): array { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Function "%s()" is deprecated and will be removed in Foundry 2.0 without replacement.', __FUNCTION__)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Function "%s()" is deprecated and will be removed in Foundry 2.0 without replacement.', __FUNCTION__); return proxy_factory($class)->many($number)->create($attributes); } @@ -103,7 +103,7 @@ function create_many(int $number, string $class, array|callable $attributes = [] */ function instantiate(string $class, array|callable $attributes = []): Proxy { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Function "%s()" is deprecated and will be removed in Foundry 2.0. Use "%s::object()" instead.', __FUNCTION__, __NAMESPACE__)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Function "%s()" is deprecated and will be removed in Foundry 2.0. Use "%s::object()" instead.', __FUNCTION__, __NAMESPACE__); return new ProxyObject(object($class, $attributes)); } @@ -134,7 +134,7 @@ function object(string $class, array|callable $attributes = []): object */ function instantiate_many(int $number, string $class, array|callable $attributes = []): array { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Function "%s()" is deprecated and will be removed in Foundry 2.0 without replacement.', __FUNCTION__)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Function "%s()" is deprecated and will be removed in Foundry 2.0 without replacement.', __FUNCTION__); return proxy_factory($class)->withoutPersisting()->many($number)->create($attributes); } @@ -152,10 +152,10 @@ function instantiate_many(int $number, string $class, array|callable $attributes */ function repository(object|string $objectOrClass): RepositoryDecorator { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Function "%s()" is deprecated and will be removed in Foundry 2.0. Use "Zenstruck\Foundry\Persistence\repository()" instead.', __FUNCTION__)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Function "%s()" is deprecated and will be removed in Foundry 2.0. Use "Zenstruck\Foundry\Persistence\repository()" instead.', __FUNCTION__); if (\is_object($objectOrClass)) { - trigger_deprecation('zenstruck\foundry', '1.37.0', \sprintf('Passing objects to "%s()" is deprecated and will be removed in Foundry 2.0. Pass directly class-string instead.', __FUNCTION__)); + trigger_deprecation('zenstruck\foundry', '1.37.0', 'Passing objects to "%s()" is deprecated and will be removed in Foundry 2.0. Pass directly class-string instead.', __FUNCTION__); $objectOrClass = $objectOrClass::class; } diff --git a/tests/Fixtures/Factories/CategoryFactory.php b/tests/Fixtures/Factories/CategoryFactory.php index c2aebdb6..77a06d1d 100644 --- a/tests/Fixtures/Factories/CategoryFactory.php +++ b/tests/Fixtures/Factories/CategoryFactory.php @@ -11,7 +11,7 @@ namespace Zenstruck\Foundry\Tests\Fixtures\Factories; -use Zenstruck\Foundry\Instantiator; +use Zenstruck\Foundry\Object\Instantiator; use Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory; use Zenstruck\Foundry\Tests\Fixtures\Entity\Category; diff --git a/tests/Fixtures/Factories/PostFactory.php b/tests/Fixtures/Factories/PostFactory.php index 9f281b4b..38dc859c 100644 --- a/tests/Fixtures/Factories/PostFactory.php +++ b/tests/Fixtures/Factories/PostFactory.php @@ -11,7 +11,7 @@ namespace Zenstruck\Foundry\Tests\Fixtures\Factories; -use Zenstruck\Foundry\Instantiator; +use Zenstruck\Foundry\Object\Instantiator; use Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory; use Zenstruck\Foundry\Tests\Fixtures\Entity\Post; diff --git a/tests/Functional/FactoryDoctrineCascadeTest.php b/tests/Functional/FactoryDoctrineCascadeTest.php index 551290e4..71243052 100644 --- a/tests/Functional/FactoryDoctrineCascadeTest.php +++ b/tests/Functional/FactoryDoctrineCascadeTest.php @@ -12,7 +12,7 @@ namespace Zenstruck\Foundry\Tests\Functional; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; -use Zenstruck\Foundry\Instantiator; +use Zenstruck\Foundry\Object\Instantiator; use Zenstruck\Foundry\Test\Factories; use Zenstruck\Foundry\Test\ResetDatabase; use Zenstruck\Foundry\Tests\Fixtures\Entity\Cascade\Brand; diff --git a/tests/Functional/RepositoryDecoratorTest.php b/tests/Functional/RepositoryDecoratorTest.php index 6a65b3e8..c15bfb6e 100644 --- a/tests/Functional/RepositoryDecoratorTest.php +++ b/tests/Functional/RepositoryDecoratorTest.php @@ -348,11 +348,11 @@ public function can_use_get_count(): void */ public function can_use_new_class_as_legacy_one(): void { - self::assertTrue($this->categoryFactoryClass()::repository() instanceof \Zenstruck\Foundry\RepositoryProxy); self::assertTrue($this->categoryFactoryClass()::repository() instanceof \Zenstruck\Foundry\Persistence\RepositoryDecorator); + self::assertTrue($this->categoryFactoryClass()::repository() instanceof \Zenstruck\Foundry\RepositoryProxy); - self::assertTrue($this->categoryFactoryClass()::assert() instanceof \Zenstruck\Foundry\RepositoryAssertions); self::assertTrue($this->categoryFactoryClass()::assert() instanceof \Zenstruck\Foundry\Persistence\RepositoryAssertions); + self::assertTrue($this->categoryFactoryClass()::assert() instanceof \Zenstruck\Foundry\RepositoryAssertions); } abstract protected function categoryClass(): string; diff --git a/tests/Unit/Bundle/DependencyInjection/ZenstruckFoundryExtensionTest.php b/tests/Unit/Bundle/DependencyInjection/ZenstruckFoundryExtensionTest.php index 95708d16..7780f794 100644 --- a/tests/Unit/Bundle/DependencyInjection/ZenstruckFoundryExtensionTest.php +++ b/tests/Unit/Bundle/DependencyInjection/ZenstruckFoundryExtensionTest.php @@ -18,7 +18,7 @@ use Symfony\Bundle\MakerBundle\MakerBundle; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Zenstruck\Foundry\Bundle\DependencyInjection\ZenstruckFoundryExtension; -use Zenstruck\Foundry\Instantiator; +use Zenstruck\Foundry\Object\Instantiator; /** * @author Kevin Bond diff --git a/tests/Unit/InstantiatorTest.php b/tests/Unit/InstantiatorTest.php index 9b4777ba..289f6964 100644 --- a/tests/Unit/InstantiatorTest.php +++ b/tests/Unit/InstantiatorTest.php @@ -13,7 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; -use Zenstruck\Foundry\Instantiator; +use Zenstruck\Foundry\Object\Instantiator; /** * @author Kevin Bond