-
Notifications
You must be signed in to change notification settings - Fork 11.3k
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
[12.x] Add resource helper functions to Model/Collections #55107
Conversation
thanks for pr, but I don't think the difference between Also, no test has been written for them. |
I agree that it is not a big difference at a first glance, but especially when queries become quite long it is a significant upgrade in terms of readability. At the moment we are forced to introduce an intermediate variable or nest long query builder calls. This can get messy real fast, especially when being used in conjunction with defered/optional props in Inertia.js that require a function. // Messy nesting required for shorthand function
$modules = fn () => ModuleResource::collection(Module::query()
->withDefaultRelations()
// potentially many more calls
->withCommonSearch($search)
->simplePaginate(40, page: $search->page));
// or forced to use normal function
$modules = function () use ($search) {
$models = Module::query()
->withDefaultRelations()
// potentially many more calls
->withCommonSearch($search)
->simplePaginate(40, page: $search->page);
return ModuleResource::collection($modules);
}
// arguably easier to write and more readable
$modules = fn () => Module::query()
->withDefaultRelations()
->withCommonSearch($search)
// potentially many more calls
->simplePaginate(40, page: $search->page)
->toResourceCollection(ModuleResource::class); I'm happy to write the necessary tests for it too if the discussion about adding this feature is still open. Also this is a pure enhancement, the "old way" of creating resources would still be valid. |
@TimKunze96 I kinda dig this. I wonder if we could add some auto-discovery to it based on conventions. 👀 So that you could just do |
I added a basic implementation of this. I think there is technically an edge case bug in there: Since the Paginator Instance does not "remember" what model it got generated by (as far as I could tell) I have to infer the model type via the items, but if the item list is empty it would just run fine, even if the related resource does not exist. So to make it absolutely bulletproof the paginator would need to remember the calling class. |
|
||
$model = $this->first(); | ||
|
||
assert(is_object($model), 'Resource collection guesser expects the collection to contain objects.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using assertions in production code is risky as they may be disabled in production environments (zend.assertions=-1). When assertions fail in this state, no proper errors are thrown, leading to silent failures or unexpected behavior. Better to use explicit exceptions with meaningful error messages for reliable error handling.
https://www.php.net/manual/en/ini.core.php#ini.zend.assertions
see : \Illuminate\Database\Eloquent\Collection::toQuery
I changed the code to explicitly throw a The tests currently use class aliases to simulate the existence of the corresponding resource classes, although there may be a better approach. |
* | ||
* @return class-string<JsonResource> | ||
*/ | ||
protected function guessResourceName(): string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a lot of duplications, maybe we can avoid the duplications?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah you are right. I refactored the code to use traits instead.
I refactored to code to make use of traits. It also gets applied to the base collection class instead of the eloquent collection class, since there are many cases were users mutate query results and end up with regular collections instead of eloquent collections. |
For custom collections it might also be convenient to define the collections resource class on the collection class via
|
throw_unless(class_exists($resourceClass), \LogicException::class, sprintf('Failed to find resource class for model [%s].', $className)); | ||
throw_unless( | ||
class_exists($resourceClass = static::guessResourceName()), | ||
LogicException::class, sprintf('Failed to find resource class for model [%s].', get_class($this)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd put each argument on a separate line for consistency
LogicException::class, sprintf('Failed to find resource class for model [%s].', get_class($this)) | |
LogicException::class, | |
sprintf('Failed to find resource class for model [%s].', get_class($this)) |
throw_unless( | ||
class_exists($resourceClass = static::guessResourceName()), | ||
LogicException::class, sprintf('Failed to find resource class for model [%s].', get_class($this)) | ||
); | ||
|
||
return $resourceClass::make($this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might be a decent use case for tap()
:
throw_unless( | |
class_exists($resourceClass = static::guessResourceName()), | |
LogicException::class, sprintf('Failed to find resource class for model [%s].', get_class($this)) | |
); | |
return $resourceClass::make($this); | |
$resourceClass = tap(static::guessResourceName(), fn (string $resourceClass) => throw_unless( | |
class_exists($resourceClass), | |
LogicException::class, sprintf('Failed to find resource class for model [%s].', get_class($this)) | |
)); | |
return $resourceClass::make($this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be nice if one could do something like this:
$resourceClass = throw_unless(
static::guessResourceName(),
class_exists(...),
LogicException::class, sprintf('Failed to find resource class for model [%s].', get_class($this))
);
return $resourceClass::make($this);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or if there was a global when()
helper 🤔
$resourceClass = when(
static::guessResourceName(),
class_exists(...),
fn () => throw new LogicException(sprintf('Failed to find resource class for model [%s].', get_class($this))),
);
return $resourceClass::make($this);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or a Stringable
helper 🤔
throw_unless( | |
class_exists($resourceClass = static::guessResourceName()), | |
LogicException::class, sprintf('Failed to find resource class for model [%s].', get_class($this)) | |
); | |
return $resourceClass::make($this); | |
$resourceClass = Str::of(static::guessResourceName()) | |
->when( | |
class_exists(...), | |
fn () => throw new LogicException(sprintf('Failed to find resource class for model [%s].', get_class($this))), | |
) | |
->value(); | |
return $resourceClass::make($this); |
Thanks! |
@taylorotwell as outlined on issue #55272, this PR added a missing dependency to As such, projects using only the I am not sure if this could be considered a breaking change due to this. |
Avoid regression issue introduced in #55107 fixes #55272 Signed-off-by: Mior Muhammad Zaki <[email protected]>
Avoid regression issue introduced in #55107 fixes #55272 Signed-off-by: Mior Muhammad Zaki <[email protected]>
* [12.x] Fix `illuminate/database` usage as standalone package Avoid regression issue introduced in #55107 fixes #55272 Signed-off-by: Mior Muhammad Zaki <[email protected]> * Update composer.json * Update composer.json --------- Signed-off-by: Mior Muhammad Zaki <[email protected]> Co-authored-by: Taylor Otwell <[email protected]>
I tried doing something very similar in 2020... #35515 |
This PR adds a couple of helper functions that will make generating resource instances a lot more fluent.
Currently we need to do this:
After change: