Skip to content

Add options DTOs for Select::load() method#553

Open
roxblnfk wants to merge 12 commits into2.xfrom
relation-configs
Open

Add options DTOs for Select::load() method#553
roxblnfk wants to merge 12 commits into2.xfrom
relation-configs

Conversation

@roxblnfk
Copy link
Member

@roxblnfk roxblnfk commented Feb 26, 2026

Load Options DTO — Summary

Motivation

Select::load() historically accepted raw arrays for configuration. This made the API non-discoverable:
the user had to know key names, valid value types, and which options apply to which relation type.

The DTO approach replaces untyped arrays with typed objects — one per relation type — where every option
is a named constructor parameter with a default value, a type, and a docblock.

// Before
$select->load('comments', [
    'method' => Select\JoinableLoader::INLOAD,
    'where'  => ['@.status' => 'public'],
]);

// After
$select->load('comments', new HasManyLoadOptions(
    method: LoadMethod::SingleQuery,
    where: ['@.status' => 'public'],
));

Array syntax is still supported for backwards compatibility.


DTO Hierarchy

LoadOptions                          # scope, minify, table
├── JoinableLoadOptions              # + method, as, using
│   ├── HasOneLoadOptions            # + where, orderBy          (default: SingleQuery)
│   ├── HasManyLoadOptions           # + where, orderBy          (default: OuterQuery)
│   ├── BelongsToLoadOptions         # + where                   (default: OuterQuery)
│   ├── ManyToManyLoadOptions        # + where, orderBy, pivot   (default: OuterQuery)
│   ├── MorphedHasOneLoadOptions     # + where, orderBy          (default: SingleQuery)
│   └── MorphedHasManyLoadOptions    # + where, orderBy          (default: OuterQuery)
└── BelongsToMorphedLoadOptions      # (no extra options)

All classes live in Cycle\ORM\Select\Options.

Enums

Enum Cases Maps to
LoadMethod SingleQuery (1), OuterQuery (2) INLOAD / POSTLOAD
JoinMethod InnerJoin (3), LeftJoin (4) JOIN / LEFT JOIN

$method accepts LoadMethod|JoinMethod|null.

Table Override ($table)

Every DTO exposes ?string $table (@var non-empty-string|null).
When set, the loader reads data from the specified table instead of the one defined in schema.

Use case — loading from an archive:

$select->load('comments', new HasManyLoadOptions(
    table: 'comment_archive',
    orderBy: ['@.created_at' => 'DESC'],
));

The option is available for all relation types except BelongsToMorphed
(target is resolved dynamically by discriminator).

toArray() Mapping

Each DTO implements toArray(): array that converts to the legacy options format understood by loaders.
Null-valued optional fields are omitted so they don't override schema-defined defaults
(loaders merge via $options + $this->options).

Integration Points

File Change
Select::load() Accepts LoadOptions|array as second argument
AbstractLoader::loadRelation() Converts DTO via $options->toArray()
ManyToManyLoader::loadRelation() Same — signature updated to LoadOptions|array
ChainTrait::loadRelation() Abstract signature updated

Tests

Unit Tests (tests/ORM/Unit/Select/Options/)

One test class per DTO + enum. Cover constructor defaults, toArray() output, enum values.

Test Covers
LoadOptionsTest LoadOptions
JoinableLoadOptionsTest JoinableLoadOptions
HasOneLoadOptionsTest HasOneLoadOptions
HasManyLoadOptionsTest HasManyLoadOptions
BelongsToLoadOptionsTest BelongsToLoadOptions
ManyToManyLoadOptionsTest ManyToManyLoadOptions
MorphedHasOneLoadOptionsTest MorphedHasOneLoadOptions
MorphedHasManyLoadOptionsTest MorphedHasManyLoadOptions
BelongsToMorphedLoadOptionsTest BelongsToMorphedLoadOptions
LoadMethodTest LoadMethod enum
JoinMethodTest JoinMethod enum

Functional Tests (tests/ORM/Functional/Driver/Common/Relation/)

Each abstract test class has driver-specific subclasses for SQLite, MySQL, Postgres, SQLServer.

Test Relation Methods
HasOneLoadOptionsTest HAS_ONE SingleQuery, OuterQuery, Where, Where+SingleQuery, OrderBy, ScopeDisabled, CustomScope, SingleQuery+Scope+Where, Defaults, ArchiveTable
HasManyLoadOptionsTest HAS_MANY SingleQuery, OuterQuery, Where, Where+SingleQuery, OrderBy, ScopeDisabled, CustomScope, CustomScope+SingleQuery, SingleQuery+Scope+Where, Defaults, ArchiveTable
BelongsToLoadOptionsTest BELONGS_TO SingleQuery, OuterQuery, Using, Defaults, ArchiveTable
ManyToManyLoadOptionsTest MANY_TO_MANY SingleQuery, OuterQuery, OrderBy, Where, Where+SingleQuery, ScopeDisabled, CustomScope, SingleQuery+Scope+Where, Defaults, ArchiveTable
MorphedHasManyLoadOptionsTest MORPHED_HAS_MANY SingleQuery, OuterQuery, Where, Where+SingleQuery, ScopeDisabled, CustomScope, SingleQuery+Scope+Where, Defaults, ArchiveTable
MorphedHasOneLoadOptionsTest MORPHED_HAS_ONE SingleQuery, OuterQuery, Defaults, ArchiveTable

@roxblnfk roxblnfk added the type:feature New feature. label Feb 26, 2026
@codecov
Copy link

codecov bot commented Feb 27, 2026

Codecov Report

❌ Patch coverage is 92.40506% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.75%. Comparing base (4be87c3) to head (9a645e7).

Files with missing lines Patch % Lines
src/Select/AbstractLoader.php 69.23% 4 Missing ⚠️
src/Select.php 50.00% 1 Missing ⚠️
src/Select/Loader/ManyToManyLoader.php 50.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##                2.x     #553      +/-   ##
============================================
+ Coverage     91.73%   91.75%   +0.02%     
- Complexity     1924     1962      +38     
============================================
  Files           123      131       +8     
  Lines          4958     5031      +73     
============================================
+ Hits           4548     4616      +68     
- Misses          410      415       +5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type:feature New feature.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant