Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 4 additions & 30 deletions docs/traits/factory.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,51 +7,25 @@ This is required for [Using Dependency Injection With Active Record](../using-di

The following method is provided by the `FactoryTrait`:

- `withFactory()` clones the current instance of Active Record then sets the factory for the new instance and returns
the new instance.
- `instantiate()` creates a new instance of the model with initialized dependencies using [yiisoft/factory](https://github.com/yiisoft/factory).

## Usage

```php
use Yiisoft\ActiveRecord\ActiveRecord;
use Yiisoft\ActiveRecord\ActiveRecordFactory;
use Yiisoft\ActiveRecord\Trait\FactoryTrait;
use Yiisoft\Factory\Factory;

final class User extends ActiveRecord
{
use FactoryTrait;

public function __construct(Factory $factory, private MyService $myService)
{
$this->factory = $factory;
}
public function __construct(private MyService $myService) {}
}

$user = $factory->create(User::class); // returns a new User instance with an initialized `Factory` and `MyService` instances.
$user = User::instantiate(); // returns a new User instance with an initialized `MyService` instance.
```

If the `$factory` property is initialized, then the defined relations will be created using this factory.

## Limitations

When using `FactoryTrait`, you should not use the static `ActiveRecord::query()` method. It will not work correctly.
Instead, create a new instance of the model using the factory and create a new query object by calling the
`createQuery()` method on the model instance.

```php
$user = $factory->create(User::class);
/** @var Yiisoft\ActiveRecord\ActiveQueryInterface $query */
$query = $user->createQuery();
```

Then you can use the active query object as usual, for example:

```php
$users = $query->where(['is_active' => true])->all();
```

Also, you cannot use `RepositoryTrait` with `FactoryTrait`, because it uses static `ActiveRecord::query()` method.

## See also

- [Using Dependency Injection With Active Record](../using-di.md);
Expand Down
71 changes: 40 additions & 31 deletions docs/using-di.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,60 @@
# Using Dependency Injection With Active Record

Using [dependency injection](https://github.com/yiisoft/di) in the Active Record model allows injecting dependencies
into the model and use them in the model methods.
into the model and using them in the model methods.

To create an Active Record model with dependency injection, you need to use
a [factory](https://github.com/yiisoft/factory) that will create an instance of the model and inject the dependencies
into it.

## Define The Active Record Model
## Define the Factory for Active Record

Yii Active Record provides a [FactoryTrait](traits/factory.md) trait that allows to use the factory with the Active Record class.
To use dependency injection with Active Record, you need to define the factory in `ActiveRecordFactory` class using one
of the following ways:

```php
use Yiisoft\ActiveRecord\ActiveQueryInterface;
use Yiisoft\ActiveRecord\ActiveRecord;
use Yiisoft\ActiveRecord\Trait\FactoryTrait;
### Using the bootstrap configuration

#[\AllowDynamicProperties]
final class User extends ActiveRecord
{
use FactoryTrait;

public function __construct(private MyService $myService)
{
Add the following code to the configuration file, for example, in `config/common/bootstrap.php`:

```php
use Psr\Container\ContainerInterface;
use Yiisoft\ActiveRecord\ActiveRecordFactory;
use Yiisoft\Factory\Factory;

return [
static function (ContainerInterface $container): void {
ActiveRecordFactory::set(new Factory($container));
// or `ActiveRecordFactory::set($container->get(Factory::class))` if the factory is defined in the container.
}
}
];
```

When you use dependency injection in the Active Record model, you need to create the Active Record instance using
the factory.
> [!NOTE]
> The factory can be represented as `Factory` or `StrictFactory` class instance.

```php
/** @var \Yiisoft\Factory\Factory $factory */
$user = $factory->create(User::class);
```
### Using DI container autowiring

To create `ActiveQuery` instance you also need to use the factory to create the Active Record model.
You can set the factory for Active Record using the DI container autowiring.

```php
$userQuery = new ActiveQuery($factory->create(User::class)->withFactory($factory));
use Psr\Http\Message\ResponseInterface;
use Yiisoft\ActiveRecord\ActiveRecordFactory;
use Yiisoft\Factory\Factory;

final class SomeController
{
public function someAction(Factory $factory): ResponseInterface
{
ActiveRecordFactory::set($factory);

// ...
}
}
```

## Factory Parameter In The Constructor
## Define The Active Record Model

Optionally, you can define the factory parameter in the constructor of the Active Record class.
Yii Active Record provides a [FactoryTrait](traits/factory.md) trait that allows using the factory with the Active Record class.

```php
use Yiisoft\ActiveRecord\ActiveQueryInterface;
Expand All @@ -55,17 +66,15 @@ final class User extends ActiveRecord
{
use FactoryTrait;

public function __construct(Factory $factory, private MyService $myService)
{
$this->factory = $factory;
}
public function __construct(private MyService $myService) {}
}
```

This will allow creating the `ActiveQuery` instance without calling `ActiveRecord::withFactory()` method.
Now you can create the Active Record instance or `ActiveQuery` instance using the factory.

```php
$userQuery = new ActiveQuery($factory->create(User::class));
$user = User::instantiate();
$userQuery = User::query();
```

Back to [Create Active Record Model](create-model.md)
75 changes: 75 additions & 0 deletions src/ActiveRecordFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

declare(strict_types=1);

namespace Yiisoft\ActiveRecord;

use InvalidArgumentException;
use Yiisoft\Factory\Factory;
use Yiisoft\Factory\StrictFactory;

/**
* ActiveRecordFactory is a factory class for creating active record instances.
* It is used by {@see FactoryTrait} trait.
*/
final class ActiveRecordFactory
{
private const DEFAULT = '';

/** @var (Factory|StrictFactory)[] $factories */
private static array $factories = [];

/**
* Clear all registered factories.
*/
public static function clear(): void
{
self::$factories = [];
}

/**
* Creates an active record instance.
*
* @param class-string<ActiveRecordInterface> $className The class name of the active record to be created.
*/
public static function create(string $className): ActiveRecordInterface
{
/** @var ActiveRecordInterface */
return self::get($className)->create($className);
}

/**
* Checks if a factory for the given class name exists.
*
* @param ?class-string<ActiveRecordInterface> $className The class name of the active record to be checked
* or `null` to check default factory.
*/
public static function has(?string $className = null): bool
{
return isset(self::$factories[$className ?? self::DEFAULT]);
}

/**
* Sets a factory by name.
*
* @param Factory|StrictFactory $factory The factory to be set.
* @param ?class-string<ActiveRecordInterface> $className The class name of the active record to be set
* or `null` to set default factory.
*/
public static function set(Factory|StrictFactory $factory, ?string $className = null): void
{
self::$factories[$className ?? self::DEFAULT] = $factory;
}

/**
* Returns the factory for the given class name or the default factory if none is found.
*
* @param class-string<ActiveRecordInterface> $className The class name of the active record to be checked.
*/
private static function get(string $className): Factory|StrictFactory
{
return self::$factories[$className]
?? self::$factories[self::DEFAULT]
?? throw new InvalidArgumentException("Factory for class '$className' not found");
}
}
39 changes: 7 additions & 32 deletions src/Trait/FactoryTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,21 @@

namespace Yiisoft\ActiveRecord\Trait;

use Yiisoft\ActiveRecord\ActiveQueryInterface;
use Yiisoft\ActiveRecord\ActiveRecordInterface;
use Yiisoft\Factory\Factory;

use function is_string;
use function method_exists;
use Yiisoft\ActiveRecord\ActiveRecordFactory;

/**
* Trait to add factory support to ActiveRecord.
* Trait to add factory support to an active record model by using {@see ActiveRecordFactory} class.
*
* @see AbstractActiveRecord::createQuery()
* @see AbstractActiveRecord::instantiate()
*/
trait FactoryTrait
{
private Factory $factory;

/**
* Set the factory to use for creating new instances.
* Creates a new instance of the active record class.
*/
public function withFactory(Factory $factory): static
{
$new = clone $this;
$new->factory = $factory;
return $new;
}

public function createQuery(ActiveRecordInterface|string|null $modelClass = null): ActiveQueryInterface
public static function instantiate(): static
{
if (!isset($this->factory)) {
return parent::createQuery($modelClass);
}

$model = is_string($modelClass)
? $this->factory->create($modelClass)
: $modelClass ?? $this;

if (method_exists($model, 'withFactory')) {
return parent::createQuery($model->withFactory($this->factory));
}

return parent::createQuery($model);
/** @var static */
return ActiveRecordFactory::create(static::class);
}
}
Loading