To work with the Cycle Active Record, you need to extend the entity class with the ActiveRecord
.
Note Below are examples of defining entities using attributes (see Annotated Entities), but you are still free to define entities in any other way.
Strict Approach:
In the strict approach, you define your entity with private properties and provide public getter and setter methods to access and modify the properties.
This approach encapsulates the entity's internal state and provides better control over how the properties are accessed and modified.
<?php
declare(strict_types=1);
namespace App\Entities;
use Cycle\ActiveRecord\ActiveRecord;
use Cycle\Annotated\Annotation\Column;
use Cycle\Annotated\Annotation\Entity;
use Cycle\ORM\Entity\Behavior\CreatedAt;
#[CreatedAt(field: 'createdAt')]
#[Entity(table: 'users')]
class User extends ActiveRecord
{
#[Column(type: 'bigInteger', primary: true, typecast: 'int')]
private int $id;
#[Column(type: 'string')]
private string $name;
#[Column(type: 'string', unique: true)]
private string $email;
private \DatetimeImmutable $createdAt;
public function __construct(string $name, string $email)
{
$this->name = $name;
$this->email = $email;
}
public static function create(string $name, string $email): static
{
return static::make([
'name' => $name,
'email' => $email,
]);
}
public function id(): int
{
return $this->id;
}
public function name(): string
{
return $this->name;
}
public function changeName(string $name): void
{
$this->name = $name;
}
public function email(): string
{
return $this->email;
}
public function changeEmail(string $email): void
{
$this->email = $email;
}
}
Once the entities are described, we can start working with them.
Using constructors is not recommended, as it can lead to undesirable effects, but it's still possible to use them. Look at the Extended Defining Entities section for more information.
make()
MethodThe make()
method is a static factory method that accepts an array of properties and returns a new instance of the entity.
$user = User::make([
'name' => $name,
'email' => $email,
]);
To save the entity to the database, you can use the save()
method:
The save()
method returns a result object that contains information about the operation.
$result = $user->save();
// Check if the operation was successful
$result->isSuccess();
If you prefer to have an exception thrown when the operation fails, you can use the saveOrFail()
method:
$user->saveOrFail();
Entities that extends ActiveRecord
class includes powerful Cycle-ORM Query Builder
allowing you to fluently query the database table associated with the Entity.
The process of querying data usually takes the following three steps:
ActiveRecord::query()
method;The ActiveRecord::query()
method returns a new instance of the ActiveQuery
class,
which is a wrapper around the query builder provided by Cycle ORM.
It means that you can use all the methods provided by the query builder and additionally some methods provided by the related ActiveQuery
class.
// Query all the dog people
$dogsGuys = User::query()
->where('likes_dogs', true)
->fetchAll();
// Query 10 users who have a dog named Rex
$rexOwners = User::query()
->load('pet', ['where' => ['type' => 'dog', 'name' => 'Rex']])
->limit(10)
->fetchAll();
// Query user by id
$user = User::findByPK($id);
To delete a single record, first retrieve the Active Record entity and then call the delete()
or deleteOrFail()
method.
User::findByPK($id)->delete();
Post::findByPK($id)->deleteOrFail();
Each call to the save()
, delete()
, deleteOrFail()
, or saveOrFail()
method
runs at least one transaction to the database.
However, you may need to save multiple distinct entities in a single transaction.
To achieve this, you can use two methods: ::transact()
and ::groupActions()
.
The ::transact()
method will immediately open a transaction and complete it along with the callback.
The transaction will be rolled back if an exception is thrown from the function; otherwise, it will be committed.
Note that this does not currently affect the execution of ActiveRecord write operations. Single saves or deletions of entities will still be executed in their own transactions. This behavior may change in the future.
ActiveRecord::transact(
function () use ($user, $account, $post) {
$dbal->query('DELETE FROM users');
// Will be executed in a nested transaction
$user->save();
// Will be executed in a nested transaction
$account->save();
// Will be executed in a nested transaction
$post->delete();
}
);
The ::groupActions()
method differs from ::transact()
in that:
::groupActions()
was not called.In other words, all operations on entities within the function are placed into a single Unit of Work of the EntityManager and executed upon exiting the function.
You can get the EntityManager as the first parameter of the passed function to perform actions with other ORM entities that are not converted to ActiveRecord.
To configure the transaction behavior of the Entity Manager, you can use the second parameter of the transact()
method:
ActiveRecord::groupActions(
function (EntityManagerInterface $em) use ($users, $user, $account, $post) {
array_walk($users, fn ($user) => $user->save());
$user->save();
$post->delete();
$em->persist($account);
},
TransactionMode::Ignore,
);
The TransactionMode
enum provides the following options:
TransactionMode::OpenNew
(default) starts a new transaction when the Entity Manager is running after the callback.TransactionMode::Current
uses the current transaction and throws an exception if there is no active transaction.TransactionMode::Ignore
does nothing about transactions. If there is an active transaction,
it won't be committed or rolled back.You can nest the call to ::groupActions()
inside the call to ::transact()
to avoid creating unnecessary nested transactions.
However, nesting ::groupActions()
inside another ::groupActions()
is not currently allowed.
User::transact(function (DatabaseInterface $dbal) use ($user, $account, $post): void {
$dbal->query('DELETE FROM users');
User::groupActions(function () use ($user, $account, $post) {
$user->save();
$account->save();
$post->delete();
}, TransactionMode::Current);
});