laravel / nova-issues

554 stars 34 forks source link

`relatableQuery` not passing `viaRelationship` and `relationshipId` for BelongsTo relationships. #1910

Closed mikebronner closed 5 years ago

mikebronner commented 5 years ago

Description

As described in many other issues, (specifically https://github.com/laravel/nova-issues/issues/1442) I am also unable to access viaRelationship and viaRelationshipId in a relatable query on a BelongsTo relationship when creating a new record.

Steps To Reproduce

The following is the nova resource that exhibits the issue:

<?php namespace App\Nova;

use App\Blog;
use App\Category;
use App\Nova\Filters\CategoryFilter;
use Genealabs\CustomResourceToolbar\CustomResourceToolbar;
use GeneaLabs\NovaGutenberg\Gutenberg;
use Illuminate\Http\Request;
use Laravel\Nova\Fields\DateTime;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\Text;
use Laravel\Nova\Fields\BelongsTo;
use Laravel\Nova\Fields\Select;
use Orlyapps\NovaBelongsToDepend\NovaBelongsToDepend;
use Laravel\Nova\Http\Requests\NovaRequest;

class Post extends Resource
{
    public static $model = 'App\Post';
    public static $title = 'title';
    public static $search = [
        'id',
        'title',
    ];

    public static function availableForNavigation(Request $request) : bool
    {
        return false;
    }

    public function fields(Request $request) : array
    {
        return [
            ID::make()
                ->sortable(),
            Text::make('Title')
                ->sortable()
                ->rules('required', 'max:255'),
            DateTime::make("Published At"),
            $this->getOwnerRelationshipField("Author"),
            $this->getOwnerIndexField("Author"),
            BelongsTo::make("Category")
                ->nullable()
                ->prepopulate()
                ->searchable()
                ->hideFromIndex(),
            Gutenberg::make("Content"),
            BelongsTo::make("Blog", "blog", "App\Nova\Blog")
                ->searchable(),
            DateTime::make("Created", "created_at")
                ->onlyOnDetail(),
            DateTime::make("Last Updated", "updated_at")
                ->onlyOnDetail(),
            CustomResourceToolbar::make(),
        ];
    }

    public function filters(Request $request) : array
    {
        return [
            new CategoryFilter,
        ];
    }

    public static function relatableCategories(NovaRequest $request, $query)
    {
        // TODO: figure out why these aren't resolving when creating a new resource.
        // $request->viaResource
        // $request->viaResourceId

        return $query;
            // ->where("parent_id", $request->viaResourceId)
            // ->where("parent_type", Blog::class);
    }
}
jayjfletcher commented 5 years ago

I'm able to get them using

$request->query('viaResource'); $request->query('viaResourceId');

mikebronner commented 5 years ago

@jayjfletcher Thanks for the tip! I will try this out.

Update: When creating a resource via another resource, the only things in $request->query() are:

  array (
    'first' => 'false',
    'search' => NULL,
    'withTrashed' => 'false',
  )

I've inspected both $request and $query, and don't see any of the via... variables anywhere in this use-case.

Looking at the AJAX request in the browser, the viaResource and viaResourceId values are not being passed with the AJAX request for the drop-down.

jbrooksuk commented 5 years ago

I've had a 💡 moment!

Since the resource and resourceId are passed through to a route, you can access these with:

$request->route('resource') = "posts"
$request->route('resourceId') = 1
jbrooksuk commented 5 years ago

P.s. I shall add some docs.

davidhemphill commented 5 years ago

Since the viaResource, viaResourceId, and viaRelationship come in as props to the field, it should be simple to pass those along to the method that fetches the available resources. For the time being, @jbrooksuk's solution will work.

mziraki commented 5 years ago

Since the viaResource, viaResourceId, and viaRelationship come in as props to the field, it should be simple to pass those along to the method that fetches the available resources. For the time being, @jbrooksuk's solution will work.

is this possible now? or can be added to the next release? the viaResource, viaResourceId, and viaRelationship are necessary to create/edit HasMany relations with BelongsTo fields when BelongsTo depends on the viaResource and viaResourceId.

jbrooksuk commented 5 years ago

It’s not possible with the query parameters, but you can use the workaround I posted above.

I’ll re-open this and add the ability to use the query parameters for added clarity soon.

jbrooksuk commented 5 years ago

PR created.

pfried commented 5 years ago

Will this also be true for MorphTo Relationships?

If I am creating a new belongsTo Entity which has a morphTo property I am also not getting any viaResource or viaResourceId which I would need to filter in the relatableQuery of the morphable.

jbrooksuk commented 5 years ago

@pfried I've just added the parameters to the MorphToField component too.

Gummby commented 4 years ago

Not sure if this is a bug as my setup is a little different. But relatableQuery will still not send viaResource and viaResouceId to a belongsTo field on a create view of a belongsToMany relationship. The initial request receives it, but then when the belongsTo search runs it doesn't send these props as its now sending a different request. I am on Nova Version: 2.9.3

weogrim commented 4 years ago

It's weird, but if I not mistaken, relatableQueery doesn't give you any easy way to check what relationship you're dealing with.

I work with tags that have types. I use two MorphToMany fields in the Resource (Categories, Genres), so I need to be able to divide them by the relationship type categories(), genres()) on the attach page.

Both $request->get('viaRelationship') and $request->viaRelationship return null. I examined the entire $request variable and I only found references to my relationship as HTTP_REFERER.

So, If someone is looking for a solution to a problem, you can take HTTP_REFERER by $request->headers->get('referer') which return smth like https://{domain}/nova/resources/{resource}/{id}/attach/{model}?viaRelationship={relationship_name}&polymorphic=1, check if the string contain the name of the relationship and then fire the query condition.

Am I missing something? Is there a simpler solution?

trippo commented 4 years ago

@weogrim Like documentation you can use

$resource = $request->route('resource'); // The resource type...
$resourceId = $request->route('resourceId'); // The resource ID...
trippo commented 4 years ago

@davidhemphill Please also add on documentation the field

$field = $request->route('field'); //The field calling this resource...
javoscript commented 3 years ago

It's weird, but if I not mistaken, relatableQueery doesn't give you any easy way to check what relationship you're dealing with.

I work with tags that have types. I use two MorphToMany fields in the Resource (Categories, Genres), so I need to be able to divide them by the relationship type categories(), genres()) on the attach page.

Both $request->get('viaRelationship') and $request->viaRelationship return null. I examined the entire $request variable and I only found references to my relationship as HTTP_REFERER.

So, If someone is looking for a solution to a problem, you can take HTTP_REFERER by $request->headers->get('referer') which return smth like https://{domain}/nova/resources/{resource}/{id}/attach/{model}?viaRelationship={relationship_name}&polymorphic=1, check if the string contain the name of the relationship and then fire the query condition.

Am I missing something? Is there a simpler solution?

Ended up using the referer header trick. I made a little helper function to extract the route parameters from the referer URL with some Symfony routing components that are already installed in Laravel (as Laravel uses them internally for routing).

In my Nova resource:

    protected static function matchAgainstRefererRoute($request, $route)
    {
        $referer = $request->headers->get('referer');
        if (! $referer) {
            return [];
        }

        $req = \Symfony\Component\HttpFoundation\Request::create($referer, $route);
        $routes = new \Symfony\Component\Routing\RouteCollection();
        $routes->add(
            'referer',
            new \Symfony\Component\Routing\Route($route)
        );
        $context = new \Symfony\Component\Routing\RequestContext();
        $context->fromRequest($req);
        $matcher = new \Symfony\Component\Routing\Matcher\UrlMatcher($routes, $context);
        try {
            return $matcher->match($req->getPathInfo());
        } catch(\Symfony\Component\Routing\Exception\ResourceNotFoundException $e) {
            return [];
        }
    }

And then, in the relatableQuery function (relatableMenus in this example), I just send the NovaRequest object and the route I want to match against, and it extracts the parameters:

    public static function relatableMenus(NovaRequest $request, $query)
    {
        $params = self::matchAgainstRefererRoute(
            $request,
            config('nova.path') . '/resources/{resourceName}/{resourceId}/edit'
        );

        if (isset($params['resourceName']) && isset($params['resourceId']) && $params['resourceName'] === 'qrs') {
            $qr = \App\Models\Qr::find($params['resourceId']);
            if ($qr->subsidiary_id) {
                return $query->whereSubsidiaryId($qr->subsidiary_id);
            }
        }
        return $query;
    }
javoscript commented 3 years ago

@weogrim Like documentation you can use

$resource = $request->route('resource'); // The resource type...
$resourceId = $request->route('resourceId'); // The resource ID...

The issue is that when calling the relatableQuery function (or the dynamic relatable version) for a polymorphic relationship, those values are not present in the route. An example request path:

/nova-api/qrs/morphable/qrable?type=menus&current=&first=false&search=&withTrashed=false&viaResource=&viaResourceId=&viaRelationship=

The viaResource, viaResourceId and viaRelationship are all just empty

github-actions[bot] commented 3 years ago

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.