Skip to content

Releases: SoloPHP/Repository

v1.3.0

Choose a tag to compare

@SoloPHP SoloPHP released this 01 Nov 18:18

Solo Repository 1.3 adds pages: ReadQuery::page() reads one page of entities with the number of matching rows on all pages, in an order that keeps rows with equal values on the same pages.

Features

  • ReadQuery::page() takes a PageRequest — the page number from 1, its size and its order — and returns a Page with the items, the total and the request:

    use Solo\Repository\PageRequest;
    
    $page = $products->read()->with(['category'])->page(
        new PageRequest(page: 3, perPage: 20, orderBy: ['created_at' => 'DESC']),
        ['price >=' => '50.00'],
    );
    $page->items; // list<Product>, read by created_at DESC, id DESC
    $page->total; // every matching product, counted only when the page cannot tell

    The primary key ends the order in the direction of its last field, unless the order names it, so rows that tie on every requested field fall on the same pages on every read. A short page tells the total by itself: only a full page, or an empty one past the end, runs COUNT(*), over the same criteria, visibility and locale. A mapping without a primary key reads the requested order as given and rejects a request without one, and a locking read rejects page(). On PostgreSQL, an index on (created_at, id) keeps such pages index-ordered; MySQL and MariaDB already keep the key in every secondary index.

  • Page::of() applies the same counting rule to rows of custom SQL, and PageRequest::orderEndingWith() the same ordering rule, so a query of its own can page the way page() does. Page::map() replaces the items, for example with what a response shows, and keeps the total and the request.

Upgrade

composer update solophp/repository

See Pages, the changelog and the full diff since 1.2.0.

v1.2.0

Choose a tag to compare

@SoloPHP SoloPHP released this 30 Oct 19:17

Solo Repository 1.2 adds TransactionManager, which gives services transaction boundaries over the application's DBAL connection without handing them SQL access.

Features

  • TransactionManager::run() runs a callback in Connection::transactional() on the connection the repositories already use, and returns its result:

    use Solo\Repository\TransactionManager;
    
    $transactions = new TransactionManager($db); // $products also uses $db
    $transactions->run(static function () use ($products, $id): void {
        $products->update($id, ['sku' => 'TABLE-02']);
        $products->translations()->save($id, 'uk', ['name' => 'Стіл']);
        $products->links('tags')->sync($id, [2, 5]);
    });

    Construction opens no connection, and the manager never closes the application connection. DBAL keeps commit, rollback and nested savepoints: a successful nested run does not commit the outer transaction, and a caught inner failure rolls back to its savepoint. Callback results keep their type through PHPDoc generics. There are no retries, after-commit callbacks or distributed transactions, and repositories on another connection do not participate.

Upgrade

composer update solophp/repository

See Transactions and the DBAL escape hatch, the changelog and the full diff since 1.1.0.

v1.1.0

Choose a tag to compare

@SoloPHP SoloPHP released this 28 Oct 10:40

Solo Repository 1.1 makes LIKE patterns escape the same way on every supported database and adds Like, which builds such patterns from plain text.

Fixes

  • LIKE and NOT LIKE escape with \ on SQLite, MySQL, MariaDB and PostgreSQL, so \%, \_ and \\ match %, _ and \ themselves. SQLite has no default escape character, and under NO_BACKSLASH_ESCAPES neither has MySQL, nor MariaDB for single-byte character sets; there 50\% matched 50\ followed by anything. A LIKE now ends in ESCAPE '\' on SQLite and ESCAPE X'5C' on MySQL and MariaDB. Both are literals, so patterns with a fixed prefix keep their index ranges. PostgreSQL, which always escapes with \, gets no clause, and LIKE on bytea fields keeps working.

Features

  • Like::escape(), contains(), startsWith() and endsWith() turn plain text, such as a search term, into a pattern that matches it literally:

    use Solo\Repository\Like;
    
    $products->read()->findBy(['name LIKE' => Like::contains($search)]);

Behavior changes

  • On SQLite, and on MySQL and MariaDB under NO_BACKSLASH_ESCAPES, a \ in a pattern escapes the character after it instead of matching a backslash: the pattern C:\dir matches C:dir. Patterns without \, PostgreSQL and MySQL/MariaDB in their default mode are unchanged.
  • A pattern that ends in an unpaired \ throws InvalidArgumentException before SQL instead of a result that differs by database.

Upgrade

composer update solophp/repository

See LIKE patterns, the changelog and the full diff since 1.0.0.

v1.0.0

Choose a tag to compare

@SoloPHP SoloPHP released this 27 Oct 21:45
feee57b

First stable release of Solo Repository (solophp/repository): typed PHP repositories on Doctrine DBAL. Map a table to your own objects, read through reusable queries, write explicitly, and get translations with one fallback rule and relations loaded in batches.

The code is that of v1.0.0-RC6; two applications run it in production.

What 1.0 covers

  • Explicit, typed Mapping and Repository, which an application can extend into a typed repository per table.
  • Reusable ReadQuery with projections, aggregates, bounded iteration by ID, queryBuilder() and ordering by fields of BelongsTo targets.
  • Filter::all, any, not and related EXISTS predicates, with values checked against their field type before SQL.
  • Translations with one fallback rule for reading, filtering and sorting, and PATCH upserts through TranslationStore.
  • Four relation types with nested batch loading, locale inheritance and per-relation visibility.
  • Soft delete, native upserts, atomic insertMany, LinkSet pivot writes, lockForUpdate() and locking reads with forUpdate().
  • Tested on MySQL 8.4, MariaDB 12.3, PostgreSQL 17 and SQLite, with PHP 8.3–8.5 and DBAL 4.3+.

From now on the public API follows semantic versioning; see the API reference for what it covers and the rules for typed repositories.

Install

composer require solophp/repository

See the documentation, the changelog and the full diff since RC6.

v1.0.0-RC6

v1.0.0-RC6 Pre-release
Pre-release

Choose a tag to compare

@SoloPHP SoloPHP released this 27 Oct 15:13
ff122e9

Sixth release candidate of Solo Repository (solophp/repository). LinkSet writes no longer fail when another writer stores the same pair first, locking reads on MySQL no longer depend on a deprecated DBAL platform class, and the compatibility rules for typed repositories are documented.

This is a prerelease: the 1.0 API is being validated in real applications before the stable 1.0.0 tag.

Migrating from Base Repository v3: read the migration guide.

Changes since 1.0.0-RC5

  • Behavior change: LinkSet::attach() and sync() skip a pair that another writer stored after the operation read the set, instead of failing on the pivot's unique key. Their INSERT carries ON CONFLICT (pair) DO NOTHING on PostgreSQL and SQLite and ON DUPLICATE KEY UPDATE on MySQL and MariaDB; foreign-key violations still propagate. Concurrent attaches of one pair now both succeed on MySQL and on PostgreSQL in READ COMMITTED, while MariaDB with snapshot isolation (error 1020) and PostgreSQL in REPEATABLE READ or SERIALIZABLE (a serialization failure) still refuse a transaction whose snapshot is older than the other writer's commit. On PostgreSQL and SQLite these writes now need the UNIQUE pair that the guide already required. See LinkSet.
  • A locking read on MySQL tells FOR UPDATE OF support by the server version, 8.0.1 or later, instead of by DBAL's deprecated MySQL80Platform, so it keeps locking only the read's table once DBAL drops its MySQL 8 platform classes. MySQL 5.7 and MariaDB keep the plain FOR UPDATE.
  • The compatibility rules for subclasses of Repository are documented: throughout 1.x it stays a non-final readonly class with final public methods and private state, a minor release may add public methods, and constructor parameters keep their names and order. See typed repositories.

Install

composer require 'solophp/repository:^1.0@RC'

See the changelog for the full list of features and requirements, and the full diff since RC5.

v1.0.0-RC5

v1.0.0-RC5 Pre-release
Pre-release

Choose a tag to compare

@SoloPHP SoloPHP released this 27 Oct 12:00
c774271

Fifth release candidate of Solo Repository (solophp/repository). It adds locking reads, ordering by the fields of related rows and native upserts — what the first application with 140 tables still wrote by hand.

This is a prerelease: the 1.0 API is being validated in real applications before the stable 1.0.0 tag.

Migrating from Base Repository v3: read the migration guide.

Changes since 1.0.0-RC4

  • ReadQuery::forUpdate() returns the same read as a locking one: find(), findOneBy(), findBy(), rows(), exists() and iterateById() run SELECT ... FOR UPDATE and lock the rows they read until the transaction ends, and throw LogicException outside a transaction. A locking read never answers from the snapshot of a REPEATABLE READ transaction: MySQL reads the latest committed row, while PostgreSQL and MariaDB reject a row changed since the snapshot as a serialization failure. PostgreSQL and MySQL 8 lock only the rows of the read's table (FOR UPDATE OF). See the read API.
  • orderBy accepts dotted paths through BelongsTo relations to a field of the target, such as category.name, in reads, default relation orders and Load orders. Each target is joined over the rows a load of the same relation in the read sees, so a list that shows soft-deleted rows through Visibility::All also orders by them.
  • Repository::upsert($key, $data, $onInsert = []) inserts the row of a unique key, or updates the row that already holds it, in one native statement. See Repository writes.
  • Behavior change: insertMany() runs a batch that fits one statement without a transaction or savepoint. Inside a caller's transaction on PostgreSQL, a failure of such a batch now aborts that transaction, as a failed insert() does.
  • Load and default relation orders accept the same paths, resolved when the read prepares its loads; an invalid order key reports "Order must use field names or relation paths to fields."

Install

composer require 'solophp/repository:^1.0@RC'

See the changelog for the full list of features and requirements, and the full diff since RC4.

v1.0.0-RC4

v1.0.0-RC4 Pre-release
Pre-release

Choose a tag to compare

@SoloPHP SoloPHP released this 27 Oct 08:53
9d9f96b

Fourth release candidate of Solo Repository (solophp/repository). Repository can now be extended into a typed repository per table, the shape the first application with 140 tables settled on.

This is a prerelease: the 1.0 API is being validated in real applications before the stable 1.0.0 tag.

Migrating from Base Repository v3: read the migration guide.

Changes since 1.0.0-RC3

  • Repository is no longer final. An application can extend it with a readonly class that fixes the mapping in its own constructor, so a container injects each table's repository by type and static analysis reads the model from @extends Repository<Model>. See Typed repositories.
  • Every public method except the constructor is final: a subclass adds methods of its own and cannot change the inherited reads and writes.
  • Code that constructs Repository directly keeps working unchanged.

Install

composer require 'solophp/repository:^1.0@RC'

See the changelog for the full list of features and requirements, and the full diff since RC3.

v1.0.0-RC3

v1.0.0-RC3 Pre-release
Pre-release

Choose a tag to compare

@SoloPHP SoloPHP released this 25 Oct 11:23
3383800

Third release candidate of Solo Repository (solophp/repository). It closes two gaps that moving the first real application to RC2 revealed: long IN lists bind as one DBAL list parameter, and a mapping read only as rows needs no hydrator.

This is a prerelease: the 1.0 API is being validated in real applications before the stable 1.0.0 tag.

Migrating from Base Repository v3: read the migration guide.

Changes since 1.0.0-RC2

  • IN and NOT IN criteria and the key lists of relation loads and lockForUpdate() are bound as one DBAL list parameter (ArrayParameterType) of values converted through the field type, instead of one named parameter per value. DBAL expands the list into the same SQL when it runs the statement. A type without a DBAL list binding, such as boolean, still binds each value, and the parameter budget still counts every value of a list.
  • Behavior change: a queryBuilder() with IN criteria holds a list parameter. Run the builder itself, or pass getParameterTypes() along with getParameters() when its SQL becomes part of another statement.
  • The hydrate callback of a Mapping is optional, and Mapping::$hydrate is nullable. A mapping without one is read through rows(), aggregates, queryBuilder() and related filters, and written as usual; find(), findOneBy(), findBy(), iterateById() and loading it as a relation target throw LogicException before SQL.

Install

composer require 'solophp/repository:^1.0@RC'

See the changelog for the full list of features and requirements, and the full diff since RC2.

v1.0.0-RC2

v1.0.0-RC2 Pre-release
Pre-release

Choose a tag to compare

@SoloPHP SoloPHP released this 21 Oct 15:09
64857aa

Second release candidate of Solo Repository (solophp/repository). It closes the gaps that migrating the first real application from RC1 revealed, and lowers the time and memory of reads, relation loads and insertMany().

This is a prerelease: the 1.0 API is being validated in real applications before the stable 1.0.0 tag.

Migrating from Base Repository v3: read the migration guide.

Changes since RC1

  • ReadQuery::queryBuilder($alias, $criteria) returns a DBAL QueryBuilder with the mapped table under the alias in FROM, the criteria and visibility in WHERE, and the translation joins the criteria need. It selects nothing: add the SELECT, joins and grouping, and add your own conditions with andWhere(), because where() replaces the criteria and visibility. It replaces v3's table() with applyCriteria().
  • Mappings without a primary key: Mapping::$primaryKey may be NULL for tables with composite keys. Their rows are read, inserted and changed by criteria, and operations by ID throw before SQL.
  • Translations::$defaultLocale is optional. Without it, a read that names no locale joins no translations and returns NULL translated fields; conditions, sorts and aggregates on translated fields throw LogicException.
  • TranslationStore::all($id) returns the translations of every locale of one owner, keyed by locale.
  • The assign callback of a relation is optional, for relations that are only filtered by or linked; loading such a relation throws LogicException before SQL.
  • A relation's target factory runs once per owner mapping instead of on every load, filter and link set.
  • Reads without relation loads hydrate rows directly, every read releases rows as it goes, relation batches reuse one prepared target read, and insertMany() no longer copies its rows.

Behavior changes

  • TranslationStore::save() and seed() insert only the given fields into a new translation row, so omitted fields take their column defaults instead of NULL. A translated column with a non-NULL default therefore keeps that default and does not fall back.
  • Relation::$assign and Mapping::$primaryKey are nullable, and Mapping::$generatedId is false without a primary key.

Install

composer require 'solophp/repository:^1.0@RC'

See the changelog for the full list of features and requirements, and the full diff since RC1.

v1.0.0-RC1

v1.0.0-RC1 Pre-release
Pre-release

Choose a tag to compare

@SoloPHP SoloPHP released this 21 Oct 09:12
47fd1eb

First release candidate of Solo Repository (solophp/repository), the successor of solophp/base-repository v3. It replaces inheritance with explicit, typed mappings and composition, moves reads to reusable queries, and loads relations in batches with one translation fallback rule.

This is a prerelease: the 1.0 API is complete and is being validated in a real application before the stable 1.0.0 tag.

Migrating from Base Repository v3: read the migration guide.

Highlights

  • Mapping and Repository by composition; constructing them never opens a database connection.
  • ReadQuery with projections, count()/exists(), sum()/min()/max()/avg() and bounded iterateById() exports.
  • Four relation types with nested batch loading, locale inheritance and per-relation visibility.
  • Filter::all/any/not/related criteria. Criteria values and written values must have the form of their field type, so drivers cannot silently turn '12x' into 12.
  • Soft delete, lockForUpdate() row locks, transactional insertMany(), many-to-many LinkSet, translation save()/seed().
  • Tested on SQLite, MySQL 8.4, MariaDB 12.3 and PostgreSQL 17 with PHP 8.3 and 8.5 and Doctrine DBAL 4.3 or later.

Install

Until the package is on Packagist, add the GitHub repository:

composer config repositories.solophp-repository vcs https://github.com/SoloPHP/Repository.git
composer require 'solophp/repository:^1.0@RC'

See the changelog for the full list of features and requirements.