-
Notifications
You must be signed in to change notification settings - Fork 0
Repositories
This page shows how to perform CRUD operations using repositories and models.
See Instantiation for instantiate prime service classes.
<?php
use Bdf\Prime\ConnectionManager;
use Bdf\Prime\Locatorizable;
use Bdf\Prime\ServiceLocator;
$connections = new ConnectionManager();
$connections->declareConnection('myDB', '...');
$prime = new ServiceLocator($connections);
$prime->setDI($kernel->getContainer());
$prime->setSerializer($kernel->getContainer()->get('serializer'));
// Enable active record system
Locatorizable::configure($prime);Now you can use ServiceLocator::repository() to access to EntityRepository instance.
You can also use Model::repository() is active record is enabled.
<?php
// Get repository for User entities
$userRepository = $prime->repository(User::class);
$userRepository = User::repository();The repository provide some accessor for entity metadata and helpers to handle entity objects.
<?php
$repository = $prime->repository(User::class);
//-------------------
// Metadata accessors
//-------------------
// Get the mapper instance
// The mapper contains all entity configurations, and also utilities methods for hydrate or extract values on entities
$repository->mapper();
// Get entity metadata
// Metadata are used as cache, and store all mapping configurations
// Be aware when use metadata, this object is internal, and all its fields are public
// So it'll easy to break prime.
// Prefer use `Mapper::info()` instead which provides a more convenient and safe way to resolve entity properties.
$repository->metadata();
// Get entity name
// This name is almost always the entity class name, but it's not guaranteed.
$repository->entityName();
// Get actual entity class
// If `EntityRepository::entityName()` is not a valid class, `stdClass` will be returned.
$repository->entityName();
// Check if the mapper is marked as read only
$repository->isReadOnly();
//----------------
// Helpers methods
//----------------
// Create a `Criteria` instance with given filters
$criteria = $repository->criteria(['foo' => 'bar']);
$repository->where($criteria->all())->first();
// Create entity (without saving it)
// This is useful if there is no accessible constructor.
// Prefer use of constructor directly.
$user = $repository->entity(['login' => 'foo', 'password' => 'bar']);
$user->login(); // 'foo'
// Get related EntityCollection instance.
// This object wrap an array of entities and repository instance to allow optimised write or loading/search operations
$collection = $repository->collection([$userFoo, $userBar, ...]);
$collection->update(['password' => null]);
$collection->refresh();
$collection->link('groups')->walk();
// Set a property value
$repository->hydrateOne($user, 'pseudo', 'Johnny');
$user->pseudo(); // 'Johnny'
// Get a property value
$repository->extractOne($user, 'pseudo'); // 'Johnny'| Method name | Description | Usage |
|---|---|---|
insert |
Perform insert operation. If the entity already exists no write will be performed, and an exception will be thrown unless $ignore parameter is set to true. |
$repository->insert($user); |
insertIgnore |
Perform insert operation but ignore error on fail. Same as passing true on second parameter of insert. |
$repository->insertIgnore($user); |
update |
Perform update of the entity. If the entity do not exists, no write will be performed, and will return 0. Fields to update can be passed as second parameter. You can use dot . as separator for update embedded fields. If $attributes parameter is ignored, all fields will be updated. |
$repository->update($user, ['login', 'password']); |
delete |
Delete an entity. If the entity do not exists, no write will be performed, and will return 0. | $repository->delete($user); |
save |
Insert or update entity. This method will first check if the primary key is set to select operation to perform. | $repository->save($user); |
replace |
Insert or update entity. Unlike save, saving and saved events will not be triggered, also exists will be called to ensure that the entity actually exists. Note: this method will not use REPLACE operation of MySQL. |
$repository->replace($user); |
duplicate |
Duplicate an entity by regenerate its primary key and inserting it. | $repository->duplicate($user); |
updateBy |
Perform a low level update operation. Entities will not be loaded before applying update, so all events will be skipped. | $repository->updateBy(['password' => null], ['createdAt >=' => new DateTime('2022-10-05')]); |
deleteBy |
Perform a low level delete operation. Entities will not be loaded before applying update, so all events will be skipped. | $repository->deleteBy(['createdAt >=' => new DateTime('2022-10-05')]); |
saveAll |
Perform a recursive save operation on current entity and also its attached relations. See Loading relations for format of $relations parameter. |
$repository->saveAll($user, ['credentials']); |
deleteAll |
Perform a recursive delete operation on current entity and also its attached relations. See Loading relations for format of $relations parameter. |
$repository->deleteAll($user, ['credentials', 'options']); |
EntityRepository forward all calls to Query instance, created using EntityRepository::builder().
So all query methods like where, with, all... can be directly called from repository instance.
Repository also provide some helpers methods :
| Method name | Description | Usage |
|---|---|---|
count |
Count number of entities which match with given criteria. | $repository->count(['roles' => (new Like(3))->searchableArray()]); |
exists |
Check if the given entity exists on database. | $repository->exists($user); |
refresh |
Find entity from database with same primary key as entity passed as parameter. | $repository->refresh($user); |
loadRelations |
Load attached relations. If a relation is already loaded it will be ignored. See Loading relations for format of $relations parameter. |
$repository->loadRelations($user, ['credentials']); |
reloadRelations |
Load attached relations even if it's already loaded. See Loading relations for format of $relations parameter. |
$repository->reloadRelations($user, ['credentials']); |
$repository = $prime->repository(User::class);
// Use directly Query method
foreach ($repository->where(['pseudo' => (new Like('j'))->startsWith()])->walk() as $user) {
// ...
}
// Check if the user in database if different from current user instance
$hasChanged = $repository->refresh($user) == $user;
// Perform count query
if ($repository->count(['pseudo' => $pseudo])) {
throw new FormError('Pseudo already in use');
}See command prime:upgrade for launch upgrades from CLI.
Repository allows to perform schema upgrade automatically, by computing diff of declared structure on mapper with actual table structure.
To get the upgrader utility instance, use EntityRepository::schema(). You can pass true as parameter to force creation of upgrader
even if schema manager is disabled on mapper.
An instance of StructureUpgraderInterface will be returned, which provides methods :
-
migrate: migrate actual table structure to declared one. If$listDropis set to true,DROP COLUMNqueries will be executed. -
diff: Perform a migration simulation. List only queries which will be executed bymigrate. -
truncate: Empties the table -
drop: Completely remove table and its sequence table
To see queries usage, read Queries.
To create custom queries, or call optimised queries, you need to use RepositoryQueryFactory.
You can access to this object by calling RepositoryInterface::queries(), or directly call the method from repository instance, which forward method calls to this object.
To create a configured (i.e. with configured table name and preprocessor) custom query, you can call RepositoryQueryFactory::make() with query class name.
The Query instance can be retrieved by calling RepositoryQueryFactory::builder().
Some additional utility methods are also available :
-
RepositoryQueryFactory::keyValue(): Create aKeyValueQueryif supported by the repository (i.e. has no constraints). A filter can be set directly on methods parameters, which will callKeyValueQueryInterface::where(). -
RepositoryQueryFactory::countKeyValue(): Works likeRepositoryQueryFactory::keyValue(), but execute directlyKeyValueQueryInterface::count(), and save query instance. So compiled query can be reused. -
RepositoryQueryFactory::findById(): Find an entity by its primary key. In case of composite key, an array should be passed with all key components. The internal executed query instance is saved, so compiled query will be reused. This method is the recommended way for get an entity by its PK. -
RepositoryQueryFactory::entities()Refresh a list of entities from database.
$repository = $prime->repository(User::class);
// Directly call optimised PK query from repository instance
$user = $repository->findById(42);
// Save as above, but access to RepositoryQueryFactory object before
$user = $repository->queries()->findById(42);
// Count users with a login equals to 'robert'
$repository->queries()->countKeyValue('login', 'robert');
// Perform a simple key-value filter query
$repository->queries()->keyValue('login', 'jean')->first();
// Same as above but with array filter
$repository->queries()->keyValue(['login' => 'jean'])->first();
// Create a custom query
$query = $repository->queries()->make(BulkInsertQuery::class)->columns(['login', 'password', 'name', 'roles'])->bulk();
foreach ($rows as $row) {
$query->values($row);
}
$query->execute();When you only need to handle write operation, you can use WriterInterface object.
This object can be accessed using RepositoryInterface::writer(). It provides a method for each write operation : insert, update and delete.
The writer can be wrapped into a BufferedWriter which act as unit of works for a single repository.
So it stack all writes in memory, before applying all by calling BufferedWriterInterface::flush().
Note:
BufferedWriterwill not use internally transactions, so to ensure that write operations are atomic, don't forget to callEntityRepository::transaction()
$repository = $prime->repository(User::class);
// Start a transaction to ensure that write operations will be atomic
$repository->transaction(function (EntityRepository $repository) use($toUpdate) {
// Create the buffered writer
$writer = new BufferedWriter($repository);
// Stack operations
$writer->insert(new User(...));
$writer->insert(new User(...));
$writer->delete(User::findById(41));
$writer->update($toUpdate);
// Perform write
$writer->flush();
});You can temporarily change the connection of the repository. Once changed all read and write operations will be performed on this new connection.
$repository = $prime->repository(User::class);
$user = new User(...);
$repository->on('tmp_connection', function (EntityRepository $repository) use($user) {
$repository->insert($user); // Save user into tmp_connection
});
$repository->exists($user); // false : connection is reset, so $user do not exist hereTo ensure atomicity of multiple write operations on a repository, you should use transaction. When any exception is raised during execution of write operations, or if false is returned, all operations will be cancelled, and database return to a clean state.
Note: transaction is enabled globally on active connection, so any write on any repositories on same connection will be on same transaction, but repositories on other connections are not handled by transactions, which can cause corruption when transaction is cancelled.
$repository = $prime->repository(User::class);
$user = new User(...);
$repository->transaction(function (EntityRepository $repository) use($user) {
$repository->insert($user); // Save user into tmp_connection
return false; // Cancel transaction
});
$repository->exists($user); // falseBehaviors and constraints can be disabled during runtime. Two distinct functionality can be disabled :
- Constraints, by calling
EntityRepository::withoutConstraints(), which will disable all constraint on the next query. It also ignoresSoftDeletablebehavior. Once the next query is created, constraints will be re-enabled. You can check disable status usingEntityRepository::isWithoutConstraints() - Event listeners, by calling
EntityRepository::disableEventNotifier(), which will disable all registered events (so disable all behaviors). It can be re-enabled by callEntityRepository::enableEventNotifier().
$repository = $prime->repository(User::class);
$user = $repository->findById(123);
$user->delete(); // user is SoftDeletable : so it's not removed
$repository->exists($user); // false : constraints are enabled
$repository->withoutConstraints()->exists($user); // true : constraints are disabled
$repository->disableEventNotifier()->delete(); // Disable SoftDeletable behavior, so user will be actually removed from database
$repository->withoutConstraints()->exists($user); // false
$repository->enableEventNotifier(); // Event notifier must be re-enabled manuallyEvent listeners can be added at runtime on EntityRepository instance.
See Registering listeners for get the list of events descriptions.
You can set a temporary listener by setter parameter $once to true, so the listener will be removed once it's called.
$repository = $prime->repository(User::class);
$indexer = new UserIndexer(...);
$repository->inserted(function (User $user) use($indexer) {
$indexer->insert($user);
}, true);
$repository->inserted(function (User $user) use($indexer) {
$indexer->update($user);
}, true);
$repository->save(new User(...));To use Model, active record must be enabled. See Entity for entity declaration using Model.
All static method call are forwarded to EntityRepository instance, which allow to call MyEntity::where(...) or MyEntity::refresh(...)
without explicitly getting repository instance.
| Method name | Description | Example |
|---|---|---|
import |
Fill entity with given data. Key of the array if the property name. Prefer use setters. | $user->import(['login' => 'my_login', 'password' => 'my_password']) |
export |
Get array version of the entity. The returned format is same as parameter of import. So you can duplicate an entity by calling $entity->import($other->export()). A list of properties to export can be passed as parameter. |
$user->export(['login', 'password']) |
save, insert, replace, duplicate, deleted
|
Forward call to corresponding method on EntityRepository passing the current entity as parameter. |
$user->save() |
load |
Load attached relations passed as parameter. Ignore relations already loaded. | $user->load(['credentials', 'groups.admin']) |
reload |
Load attached relations passed as parameter. | $user->reload(['credentials', 'groups.admin']) |
relation |
Get EntityRelation instance related to current entity and given relation. |
$user->relation('groups')->add($group) |
saveAll, deleteAll
|
Perform recursive write operation on current entity and attached relations. Forward call to corresponding method on EntityRepository. |
$user->saveAll(['credentials']) |
toArray |
Convert entity to array using serializer. The format is not the same as export. |
$user->toArray() |
fromArray (static) |
Instantiate entity and fill with given data using serializer. Use same format as toArray. |
User::fromArray($userData) |
toJson |
Convert entity to JSON object using serializer. | $user->toJson() |
fromJson (static) |
Instantiate entity and fill with given JSON object using serializer. Use same format as toJson. |
User::fromJson($request->getContent()) |
// Call query repository directly from static call
$user = User::findById(123);
$user->load('credentials');
$user->import($data);
$user->saveAll('credentials');