### Docker Compose Commands for Testing Source: https://github.com/nextras/orm/blob/main/contributing.md Commands to manage Docker containers for testing, including starting services, installing dependencies, and running tests. ```bash docker compose up # or with specific versions # PGSQL_VERSION=13 MYSQL_VERSION=5.7 docker compose up ``` ```bash docker compose exec -T php composer install ``` ```bash docker compose exec -T php composer tests ``` -------------------------------- ### Database Configuration Example Source: https://github.com/nextras/orm/blob/main/contributing.md Example INI file for configuring database connections for testing. Ensure to update credentials and uncomment sections as needed. ```ini ; databases.ini example [mysql] driver = mysqli host = "mysql" database = nextras_orm_test username = root password = root [pgsql] driver = pgsql host = "pgsql" database = nextras_orm_test username = postgres password = postgres ``` -------------------------------- ### Access Repositories with DIRepositoryFinder Source: https://github.com/nextras/orm/blob/main/docs/config-nette.md Example of accessing repositories when using DIRepositoryFinder. Repositories are retrieved using their class name from the DIC. ```php namespace MyApp; class MyService { /** @var Orm */ private $orm; public function __construct(Orm $orm) { $this->orm = $orm; } public function doSomething($postId) { $post = $this->orm->getRepository(PostsRepository::class)->getById($postId); // ... } } ``` -------------------------------- ### Register Repository Event Callback Source: https://github.com/nextras/orm/blob/main/docs/events.md Subscribe to repository events by adding a callback to the event property. This example shows how to hook into the `onBeforeInsert` event for the `books` repository. ```php $orm->books->onBeforeInsert[] = function (Book $book) { echo "Inserting into DB " . $book->title; }; ``` -------------------------------- ### Identity Map Example Source: https://github.com/nextras/orm/blob/main/docs/repository.md Demonstrates the Identity Map pattern where fetching the same entity by ID or by a unique property returns the identical object instance. ```php // in this example title property is unique $book1 = $orm->books->getById(1); $book2 = $orm->books->findBy(['title' => $book1->title])->fetch(); $book1 === $book2; // true ``` -------------------------------- ### Managing Relationship Entities Source: https://github.com/nextras/orm/blob/main/docs/relationships.md Provides examples of using methods like `add`, `remove`, `set`, and `removeAll` to manage entities within a relationship. These methods automatically update the reverse side of the relationship if loaded. ```php $author->books->add($book); $author->books->remove($book); $author->books->set([$book]); $author->books->removeAll(); $book->tags->add($tag); $book->tags->remove($tag); $book->tags->set([$tag]); $book->tags->removeAll(); ``` -------------------------------- ### Efficient Data Fetching with Nextras ORM Source: https://github.com/nextras/orm/blob/main/docs/default.md Demonstrates how Nextras ORM fetches related data efficiently in advance, avoiding N+1 query problems. This example shows fetching authors, their books, translators, and tags with only 4 queries. ```php $authors = $orm->authors->findAll(); foreach ($authors as $author) { echo $author->name; foreach ($author->books as $book) { echo $book->title; echo $book->translator->name; foreach ($book->tags as $tag) { echo $tag->name; } } } ``` -------------------------------- ### Calling Collection Functions Source: https://github.com/nextras/orm/blob/main/docs/collection-functions.md Demonstrates how to call a collection function using `findBy`. Collection functions are passed as an array, with the first element being the function identifier and subsequent elements being its arguments. This example also shows how to compose and nest collection functions, including the use of `ICollection::OR`. ```php // collection function call $collection->findBy([MyFunction::class, 'arg1', 'arg2']); // or compose & nest the calls together // ICollection::OR is also a collection function $collection->findBy( [ ICollection::OR, [MyFunction::class, 'arg1', 'arg2'], [AnotherFunction::class, 'arg3'], ] ); ``` -------------------------------- ### Define Custom JsonWrapper for Entity Property Source: https://github.com/nextras/orm/blob/main/docs/entity.md Example of creating a custom property wrapper to handle JSON serialization and deserialization for an entity property. ```php /** * ... * @property \stdClass $json {wrapper JsonWrapper} */ class Data extends Entity { } class JsonWrapper extends ImmutableValuePropertyWrapper { public function convertToRawValue($value) { return json_encode($value); } public function convertFromRawValue($value) { return json_decode($value); } } ``` -------------------------------- ### Define Composite Primary Key with {primary-proxy} Source: https://github.com/nextras/orm/blob/main/docs/entity.md Use the {primary-proxy} modifier for composite primary keys, allowing easy access to the primary key components. This example shows how it works with relationships. ```php /** * @property array{Tag, User} $id {primary-proxy} * @property Tag $tag {m:1 Tag::$followers} {primary} * @property User $follower {m:1 User::$followedTags} {primary} */ class TagFollower extends Nextras\Orm\Entity\Entity { } ``` -------------------------------- ### Assign New Author to Books Before Removing Old Author Source: https://github.com/nextras/orm/blob/main/docs/repository.md When an entity with a OneHasMany relationship has a non-nullable reverse side, you must reassign related entities before removing the parent. This example shows how to assign books to a new author. ```php $author = $orm->authors->getById(...); $newAuthor = $orm->authors->getById(...); foreach ($author->books as $book) { $book->author = $newAuthor; } $orm->remove($author); ``` -------------------------------- ### Nested Filtering with AND and OR Source: https://github.com/nextras/orm/blob/main/docs/collection-filtering.md Nest filtering structures using ICollection::OR and ICollection::AND (default) to create complex query conditions. This example finds men older than 10 and women younger than 12. ```php // find all men older than 10 years and women younger than 12 years $authors = $orm->author->findBy([ ICollection::OR, [ ICollection::AND, 'age>=' => 10, 'sex' => 'male', ], [ ICollection::AND, 'age<=' => 12, 'sex' => 'female', ], ]); ``` ```php // find all men older than 10 years and women younger than 12 years $authors = $orm->author->findBy([ ICollection::OR, [ 'age>=' => 10, 'gender' => 'male', ], [ 'age<=' => 12, 'gender' => 'female', ], ]); ``` -------------------------------- ### Accessing and Setting Entity Properties Source: https://github.com/nextras/orm/blob/main/docs/entity.md Illustrates how to interact with entity properties using both direct property access and explicit `getValue`/`setValue` methods. It also shows how to check for the existence of a property and the persistence status of an entity. ```php $member = new Member(); $member->name = 'Jon'; $member->setValue('name', 'Jon'); $member->born = 'now'; // will be automatically converted to DateTimeImmutable echo $member->name; echo $member->getValue('name'); echo isset($member->web) ? 'has web' : '-'; echo $member->hasValue('web') ? 'has web' : '-'; $member->isPersisted(); // false ``` -------------------------------- ### Configure Multiple ORM Instances with Connections Source: https://github.com/nextras/orm/blob/main/docs/config-nette.md Set up multiple instances of Nextras ORM, each potentially with its own database connection and model. This is achieved by defining separate OrmExtension configurations and linking them to specific DbalExtension connections. ```neon extensions: nextras.orm1: Nextras\Orm\Bridges\NetteDI\OrmExtension nextras.orm2: Nextras\Orm\Bridges\NetteDI\OrmExtension nextras.dbal1: Nextras\Dbal\Bridges\NetteDI\DbalExtension nextras.dbal2: Nextras\Dbal\Bridges\NetteDI\DbalExtension nextras.orm1: model: MainModel connection: @nextras.dbal1.connection nextras.orm2: model: AnotherModel connection: @nextras.dbal2.connection autowiredInternalServices: false ``` -------------------------------- ### Configure Nette DI for PhpDoc Repositories Source: https://github.com/nextras/orm/blob/main/docs/config-nette.md Register the OrmExtension and specify your custom model class in the config.neon file. This enables the automatic repository discovery based on PhpDoc annotations. ```neon extensions: nextras.orm: Nextras\Orm\Bridges\NetteDI\OrmExtension nextras.orm: model: MyApp\Model ``` -------------------------------- ### Access Repositories via Model or DIC Source: https://github.com/nextras/orm/blob/main/docs/config-nette.md Demonstrates how to retrieve and use repositories after configuring the ORM with PhpDoc annotations. Repositories can be accessed through the model's magic getters or directly from the DIC. ```php $orm = $dic->getByType(Model::class); // or auto-wire $orm->posts->findAll(); $postsRepository = $dic->getByType(PostsRepository::class); // or auto-wire $postsRepository->findAll(); ``` -------------------------------- ### Add Custom Modifiers to Metadata Parser Source: https://github.com/nextras/orm/blob/main/docs/config-nette.md Extend the ORM's parsing capabilities by adding custom modifiers. This is done by calling `addModifier` on the parser factory's setup, providing the modifier name and a callback for parsing. ```neon services: nextras.orm.metadataParserFactory: setup: - addModifier(modifier, [@myservice, parseMethod]) ``` -------------------------------- ### Configure Nette DI for DI Repository Finder Source: https://github.com/nextras/orm/blob/main/docs/config-nette.md Use DIRepositoryFinder to leverage existing IRepository instances defined in your DI container. This approach reuses services already registered in DIC and does not require a custom model class. ```neon extensions: nextras.orm: Nextras\Orm\Bridges\NetteDI\OrmExtension nextras.orm: repositoryFinder: Nextras\Orm\Bridges\NetteDI\DIRepositoryFinder services: - MyApp\PostsRepository(MyApp\PostsMapper()) ``` -------------------------------- ### Implement Entity Creation Event Source: https://github.com/nextras/orm/blob/main/docs/events.md Override the `onCreate` method within an entity to execute custom logic when a new entity is created. Ensure to call the parent implementation. ```php /** * @property int $id {primary} * @property DateTimeImmutable $createdAt */ class Book extends Nextras\Orm\Entity\Entity { public function onCreate(): void { parent::onCreate(); $this->createdAt = new DateTimeImmutable(); } } ``` -------------------------------- ### Define Repository with Custom Finders Source: https://github.com/nextras/orm/blob/main/docs/repository.md Extend the base Repository class and define `getEntityClassNames`. Implement custom finder methods like `findLatest` and `findByTags` using collection manipulation methods. ```php /** * @extends Repository */ final class BooksRepository extends Repository { static function getEntityClassNames(): array { return [Book::class]; } /** * @return ICollection */ public function findLatest() { return $this->findAll()->orderBy('id', ICollection::DESC)->limitBy(3); } /** * @return ICollection */ public function findByTags($name) { return $this->findBy(['tags->name' => $name]); } } ``` -------------------------------- ### Connecting Related Entities Source: https://github.com/nextras/orm/blob/main/docs/entity.md Shows how to manually create and connect entities, such as an 'Author' and a 'Book'. When an 'Author' entity is attached to a repository, any new connected entities like 'Book' are automatically attached as well. ```php $author = new Author(); $book = new Book(); $book->author = $author; $book->tags->set([new Tag(), new Tag()]); ``` -------------------------------- ### Render Articles with Pagination Source: https://github.com/nextras/orm/blob/main/docs/collection.md Renders articles for a given category, applying pagination. It uses countStored for total count and limitBy for fetching paginated results. ```php public function renderArticles(int $categoryId): void { $articles = $this->orm->articles->findBy(['category' => $categoryId]); $limit = 10; $offset = $this->page * 10; $this->paginator->totalCount = $articles->countStored(); $this->template->articles = $articles->limitBy($limit, $offset); } ``` -------------------------------- ### Injecting Dependencies into an Entity Source: https://github.com/nextras/orm/blob/main/docs/entity.md Demonstrates how to inject a service dependency into an entity using a dedicated injection method. Ensure the dependency is provided by your DI container. ```php /** * ... */ class Book extends Nextras\Orm\Entity\Entity { /** @var EanSevice */ private $eanService; public function injectEanService(EanService $service) { $this->eanService = $service; } } ``` -------------------------------- ### Filter Authors by Multiple Specific Books Source: https://github.com/nextras/orm/blob/main/docs/collection-filtering.md Demonstrates using AnyAggregator with grouping keys to find authors who authored specific books with distinct price conditions. This separates the join conditions for each book. ```php $authors = $orm->authors->findBy([ ICollection::AND, [ new AnyAggregator('any1'), 'books->title' => 'Book 1', 'books->price' => 50, ], [ new AnyAggregator('any2'), 'books->title' => 'Book 2', 'books->price' => 150, ], ]); ``` -------------------------------- ### Proxy SQL Queries via Repository Source: https://github.com/nextras/orm/blob/main/docs/repository.md Use the `@method` annotation in the repository to proxy SQL queries defined in the mapper. This allows calling SQL-based finders directly from the repository instance. ```php /** * @method ICollection findBooksWithEvenId() * @extends Repository */ final class BooksRepository extends Repository { // ... } ``` ```php /** * @extends DbalMapper */ final class BooksMapper extends DbalMapper { /** @return ICollection */ public function findBooksWithEvenId(): ICollection { return $this->toCollection( $this->builder()->where('id % 2 = 0') ); } } ``` -------------------------------- ### Create and Persist New Entities with Nextras ORM Source: https://github.com/nextras/orm/blob/main/docs/default.md Instantiate new entities, set their properties, and then use the ORM's persistAndFlush method to save them to the database. This method handles cascading persistence and transactions. ```php $author = new Author(); $author->name = 'Jon Snow'; $author->born = 'yesterday'; $author->email = 'snow@wall.st'; $publisher = new Publisher(); $publisher->name = '7K publisher'; $book = new Book(); $book->title = 'My Life on The Wall'; $book->author = $author; $book->publisher = $publisher; $orm->books->persistAndFlush($book); ``` -------------------------------- ### LIKE Filtering with Wildcards Source: https://github.com/nextras/orm/blob/main/docs/collection-filtering.md Perform LIKE filtering using the '~' operator. Wrap values with LikeExpression static builders like startsWith, endsWith, contains, or notContains. The 'raw' method accepts wildcard expressions but requires sanitized input. ```php // finds all users with email hosted on gmail.com $authors = $orm->authors->findBy([ 'email~' => LikeExpression::endsWith('@gmail.com'), ]); ``` -------------------------------- ### Using a Custom LikeFunction for Filtering Source: https://github.com/nextras/orm/blob/main/docs/collection-functions.md Demonstrates how to use a custom collection function, LikeFunction, to filter entities based on a string property using a LIKE operator with a prefix. ```php $users->findBy([ LikeFunction::class, 'phone', '+420' ]); ``` -------------------------------- ### Fetch All Book Entities Indexed by ID Source: https://github.com/nextras/orm/blob/main/docs/collection.md Fetches all book entities and returns them as an associative array where the keys are the book IDs and the values are the entire entity objects. Use 'id' for the key and null for the value. ```php $orm->books ->findAll() ->fetchPairs('id', null); ``` -------------------------------- ### Custom Aggregation and Comparison Wrappers Source: https://github.com/nextras/orm/blob/main/docs/collection-filtering.md Defines helper classes 'Aggregate' and 'Compare' to simplify the syntax for filtering and sorting collections using aggregation functions. This reduces verbosity for complex queries. ```php class Aggregate { public static function count(string $expression): array { return [CountAggregateFunction::class, $expression]; } } class Compare { public static function gt(string $expression, $value): array { return [ CompareGreaterThanFunction::class, $expression, $value, ]; } } // filters authors who have more than 2 books // and sorts them by the count of their books descending $authorsCollection ->findBy(Compare::gt(Aggregate::count('books->id'), 2)) ->orderBy(Aggregate::count('books->id'), ICollection::DESC); ``` -------------------------------- ### Implementing a Custom Collection Function Source: https://github.com/nextras/orm/blob/main/docs/collection-functions.md Provides a template for implementing a custom collection function by creating a class that implements the `CollectionFunction` interface. This includes the necessary methods for processing Dbal expressions (`processDbalExpression`) and array expressions (`processArrayExpression`). ```php use \Nextras\Dbal\QueryBuilder\QueryBuilder; use \Nextras\Orm\Collection\Aggregations\Aggregator; use \Nextras\Orm\Collection\Functions\CollectionFunction; use \Nextras\Orm\Collection\Functions\Result\ArrayExpressionResult; use \Nextras\Orm\Collection\Functions\Result\DbalExpressionResult; use \Nextras\Orm\Collection\Helpers\ArrayCollectionHelper; use \Nextras\Orm\Collection\Helpers\DbalQueryBuilderHelper; use \Nextras\Orm\Entity\IEntity; final class LikeFunction implements CollectionFunction { public function processDbalExpression( DbalQueryBuilderHelper $helper, QueryBuilder $builder, array $args, Aggregator|null $aggregator = null, ) : DbalExpressionResult { // TODO: Implement processDbalExpression() method. } public function processArrayExpression( ArrayCollectionHelper $helper, IEntity $entity, array $args, Aggregator|null $aggregator = null, ) : ArrayExpressionResult { // TODO: Implement processArrayExpression() method. } } ``` -------------------------------- ### Register Entities and Detect Class Name in Repository Source: https://github.com/nextras/orm/blob/main/docs/entity-sti.md Register all entities, including the abstract one first, in `getEntityClassNames()`. Override `getEntityClassName()` to determine the specific entity class based on row data. ```php /** * @extends Nextras\Orm\Repository\Repository
*/ class AddressesRepository extends Nextras\Orm\Repository\Repository { public static function getEntityClassNames(): array { return [Address::class, PrivateAddress::class, PublicAddress::class]; } public function getEntityClassName(array $data): string { return $data['type'] === Address::TYPE_PUBLIC ? PublicAddress::class : PrivateAddress::class; } } ``` -------------------------------- ### Remove Entity and Flush Changes Source: https://github.com/nextras/orm/blob/main/docs/model.md Call `remove()` to mark an entity for deletion and `flush()` to execute it. `removeAndFlush()` is a shortcut for both. ```php $user = $model->users->getById(1); $model->remove($user); $model->flush(); // or $model->removeAndFlush($user); ``` -------------------------------- ### Entity with Implicit and Explicit Date Wrappers Source: https://github.com/nextras/orm/blob/main/docs/entity.md Demonstrates entity property definitions using Nextras ORM's date handling capabilities. 'registeredAt' uses an implicit DateTimeWrapper, while 'bornOn' explicitly uses the DateWrapper for date-only handling. ```php use Nextras\Orm\Entity\PropertyWrapper\DateWrapper; /** * @property int $id {primary} * @property DateTimeImmutable $registeredAt (implicit DateTimeWrapper here) * @property DateTimeImmutable|null $bornOn {wrapper DateWrapper} */ class Member extends Nextras\Orm\Entity\Entity {} ``` -------------------------------- ### Registering Collection Functions in Repository Source: https://github.com/nextras/orm/blob/main/docs/collection-functions.md Shows how to register a custom collection function within a repository by overriding the `createCollectionFunction` method. This method should return an instance of the custom function if the requested name matches, otherwise, it should call the parent method. ```php class UsersRepository extends Nextras\Orm\Repository\Repository { public function createCollectionFunction(string $name): CollectionFunction { if ($name === MyFunction::class) { return new MyFunction(); } else { return parent::createCollectionFunction($name); } } } ``` -------------------------------- ### Provide Custom Dependency Provider for Entities Source: https://github.com/nextras/orm/blob/main/docs/config-nette.md Inject custom dependencies into your ORM entities by defining a custom dependency provider. This allows for more control over entity initialization and dependency management. ```neon services: nextras.orm.dependencyProvider: MyApp\DependencyProvider ``` -------------------------------- ### Instantiate and Assign Embeddable to Entity Source: https://github.com/nextras/orm/blob/main/docs/embeddables.md Shows how to create a new embeddable instance and assign it to an entity property. To change an embeddable's property, a new instance must be created and reassigned. ```php $user = new User(); $user->address = new Address('Main st.', 'Prague', null, 'Czechia', '10000'); echo $user->address->street; ``` -------------------------------- ### Allowing Null in Relationships Source: https://github.com/nextras/orm/blob/main/docs/relationships.md Demonstrates how to temporarily allow a null value in a relationship, typically for One-to-One relationships, by using the `set()` method with `allowNull: true` on the property. ```php $originalEan = $book->ean; $eanProperty = $book->getProperty('ean'); $eanProperty->set(new Ean(), allowNull: true); // now the original ean has $book a null, reading such property will throw; // either set a new Book to it or remove the original ean; $eanRepository->remove($originalEan); ``` -------------------------------- ### Use Repository::findByIds() Instead of findById() Source: https://github.com/nextras/orm/blob/main/docs/migrate_5.0.md The Repository::findById() method has been removed. Use Repository::findByIds() for finding multiple entities by their IDs. ```php Repository::findByIds() ``` -------------------------------- ### Define Entities with Nextras ORM Source: https://github.com/nextras/orm/blob/main/docs/default.md Define your data models using a concise and readable syntax. Properties can be typed, and relationships like one-to-many can be specified with cascade options. ```php /** * @property int $id {primary} * @property string $name * @property DateTimeImmutable|null $born {default now} * @property string $email * @property OneHasMany $books {1:m Book::$author, orderBy=[id=DESC], cascade=[persist, remove]} */ class Author extends Entity {} /** * @property int $id {primary} * @property string $title * @property Author $author {m:1 Author::$books} * @property Publisher $publisher {m:1 Publisher::$books} */ class Book extends Entity {} /** * @property int $id {primary} * @property string $name * @property OneHasMany $books {1:m Book::$publisher} */ class Publisher extends Entity {} ``` -------------------------------- ### Define Abstract and Child Entities for STI Source: https://github.com/nextras/orm/blob/main/docs/entity-sti.md Create an abstract base entity defining shared properties and concrete child entities inheriting from it. Ensure the abstract entity is registered first in the repository. ```php /** * @property int $id {primary} * @property string $type {enum self::TYPE_*} */ abstract class Address extends Nextras\Orm\Entity\Entity { const TYPE_PUBLIC = 'public'; const TYPE_PRIVATE = 'private'; } class PrivateAddress extends Address { } /** * @property Maintainer $maintainer {m:1 Maintainer::$addressees} */ class PublicAddress extends Address { } ``` -------------------------------- ### Define Model with PhpDoc Repository Annotations Source: https://github.com/nextras/orm/blob/main/docs/config-nette.md Use @property-read annotations in your model class to define repositories. The OrmExtension will automatically create DI definitions and inject a lazy loader for these repositories. ```php namespace MyApp; /** * @property-read PostsRepository $posts * @property-read UsersRepository $users * @property-read TagsRepository $tags */ class Model extends \Nextras\Orm\Model\Model { } ``` -------------------------------- ### Fetch Book Titles Sorted Descending, Naturally Indexed Source: https://github.com/nextras/orm/blob/main/docs/collection.md Fetches all book titles, sorted in descending order, and returns them as a numerically indexed list. Use null for the key and 'title' for the value. ```php $orm->books ->findAll() ->orderBy('title', ICollection::DESC) ->fetchPairs(null, 'title'); ``` -------------------------------- ### Fetch Entity by Primary Key - PHP Source: https://github.com/nextras/orm/blob/main/docs/collection.md Use `getById()` as a shortcut to retrieve an entity by its primary key. It returns the entity or null if not found. This is equivalent to using `getBy(['id' => $primaryValue]). ```php $author = $orm->author->getById(1); // Author|null // equals $author = $orm->author->getBy(['id' => 1]); ``` -------------------------------- ### Fetch Single Entity by Conditions - PHP Source: https://github.com/nextras/orm/blob/main/docs/collection.md Use `getBy()` to retrieve the first entity matching the specified conditions, or null if no match is found. Ensure the entity is not null before accessing its properties. ```php $author = $orm->author->getBy(['name' => 'Peter', 'age' => 23]); // Author|null if ($author !== null) { echo $author->name; } ``` -------------------------------- ### Define Getters and Setters for Entity Properties Source: https://github.com/nextras/orm/blob/main/docs/entity.md Implement custom logic for entity properties using protected getter and setter methods. Getters receive the stored value and return a modified one, while setters receive user input and return the value to be stored. Getters for virtual properties do not receive a value. ```php class FamilyMember extends Entity { protected function getterName(string $name): string { return ucwords($name); } protected function setterSiblingsCount(int $siblings): int { return max((int) $siblings, 0); } } ``` -------------------------------- ### Custom Property Converters in Mapper Source: https://github.com/nextras/orm/blob/main/docs/conventions.md Implement custom data transformation logic for entity properties when converting between storage and PHP formats using `setMapping` with callbacks. ```php /** * @param bool $isPublic */ class File extends Nextras\Orm\Entity\Entity { } /** * @extends DbalMapper */ class FilesMapper extends Nextras\Orm\Mapper\Dbal\DbalMapper { protected function createConventions(): Nextras\Orm\Mapper\Dbal\Conventions\IConventions { $conventions = parent::createConventions(); $conventions->setMapping('isPublic', 'is_public', function ($val) { return $val === 'Y' || $val === 'y'; }, function ($val) { return $val ? 'Y' : 'N'; }); return $conventions; } } ``` -------------------------------- ### Querying with QueryBuilder in DbalMapper Source: https://github.com/nextras/orm/blob/main/docs/mapper.md Fetch random books using Nextras Dbal's QueryBuilder. The result is wrapped in an ArrayCollection. Always use Dbal's query builder for efficiency. ```php /** * @extends DbalMapper */ class BooksMapper extends Nextras\Orm\Mapper\Dbal\DbalMapper { /** @return Nextras\Orm\Collection\ICollection */ public function getRandomBooksByBuilder(): Nextras\Orm\Collection\ICollection { return $this->toCollection( $this->builder()->addOrderBy('RAND()') ); } /** @return Nextras\Orm\Collection\ICollection */ public function getRandomBooksByQuery(): Nextras\Orm\Collection\ICollection { return $this->toCollection( $this->connection->query('SELECT * FROM tbl_books ORDER BY RAND()') ); } } ``` -------------------------------- ### Implementing a Dbal Collection Function Source: https://github.com/nextras/orm/blob/main/docs/collection-functions.md Provides the implementation of a Dbal collection function that filters entities using a LIKE comparison. It utilizes DbalQueryBuilderHelper to process the expression and appends the LIKE operator with the provided argument. ```php public function processDbalExpression( DbalQueryBuilderHelper $helper, QueryBuilder $builder, array $args, Aggregator|null $aggregator = null, ) : DbalExpressionResult { // $args is for example ['phone', '+420'] \assert(\count($args) === 2 && \is_string($args[0]) && \is_string($args[1])); $expression = $helper->processExpression($builder, $args[0], $aggregator); return $expression->append('LIKE %like_', $args[1]); } ``` -------------------------------- ### Persist and Flush Entity Changes Source: https://github.com/nextras/orm/blob/main/docs/model.md Use `persist()` to stage changes for an entity and `flush()` to commit them. Alternatively, `persistAndFlush()` combines both operations. Newly created and modified entities must be persisted again to ensure their changes are saved. ```php $user = $model->users->getById(1); $user->isEmailSubscribed = false; $model->persist($user); $model->flush(); // or $model->persistAndFlush($user); ``` -------------------------------- ### Iterating Over Relationships Source: https://github.com/nextras/orm/blob/main/docs/relationships.md Demonstrates how to iterate over entities within a relationship property. The relationship property implements the `\Traversable` interface. ```php foreach ($author->books as $book) { $book instanceof Book; // true } ``` -------------------------------- ### Define Basic Entity with Properties Source: https://github.com/nextras/orm/blob/main/docs/entity.md Defines a basic entity 'Member' with properties like ID, name, registration date, and an associated 'AdminLevel' enum. Properties are defined using Phpdoc annotations, including primary keys, wrappers, and read-only properties. ```php use Nextras\Orm\Entity\PropertyWrapper\DateWrapper; /** * @property int $id {primary} * @property string $name * @property DateTimeImmutable $bornOn {wrapper DateWrapper} * @property string|null $web * @property AdminLevel $adminLevel * @property-read int $age */ class Member extends Nextras\Orm\Entity\Entity { } enum AdminLevel: int { case Full = 1; case Moderator = 2; } ``` -------------------------------- ### Inherit Directly from DbalMapper Source: https://github.com/nextras/orm/blob/main/docs/migrate_5.0.md The base Mapper class has been removed. Inherit directly from Nextras\Orm\Mapper\Dbal\DbalMapper instead. ```php class UsersMapper extends DbalMapper { ... } ``` -------------------------------- ### Limit Collection to Last 10 Published Books Source: https://github.com/nextras/orm/blob/main/docs/collection.md Retrieves the last 10 published books by first ordering by 'publishedAt' in descending order and then applying a limit of 10. ```php $orm->books->findAll()->orderBy('publishedAt', ICollection::DESC)->limitBy(10); ``` -------------------------------- ### Filter Authors with Any Book Price > 10 Source: https://github.com/nextras/orm/blob/main/docs/collection-filtering.md Filters authors to find those who have at least one book with a price greater than 10 and currency 'eur'. The AnyAggregator is implicit in this syntax. ```php use Nextras\Orm\Collection\Aggregations\AnyAggregator; use Nextras\Orm\Collection\Aggregations\CountAggregator; use Nextras\Orm\Collection\ICollection; $authors = $orm->authors->findBy([ 'books->price>' => 10, 'books->currency' => 'eur', ]); ``` ```php $authors = $orm->authors->findBy([ ICollection::AND, new AnyAggregator(), 'books->price>' => 10, 'books->currency' => 'eur', ]); ``` -------------------------------- ### Refresh All Entities from Storage Source: https://github.com/nextras/orm/blob/main/docs/model.md Use `refreshAll()` to update all entities managed by the model with the latest data from the database. This is useful when external processes might modify the data. ```php $book = $model->books->getById(1); sleep(60); // sleep for one minute $model->refreshAll(); // $book is updated with the latest data from database ``` -------------------------------- ### Assigning Entity by ID Source: https://github.com/nextras/orm/blob/main/docs/relationships.md Shows how to assign an entity to a relationship property using its primary key (ID). The ORM automatically loads the entity if it's attached to a repository. ```php $book->author = 1; $book->author->id === 1; // true $book->tags->remove(1); ``` -------------------------------- ### Limit Collection to Penultimate 10 Published Books Source: https://github.com/nextras/orm/blob/main/docs/collection.md Retrieves books from the 11th to the 20th position by ordering by 'publishedAt' descending and applying a limit of 10 with an offset of 10. ```php $orm->books->findAll()->orderBy('publishedAt', ICollection::DESC)->limitBy(10, 10); ``` -------------------------------- ### Advanced Many-to-Many Joining Table Configuration Source: https://github.com/nextras/orm/blob/main/docs/conventions.md Override `getManyHasManyParameters` for more advanced control over joining table names and keys. This method returns an array with the joining table name and an array of joining columns. ```php use Nextras\Orm\Mapper\Dbal\DbalMapper; /** * @extends DbalMapper */ class EmployeesMapper extends DbalMapper { public function getManyHasManyParameters(PropertyMetadata $sourceProperty, DbalMapper $targetMapper): array { if ($targetMapper instanceof DepartmentsMapper) { return ['emp_dept', ['emp_no', 'dept_no']]; } return parent::getManyHasManyParameters($sourceProperty, $targetMapper); } } ``` -------------------------------- ### Fetch Single Entity by Conditions (Strict) - PHP Source: https://github.com/nextras/orm/blob/main/docs/collection.md Use `getByChecked()` to retrieve the first entity matching the specified conditions. This method throws a `NoResultException` if no matching entity is found, ensuring an entity is always returned or an exception is caught. ```php $author = $orm->author->getByChecked(['name' => 'Peter', 'age' => 23]); // returns Author or throws NoResultException echo $author->name; ``` -------------------------------- ### Configure Relationship Cascade Behavior Source: https://github.com/nextras/orm/blob/main/docs/relationships.md Define cascade behavior for relationships using an array of keywords: `persist`, `remove`, and `removeOrphan`. `persist` is the default. An empty cascade array disables persist cascade. ```php {relModifier EntityName::$reversePropertyName} ``` ```php {relModifier EntityName::$reversePropertyName, cascade=[persist]} ``` ```php {relModifier EntityName::$reversePropertyName, cascade=[persist, remove]} ``` ```php {relModifier EntityName::$reversePropertyName, cascade=[]} ``` -------------------------------- ### Filter Entities by Embeddable Property Source: https://github.com/nextras/orm/blob/main/docs/embeddables.md Demonstrates how to filter entities based on a property within an embedded object using a nested path. ```php $users = $orm->users->findBy(['address->city' => 'Prague']); ``` -------------------------------- ### Filter Authors with No Book Price > 10 Source: https://github.com/nextras/orm/blob/main/docs/collection-filtering.md Uses NoneAggregator to find authors who do not have any book matching the specified price and currency criteria. ```php $authors = $orm->authors->findBy([ ICollection::AND, new NoneAggregator(), 'books->price>' => 10, 'books->currency' => 'eur', ]); ``` -------------------------------- ### Advanced Dbal Expression Construction Source: https://github.com/nextras/orm/blob/main/docs/collection-functions.md Shows how to construct a new DbalExpressionResult for more advanced operations, such as using SUBSTRING. It reuses an existing expression's arguments and properties to maintain query integrity. ```php $expression = $helper->processExpression($builder, $args[0], $aggregator); return new DbalExpressionResult( expression: 'SUBSTRING(%ex, 1, %i) = %s', args: [$expression->getArgsForExpansion(), \strlen($args[1]), $args[1]], joins: $expression->joins, ); ``` -------------------------------- ### Control Cascade with Persist and Remove Methods Source: https://github.com/nextras/orm/blob/main/docs/relationships.md Control cascade behavior when persisting or removing entities. By default, cascades are active. Set `withCascade: false` to manually manage persistence or removal of related entities. ```php $usersRepository->persist($user, withCascade: false); ``` ```php $usersRepository->remove($user, withCascade: false); ``` -------------------------------- ### Update Generic Annotations for Repository and Mapper Source: https://github.com/nextras/orm/blob/main/docs/migrate_5.0.md Add generic arguments to Repository and Mapper types, and update phpdoc extends for DbalMapper and Repository. ```php /** * @extends Repository */ class UsersRepository extends Repository { ... } /** * @extends DbalMapper */ class UsersMapper extends DbalMapper { ... } ``` -------------------------------- ### Sort Collection by Count of Related Property (Descending) Source: https://github.com/nextras/orm/blob/main/docs/collection-filtering.md Sorts a collection of authors by the count of their associated books in descending order. Authors with more books appear first. ```php use Nextras\Orm\Collection\Functions\CountAggregateFunction; use Nextras\Orm\Collection\ICollection; $authorsCollection->orderBy( [CountAggregateFunction::class, 'books->id'], ICollection::DESC ); ``` -------------------------------- ### Sort Collection by Title Ascending Source: https://github.com/nextras/orm/blob/main/docs/collection.md Sorts a collection of books by their title in ascending order. Use this for default alphabetical sorting. ```php $orm->books->findAll()->orderBy('title'); // ORDER BY title ASC ``` -------------------------------- ### Fetch Entity by Primary Key (Strict) - PHP Source: https://github.com/nextras/orm/blob/main/docs/collection.md Use `getByIdChecked()` as a shortcut to retrieve an entity by its primary key. It throws a `NoResultException` if the entity is not found. This is equivalent to using `getByChecked(['id' => $primaryValue]). ```php $author = $orm->author->getByIdChecked(2); // returns Author or throws NoResultException // equals $author = $orm->author->getByChecked(['id' => 2]); ``` -------------------------------- ### Count Stored Entities for Pagination Source: https://github.com/nextras/orm/blob/main/docs/collection.md Counts the total number of matching entities in the storage, typically used for pagination. This method runs a COUNT query. ```php $this->paginator->totalCount = $articles->countStored(); ``` -------------------------------- ### Process Array Expression for Like Functionality Source: https://github.com/nextras/orm/blob/main/docs/collection-functions.md This function processes array expressions for 'Like' functionality, performing a prefix comparison between a property's value and user input. It utilizes ArrayCollectionHelper to retrieve property values and returns an ArrayExpressionResult indicating the comparison outcome. ```php public function processArrayExpression( ArrayCollectionHelper $helper, IEntity $entity, array $args, ?Aggregator $aggregator = null, ): ArrayExpressionResult { // $args is for example ['phone', '+420'] \assert(\count($args) === 2 && \is_string($args[0]) && \is_string($args[1])); $valueResult = $helper->getValue($entity, $args[0], $aggregator); return new ArrayExpressionResult( value: str_starts_with(haystack: $valueResult->value, needle: $args[1]), ); } ``` -------------------------------- ### Manually Remove Related Books Before Removing Author Source: https://github.com/nextras/orm/blob/main/docs/repository.md An alternative to reassigning related entities is to manually remove them first. This approach ensures that the parent entity can be safely removed without violating relationship constraints. ```php $author = $orm->authors->getById(...); foreach ($author->books as $book) { $orm->remove($book); } $orm->remove($author); ``` -------------------------------- ### Refresh All Entities Allowing Data Override Source: https://github.com/nextras/orm/blob/main/docs/model.md When calling `refreshAll(true)`, any unpersisted changes to entities will be discarded and replaced by the data from the storage. This can be useful to ensure consistency even if entities have been modified but not yet flushed. ```php $book = $model->books->getById(1); $book->title = 'Test'; $model->persistAndFlush($book); $book->title = 'Changed title'; $model->refreshAll(true); assert($book->title === 'Test'); // the title may be actually different, because another process may have changed it // the "Changed title" value is discarded and replaced by the actual database value ``` -------------------------------- ### Sort Collection by Multiple Properties Source: https://github.com/nextras/orm/blob/main/docs/collection.md Sorts a collection by multiple properties, applying secondary sorting rules when primary ones are equal. Use an associative array for property-direction pairs. ```php $orm->books->findAll()->orderBy([ 'title' => ICollection::ASC, 'publishedYear' => ICollection::DESC, ]); ``` -------------------------------- ### Customize Embeddable Mapping with Conventions Source: https://github.com/nextras/orm/blob/main/docs/embeddables.md Overrides default database column naming conventions for embeddable properties using the `IConventions` interface. ```php protected function createConventions(): IConventions { $conventions = parent::createConventions(); $conventions->setMapping('address->zipCode', 'address_postal_code'); return $conventions; } ``` -------------------------------- ### Convert Relationship to Collection Source: https://github.com/nextras/orm/blob/main/docs/relationships.md Call `toCollection()` on a relationship property to obtain a collection object for further adjustments like limiting results. This preserves optimized loading for other entities. ```php $collection = $author->books->toCollection(); $collection = $collection->limitBy(3); ``` ```php $authors = $orm->authors->findById([1, 2]); // fetches 2 authors foreach ($authors as $author) { // 1st call for author #1 fetches data for both authors by // (SELECT ... WHERE author_id = 1 LIMIT 2) UNION ALL (SELECT ... WHERE author_id = 2 LIMIT 2) // and returns data just for the author #1. // // 2nd call for author #2 uses already fetched data. $sub = $author->books->toCollection()->limitBy(2); } ``` -------------------------------- ### Persist and Flush New Entity Source: https://github.com/nextras/orm/blob/main/docs/repository.md Saves a new entity and its related entities (with cascade) to the database within a transaction. The `persistAndFlush` method handles both persisting and committing the changes. ```php $author = new Author(); $author->name = 'Jon Snow'; $author->born = 'yesterday'; $author->mail = 'snow@wall.st'; $book = new Book(); $book->title = 'My Life on The Wall'; $book->author = $author; // stores new book and author entity into database // queries are run in transaction and committed $orm->persistAndFlush($book); ``` -------------------------------- ### Conditional Rendering Based on Count Source: https://github.com/nextras/orm/blob/main/docs/collection.md Conditionally renders a list of articles only if the collection is not empty. It uses the count() method to check the number of fetched entities. ```latte {if $articles->count()} {foreach $articles} ... {/foreach} {else} You have no articles. {/if} ``` -------------------------------- ### Filter Authors with At Least Two Books > 10 Source: https://github.com/nextras/orm/blob/main/docs/collection-filtering.md Employs CountAggregator to find authors who have at least two books with a price greater than 10 and currency 'eur'. ```php $authors = $orm->authors->findBy([ ICollection::AND, new CountAggregator(atLeast: 2, atMost: null), 'books->price>' => 10, 'books->currency' => 'eur', ]); ``` -------------------------------- ### 1:1 Self-referencing Relationship Source: https://github.com/nextras/orm/blob/main/docs/relationships.md Defines a self-referencing One-to-One relationship where a Book entity can link to its next volume. The reference is stored in `book.next_volume_id`. ```php /** * @property int $id {primary} * @property Book|null $nextVolume {1:1 Book::$previousVolume, isMain=true} * @property Book|null $previousVolume {1:1 Book::$nextVolume} */ class Book extends Nextras\Orm\Entity\Entity {} ``` -------------------------------- ### 1:1 Bidirectional Relationship Source: https://github.com/nextras/orm/blob/main/docs/relationships.md Defines a bidirectional One-to-One relationship between Book and Ean entities. The reference is stored in the `book.ean_id` field. `isMain=true` designates the primary side. ```php /** * @property int $id {primary} * @property Ean $ean {1:1 Ean::$book, isMain=true} */ class Book extends Nextras\Orm\Entity\Entity {} /** * @property int $id {primary} * @property Book $book {1:1 Book::$ean} */ class Ean extends Nextras\Orm\Entity\Entity {} ``` -------------------------------- ### Filter Collection by Aggregated Count (Greater Than) Source: https://github.com/nextras/orm/blob/main/docs/collection-filtering.md Filters authors to include only those who have written more than two books. This involves grouping by author and then filtering the groups based on the count of books. ```php use Nextras\Orm\Collection\Functions\CompareGreaterThanFunction; use Nextras\Orm\Collection\Functions\CountAggregateFunction; // SELECT * FROM authors // LEFT JOIN books ON (...) // GROUP BY authors.id // HAVING COUNT(books.id) > 2 $authorsCollection->findBy( [ CompareGreaterThanFunction::class, [CountAggregateFunction::class, 'books->id'], 2, ] ); ``` -------------------------------- ### Sort Collection by Count of Related Property Source: https://github.com/nextras/orm/blob/main/docs/collection-filtering.md Sorts a collection of authors by the count of their associated books. Authors with fewer books appear first. ```php use Nextras\Orm\Collection\Functions\CountAggregateFunction; $authorsCollection->orderBy( [CountAggregateFunction::class, 'books->id'] ); ``` -------------------------------- ### Custom Property Mapping in Mapper Source: https://github.com/nextras/orm/blob/main/docs/conventions.md Define custom mappings between entity properties and database columns by overriding the `createConventions()` method in your mapper class. ```php use Nextras\Orm\Mapper\Dbal\DbalMapper; use Nextras\Orm\Mapper\Dbal\Conventions\IConventions; /** * @extends DbalMapper */ class EventsMapper extends DbalMapper { protected function createConventions(): IConventions { $conventions = parent::createConventions(); $conventions->setMapping('entityProperty', 'database_property'); return $conventions; } } ``` -------------------------------- ### Bidirectional 1:M / M:1 Relationship Source: https://github.com/nextras/orm/blob/main/docs/relationships.md Define a bidirectional one-to-many and many-to-one relationship between Author and Book entities. The `OneHasMany` type hint is used for the collection side. ```php use Nextras\Orm\Relationships\OneHasMany; /** * @property int $id {primary} * @property OneHasMany $books {1:m Book::$author} * @property OneHasMany $translatedBooks {1:m Book::$translator} */ class Author extends Nextras\Orm\Entity\Entity {} /** * @property int $id {primary} * @property Author $author {m:1 Author::$books} * @property Author $translator {m:1 Author::$translatedBooks} */ class Book extends Nextras\Orm\Entity\Entity {} ``` -------------------------------- ### Expose Relationship as Collection Source: https://github.com/nextras/orm/blob/main/docs/relationships.md Use the `exposeCollection=true` modifier in the entity property annotation to expose the relationship directly as a collection. This allows direct interaction with collection methods on the property. ```php /** * @property int|null $id * @property ICollection $books {1:m Author::$books, exposeCollection=true} */ class Author { public function setBooks(Book ...$books): void { $this->getProperty('books')->set($books); } } $author = new Author(); $author->books->findBy(...); $author->setBooks(new Book()); ``` -------------------------------- ### Clear Model Caches and References Source: https://github.com/nextras/orm/blob/main/docs/model.md Call `clear()` to free up memory by removing all cached entities and their references. This is crucial for batch processing large datasets to prevent memory exhaustion. Ensure no references to cleared entities are held after calling `clear()`. ```php $lastId = 0; do { $fetched = $model->books->findBy(['id>' => $lastId])->orderBy('id')->limitBy(1000)->fetchAll(); foreach ($fetched as $book) { // do the work $lastId = $book->id; } // release the entities from the memory $model->clear(); } while (!empty($fetched)); // optionally, the final memory release unset($fetched, $book); $model->clear(); ``` -------------------------------- ### Sort Collection by Title Descending Source: https://github.com/nextras/orm/blob/main/docs/collection.md Sorts a collection of books by their title in descending order. Use this when reverse alphabetical order is needed. ```php $orm->books->findAll()->orderBy('title', ICollection::DESC); // ORDER BY title DESC ```