Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: Polymorphic Relationship Example #29

Closed
sniper7kills opened this issue Jul 29, 2019 · 7 comments
Closed

Question: Polymorphic Relationship Example #29

sniper7kills opened this issue Jul 29, 2019 · 7 comments

Comments

@sniper7kills
Copy link

This package looks amazing, I haven't fully played with it too much but wanted to ask about polymorphic relationships.

Lets say I have Pages, Posts, and Comments. I want to make an API end point for comments that can pull the comments for a specific page or post with additional filters.

Is the recommended method to be similar to:
#28 (comment)

Where if I have two filters: polymorphic_type and polymorphic_key I should do something like this in my comments controller, or would there be a better way?

(Code may not actually work; just trying to figure out the best way to go about this)

//API\Comments.php
public function index(Request $request)
{
    $filters = $request->get('filters');
    if(isset($filters['polymorphic_type']) && isset($filters['polymorphic_key'])) {
        $polymorphic = (new $filters['polymorphic_type'])->findOrFail($filters['polymorphic_key');
        $comments = $polymorphic->comments()->getQuery();
    }else{
        $comments = Comment::class;
    }

    $result = EloquentBuilder::to(
                $comments, 
                $request->all('filters')
    )->paginate(10);
    return response()->json($result);
}
@mohammad-fouladgar
Copy link
Owner

Hi Dear @sniper7kills ,
I read your problem. it's solvable,.But, please let me to find a suitable solution.
Thanks.

@mohammad-fouladgar
Copy link
Owner

Hi @sniper7kills ,
I'm sorry for delay.

You can make a filter as CommentableFilter to your comment model:

<?php

namespace App\EloquentBuilders\Comment;

use Fouladgar\EloquentBuilder\Support\Foundation\Contracts\Filter;
use Illuminate\Database\Eloquent\Builder;

class CommentableFilter extends Filter
{
    /**
     *  Apply Commentable filter to the query builder 
     *
     * @param Builder $builder
     * @param mixed   $value
     *
     * @return Builder
     */
    public function apply(Builder $builder, $value): Builder
    {
       // You should control your logic in here
       // Simple usage:
        return $builder->where(function ($query) use($value) {
                $query->where('commentable_id', $value['key'])
                      ->where('commentable_key',$value['type']);
               });
    }
}

Next:

// Get: api/comments?filters[commentable][key]=1&filters[commentable][type]=post

$result = EloquentBuilder::to(Comment::class,$request->filters)
       ->with('commentable') // optional
       ->paginate();

    return response()->json($result);

I hope this answers your questions.

@mohammad-fouladgar mohammad-fouladgar pinned this issue Aug 9, 2019
@sniper7kills
Copy link
Author

That is much cleaner than what I was thinking of doing!

Thanks so much for the reply!

@sniper7kills
Copy link
Author

sniper7kills commented Aug 11, 2019

Because I have multiple Many-To-Many polymorphic models I thought I'd share what I ended up doing for posterity.

This example is based off of Posts, Videos and Tags.

The following would be the example filter array; One could also add a polymorphic_pivot array to filter based on any additional columns in the pivot table.

//type = what we want to get
//id = id of what we currently have

// Requesting Tags from Post/Video
// http://127.0.0.1/api/tags
$filters = ['polymorphic' => ['polymorphic_type' => 'Tag', 'polymorphic_id' => {POST_OR_VIDEO_ID}]];

// Requesting Posts from Tag
// http://127.0.0.1/api/posts
$filters = ['polymorphic' => ['polymorphic_type' => 'Post', 'polymorphic_id' => {TAG_ID}, 'polymorphic_inverse'=true]];

// Requesting Videos from Tag
// http://127.0.0.1/api/videos
$filters = ['polymorphic' => ['polymorphic_type' => 'Video', 'polymorphic_id' => {TAG_ID}, 'polymorphic_inverse'=true]];

In each of my models that is polymorphic I've added the following: (I'll probably implement a contract and trait later and make that variable private; but its working for now)

namespace App;

class Tag extends Model
{
    public $polymorphicName = 'tagable';
}

This is my Factory class that generates the query.

<?php

namespace App\EloquentFilters;

use Illuminate\Database\Eloquent\Builder;

class PolymorphicFactory
{
    //This is the namespace of the models
    private $modelNamespace = 'App\\';

    //This is the model the filter is being run on.
    protected $filtered = null;

    //This will be "tagable" or "commentable" based on the variable in the above section
    protected $polymorphicName = null;

    //These are passed to use through $value
    protected $id = null;
    protected $type = null;
    protected $inverse = false;
    protected $pivots = [];

    public function __construct(String $filtered, Array $value)
    {
        $this->filtered = $filtered;

        $this->id = $value['polymorphic_id'];
        $this->type = $this->modelNamespace.$value['polymorphic_type'];

        if(key_exists('polymorphic_pivots', $value))
            $this->pivots = $value['polymorphic_pivots'];

        if(key_exists('polymorphic_inverse',$value) && $value['polymorphic_inverse']) {
            $this->inverse = true;
            $this->polymorphicName = strtolower((new $this->type)->polymorphicName);
        }else{
            $this->polymorphicName = strtolower((new $this->filtered)->polymorphicName);
        }
    }

    public function getQuery(Builder $builder)
    {
        return $builder->join($this->polymorphicName.'s', function($join){
            return $this->getSubQuery($join);
        });
    }

    public function getSubQuery($join)
    {
        if(!$this->inverse){
            $join->on('id', '=', $this->polymorphicName.'s.'.$this->polymorphicName.'_id')
                ->where(
                    $this->polymorphicName.'s.'.$this->polymorphicName.'_type',
                    $this->type
                )
                ->where(
                    $this->polymorphicName.'s.'.strtolower((new \ReflectionClass($this->filtered))->getShortName()).'_id',
                    $this->id
                );
        }
        else{
            $join->on('id', '=', $this->polymorphicName.'s.'.strtolower((new \ReflectionClass($this->type))->getShortName()).'_id')
                ->where(
                    $this->polymorphicName.'s.'.$this->polymorphicName.'_type',
                    $this->filtered
                )
                ->where(
                    $this->polymorphicName.'s.'.$this->polymorphicName.'_id',
                    $this->id
                );
        }

        foreach($this->pivots as $col => $value)
        {
            $join->where($this->polymorphicName.'s.'.strtolower($col), $value);
        }

        return $join;
    }
}

And this is the Filter I'm using. (Just have to ensure the model being filtered is added)

<?php

namespace App\EloquentFilters\Video;

use Fouladgar\EloquentBuilder\Support\Foundation\Contracts\Filter;
use Illuminate\Database\Eloquent\Builder;
use App\EloquentFilters\PolymorphicFactory;
use App\Video;

class PolymorphicFilter  extends Filter
{
    public function apply(Builder $builder, $value): Builder
    {
        return (new PolymorphicFactory(Video::class, $value))->getQuery($builder);
    }
}

@mohammad-fouladgar
Copy link
Owner

@sniper7kills thank you for your sharing 👍
I think, strategy and abstract factory pattern can resolve this issue for make the code cleaner.

@sniper7kills
Copy link
Author

You were right; I've updated my previous comment with my updated code.
It still probably doesn't fit best practices; but its a vast improvement over the switch // if/else I was using before.

@mohammad-fouladgar
Copy link
Owner

Well done. Keep going on 👍✨

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants