spotorm / spot2

Spot v2.x DataMapper built on top of Doctrine's Database Abstraction Layer
http://phpdatamapper.com
BSD 3-Clause "New" or "Revised" License
601 stars 101 forks source link

Added the NestedRelation #290

Open alexberezinsky opened 5 years ago

alexberezinsky commented 5 years ago

NestedRelation can be used for eager-loading of multi-level related entities.

Usage example for Post Entity:

namespace Entity;

use Spot\EntityInterface as Entity;
use Spot\MapperInterface as Mapper;

class Post extends \Spot\Entity
{
    protected static $table = 'posts';

    public static function fields()
    {
        return [
            'id'           => ['type' => 'integer', 'autoincrement' => true, 'primary' => true],
            'user_id'      => ['type' => 'integer', 'required' => true],
            'title'        => ['type' => 'string', 'required' => true],
            'body'         => ['type' => 'text', 'required' => true],
            'status'       => ['type' => 'integer', 'default' => 0, 'index' => true],
            'date_created' => ['type' => 'datetime', 'value' => new \DateTime()]
        ];
    }

    public static function relations(Mapper $mapper, Entity $entity)
    {
        $userRelation = $mapper->belongsTo($entity, 'Entity\User', 'user_id');
        $profileRelation =  User::relations($mapper, $entity)['profile'];
        return [
            'user' => $userRelation,
            'user.profile' => $mapper->nestedRelation($profileRelation, $userRelation);
        ];
    }
}

And then we can eager load user profiles with posts:

$posts = $posts->all()->with(['user', 'user.profile']);

NestedRelation can be used with any relations and can be multilevel. For example:

$posts = $posts->all()->with([
     'user', 
     'user.profile'
     'user.profile.image',
     'comments',
     'comments.user',
     'comments.user.profile',
     'comments.user.profile.image'
]);
alexberezinsky commented 5 years ago

Hey guys. Please tell me what do you think about this idea. I use it for one of my projects to avoid lazy-loading of related entities. This can save a lot of db queries. I would like to contribute it to Spot2. If you like the idea I can continue working on it and improve it further.

FlipEverything commented 5 years ago

I like it! Somebody from the team will review it as soon as possible.

alexberezinsky commented 5 years ago

Ok, great. Thanks.

alexberezinsky commented 5 years ago

Hey guys,

I added some tests to the new relation.

Also I changed the way of adding nested relations to entities. So actually now there is no need to define them inside entities. Nested relations will be created automatically when added with 'with' method. So the only things to ensure is that all the parent levels are loaded and the target relation exists. For example:

$posts->all()->with(['user_comments', 'user_comments.user']);

For this case Post entity should have a 'user_comments' relation and UserComment entity should have 'user' relation. That will be enough for the above code to work.

I can improve it further if there is a need.

Thanks.