mattsplat / dynamic-loading
Dynamically load undefined relationships
Installs: 4
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1
Watchers: 1
Forks: 0
Open Issues: 0
pkg:composer/mattsplat/dynamic-loading
Requires
- php: ^8.0
- illuminate/database: ^8.0|^9.0|^10.0
Requires (Dev)
- orchestra/testbench: ^8.5
- phpunit/phpunit: ^10.0
This package is auto-updated.
Last update: 2025-10-29 03:11:27 UTC
README
Install
composer require mattsplat/dynamic-loading
Basic usage
You do not need a foreign key connecting your tables.
$collectionOfModels->dynamicLoad(
        'name_of_custom_relation', // this is only defined here and where the data is accessed
        function ($model) {
            
            return RelatedModel::where('conditions', $model->conditions)->orderBy('condition');
     
        },
        'relation_key', // Optional if this does not exist it will be created as an alias
        'model_key' // Optional used to match the records back to the model
    );
If relation_key and model_key are not defined they will be created
model_key: {primary_key} relation_key: {model_name}_{primary_key}
Examples
Ranks user doesn't have ending with 'r'
Given the following database structure it's easy to add a relation for the current rank but not for ranks not associated with the user.
users
- id
- rank_id
ranks
- id
- name
    /// add r ranks relation that gets all ranks that start with r except the user's current rank 
    $users = $users->dynamicLoad(
        'r_ranks', 
        fn($m) => Rank::where('name', 'like', '%r')->where('id', '!=', $m->rank_id)
    );
This would produce 1 query and add the matching ranks query as a relationship to the the collection of users.
result
$users[0] => [
        'id' => 1,
        'rank_id' => 5,
        'r_ranks' => [
                0 => [
                    'id' => 6,
                    'name' => 'Performer'
                
                ],
                1 =>  => [
                    'id' => 8,
                    'name' => 'Racer'
                
                ]
        ]
]
This works by injecting the user_id in a subselect for each model and combining the subqueries with a union.
query produced
select * from (select *, 1 as user_id from "ranks" where "name" like '%r' and "id" != 1) 
        union select * from (select *, 2 as user_id from "ranks" where "name" like '%r' and "id" != 1) 
        union select * from (select *, 3 as user_id from "ranks" where "name" like '%r' and "id" != 1) 
        union select * from (select *, 4 as user_id from "ranks" where "name" like '%r' and "id" != 1)
Here we have a matching user_id on logins table but if we only want the latest login we have to return them all with a typical relationship. Instead we can dynamically load the just the latest login for a collection of users using just 1 query.
users
- id
logins
- id
- created_at
- user_id
   // get the latest login for user
    $users = $users->dynamicLoad(
        'latest_login', 
        fn($m) => Login::where('user_id', '!=', $m->id)->latest()->limit(1)
    );
The results would look something like this
$users => [
        'id' => 1,
        'latest_login' => [
                0 => [
                    'id' => 6,
                    'created_at' => '2020-01-20 10:10:00'
                
                ],
        ]
],
[
        'id' => 2,
        'latest_login' => [
                0 => [
                    'id' => 78,
                    'created_at' => '2020-01-25 15:10:00'
                
                ],
        ]
],