I have seen two ways of paginations I would like to know the differences between them. Or if there is a strict way to use either of them. Please don't mind the type of data used in the example.
First one
public function rules()
{
return [
'query_value' => 'string',
'status' => ['string', Rule::in(BaseModel::STATUS_CODES)],
'pagination' => 'integer',
];
}
public function paginationResult()
{
return !is_null($this->get('pagination')) && $this->get('pagination') > 0 ? $this->get('pagination') : 10;
}
in a controller
$this->itemsPerPage = $filterRequest->paginationResult();
Second one
$users = User::where('votes', '>', 100)->paginate(10);
FormRequest class
class UserRequest extends FormRequest
{
public function rules()
{
return [
'query_value' => 'string',
'status' => ['string', Rule::in(BaseModel::STATUS_CODES)],
'pagination' => 'integer',
];
}
public function paginationValue()
{
return $this->pagination ?: 10;
}
}
Then in controller
public function index(UserRequest $request)
{
$users = User::where('votes', '>', 100)->paginate($request->paginationValue());
//...rest of method code
}
The paginate() method will return an instance of Illuminate\Pagination\LengthAwarePaginator which means that you can display links like 1 2 ......9 10 - numbers show the page numbers for paginated results
Another way to paginate is using simplePaginate() which will return an instance of Illuminate\Pagination\Paginator, which means you can display links as Prev Next
Related
I am trying to make a global search feature on my website, and for that I need to query the database to get the records. The frontend of my application expect the following array:
[
['name' => 'Result #1...', 'url' => 'http://...'],
['name' => 'Result #2...', 'url' => 'http://...'],
['name' => 'Result #3...', 'url' => 'http://...'],
]
To accomplish this, I have added the below in my AppServiceProvider:
public function boot()
{
view()->composer('*', function($view) use ($auth) {
return \View::share('SearchData', (new GlobalSearch($auth->user()->currentTeam))->all());
});
}
The $SearchData is created in the GlobalSearch class:
class GlobalSearch
{
public $data;
public function __construct(public Team $team){
$this->data = $team->properties()->with(['leases', 'leases.tenant', 'leases.files', 'leases.invoices']);
}
protected function propertyData() : array
{
$properties = $this->data->get();
return $properties->map(function ($property) {
$array['name'] = \Str::limit($property->address, 40);
$array['url'] = route('properties.show', ['property' => $property]);
return $array;
})->toArray();
}
public function all() : array
{
return $this->propertyData();
}
}
Now the above code does work - I successfully get an array in the correct mapping. However, in my database I only have 1 property in the properties table - yet, there are being executed 90 duplicate queries for a single page load.
Why is this happening? I can't seem to locate why these queries are being duplicated
You can actually remove the view()->composer('*', function) part. Since View::share() does the exact same.
$searchData = (new GlobalSearch($auth->user()->currentTeam))->all();
View::share('SearchData', $searchData);
Optionally:
You could bind the GlobalSearch class to the container in your AppServiceProvider. Which will make the class available via app(GlobalSearch::class) wherever you want in the application. This means the query will only run once during the initialization process and maintain the data.
More info about singleton bindings: https://laravel.com/docs/8.x/container#binding-a-singleton
$this->app->singleton(GlobalSearch::class, function ($app) {
return new GlobalSearch(auth()->user()->team);
});
I'm trying to use Laravel's resources & Collection to build a small API.
I would like to recover all the posts of the categories
My relation on my model :
/*
|--------------------------------------------------------------------------
| RELATIONS
|--------------------------------------------------------------------------
*/
public function posts()
{
return $this->belongsToMany(Post::class, 'post_category');
}
Category controller :
public function index()
{
$categories = Category::all();
return (new CategoryCollection(CategoryResource::collection($categories)));
}
My categoryResource :
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'slug' => $this->slug
];
}
My CategoryCollection
public function toArray($request)
{
return [
'data' => $this->collection,
'posts' => PostResource::collection($this->whenLoaded('posts')),
];
}
I try to recover the posts of a category first. When I make the following command I get an error: Method ... relationLoaded does not exist
'posts' => PostResource::collection($this->whenLoaded('posts'))
What did I not understand?
I also created two PostCollection and PostResource files (basic, I did not modify them)
public function toArray($request)
{
return parent::toArray($request);
}
public function index()
{
$categories = Category::all();
return (CategoryResource::collection($categories));
}
this is might help , try this
and if you want to use posts resource
you need make posts resources
and inside the CategoryResource
'posts'=>PostResource::collection($this->posts)
cannot use Resource in Collection.
Use this
public function index()
{
$categories = Category::all();
return (new CategoryCollection($categories));
}
Laravel 5.8
PHP 7.4
I want to load the relationships conditionally like
http://127.0.0.1:8000/api/posts
and
http://127.0.0.1:8000/api/posts/1 are my end points now, I want to load comments like
http://127.0.0.1:8000/api/posts/?include=comments and
http://127.0.0.1:8000/api/posts/1/?include=comments
If the query parameter is there, only then it should load comments with posts or it should load only posts/post
I am doing this by referring a blog post
now, RequestQueryFilter
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
class RequestQueryFilter
{
public function attach($resource, Request $request = null)
{
$request = $request ?? request();
return tap($resource, function($resource) use($request) {
$this->getRequestIncludes($request)->each(function($include) use($resource) {
$resource->load($include);
});
});
}
protected function getRequestIncludes(Request $request)
{
// return collect(data_get($request->input(), 'include', [])); //single relationship
return collect(array_map('trim', explode(',', data_get($request->input(), 'include', [])))); //multiple relationships
}
}
and in helper
<?php
if ( ! function_exists('filter') ) {
function filter($attach)
{
return app('filter')->attach($attach);
}
}
?>
in PostController
public funciton show(Request $request, Post $post) {
return new PostResource(filter($post));
}
but when I am trying to retrieve
http://127.0.0.1:8000/api/posts/1/?include=comments getting no comments, with no error in log
A work around will be PostResource
public function toArray($request)
{
// return parent::toArray($request);
$data = [
'id' => $this->id,
'name' => $this->title,
'body' => $this->content,
];
$filter = $request->query->get('include', '');
if($filter){
$data[$filter] = $this->resource->$filter;
}
return $data;
}
I want to load the relationships conditionally like
Lazy Eager Loading using the load() call
The Lazy Eager Loading accomplishes the same end results as with() in Laravel, however, not automatically. For example:
?include=comments
// Get all posts.
$posts = Post::without('comments')->all();
if (request('include') == 'comments')) {
$posts->load('comments');
}
return PostResource::collection($posts);
Alternativelly, you could require the include query string to be an array:
?include[]=comments&include[]=tags
// Validate the names against a set of allowed names beforehand, so there's no error.
$posts = Post::without(request('includes'))->all();
foreach (request('includes') as $include) {
$posts->load($include);
}
return PostResource::collection($posts);
The call without() is only required in case you defined your model to automatically eager load the relationships you want to conditionally load.
With all data filtered in Controller, just make sure to display only loaded relations in your PostResource
public function toArray($request) {
$data = [...];
foreach ($this->relations as $name => $relation)
{
$data[$name] = $relation;
}
return $data;
}
I would create a custom resource for the posts with
php artisan make_resource
command.
E.g. PostResource.
The toArray function of the resource must return the data.
PostResource.php
public function toArray($request){
$data =['title' => $this->resource->title,
'body' => $this->resource->body,
'images' => new ImageCollection($this->whenLoaded('images')),
];
$filter = $request->query->get('filter', '');
if($filter){
$data['comments'] => new CommentCollection($this->resource->comments);
}
return $data;
}
Also, for collections, you need to create a ResourceCollection.
PostResourceCollection.php
class PostResourceCollection extends ResourceCollection
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request
* #return array
*/
public function toArray($request)
{
return [
'data' => $this->collection,
];
}
}
In your controller:
PostsController.php
//show one post
public function show(Post $post, Request $request)
{
/**this response is for API or vue.js if you need to generate view, pass the resource to the view */
return $this->response->json( new PostResource($post));
}
//list of posts
public function index(Request $request)
{
$posts = Post::all();
/**this response is for API or vue.js if you need to generate view, pass the resource to the view */
return $this->response->json( new PostResourceCollection($posts));
}
Partial Solution
It will need a small change in resource class
public function toArray($request)
{
// return parent::toArray($request);
$data = [
'id' => $this->id,
'title' => $this->title,
'body' => $this->body,
'comments' => new CommentCollection($this->whenLoaded('comments')),
'images' => new ImageCollection($this->whenLoaded('images')),
];
return $data;
}
and it will load comments and images if loaded and that depends on the include query parameter, if that is not included, it will not load the relationship.
However,
In post collection
return [
'data' => $this->collection->transform(function($post){
return [
'id' => $post->id,
'title' => $post->title,
'body' => $post->body,
'comments' => new CommentCollection($post->whenLoaded('comments')),
'images' => new ImageCollection($post->whenLoaded('images')),
];
}),
];
will results in
"Call to undefined method App\Models\Customer::whenLoaded()",, if anyone suggests a complete solution, it will be a great help, if I will able to do, it I will update here.
I want to add a sql filter where('comment_id', '=', 1) to php code
$datas = $this->model->ADD HERE->orderBy('created_at', 'DESC')->paginate(15);
Trying to add the string to code take me hours. How to make it?
Here is my code:
CommentResource.php passing the sql filter as string parameter.
<?php
class CommentResource extends BaseResource
{
public function index()
{
$filter = "where('comment_id', '=', 1)";
return parent::index_filter($filter);
}
CommentResource.php
<?php
class BaseResource extends Controller
{
protected function index_filter($filter)
{
$datas = $this->model->ADD HERE->orderBy('created_at', 'DESC')->paginate(15);
return view($this->resourceView.'.index')->with('datas', $datas);
}
}
As I understand you want to use different types of where as filters in your queries. That's why you want to make them dynamic. I would suggest the following solution for your task:
<?php
class CommentResource extends BaseResource
{
public function index()
{
$filter = [ 'operator' => 'where', 'args' => ['comment_id', '=', 1]];
return parent::index_filter($filter);
}
<?php
class BaseResource extends Controller
{
protected function index_filter($filter)
{
$where = $filter['operator'];
$args = $filter['args'];
$datas = $this->model->$where(...$args)->orderBy('created_at', 'DESC')->paginate(15);
return view($this->resourceView.'.index')->with('datas', $datas);
}
}
However, it will work starting from Php5.6+ because of oeprator ...
I am not sure if I got your requirements correctly, but if you rewrite index_filter to accept field and value separately, then you may user a regular where() from laravel:
protected function index_filter($field,$value)
{
$datas = $this->model->where($field,$value)->orderBy('created_at', 'DESC')->paginate(15);
return view($this->resourceView.'.index')->with('datas', $datas);
}
You can find the docs here. In case you really need more flexibility:
protected function index_filter($filter)
{
$datas = $this->model->whereRaw($filter)->orderBy('created_at', 'DESC')->paginate(15);
return view($this->resourceView.'.index')->with('datas', $datas);
}
Have in mind though that this is really dangerous, as you expose the possibility to inject malicious code, it should be definitely properly escaped beforehand.
My latest code works right. I'll post here.
<?php
class CommentResource extends BaseResource
{
public function index()
{
$options = [
'filters'=>[
[ 'operator' => 'where',
'args' => [
[ 'article_id', '=', $article_id ],
[ 'comment_id', '=', $comment_id ],
// add filter args...
],
],
// add filter operators here...
],
'sorts' => [
'column' => $sortColumn, // change sort column...
'order' => $sortOrder, // change sort order...
],
];
return parent::index_filter($options);
}
<?php
class BaseResource extends Controller
{
protected function index_filter($options, $number=15)
{
$result = $this->model;
foreach ($options['filters'] as $filter) {
$operator = $filter['operator'];
$args = $filter['args'];
$result = $result->$operator($args);
}
if ( $options['sorts'] != [] ) {
$column = $options['sorts']['column'];
$order = $options['sorts']['order'];
$result = $result->orderBy($column, $order);
}
return $result->paginate($number);
}
}
The reason I change ...$args to $args is, when 'args' has more than on value, for example,
'args' => [
[ 'article_id', '=', $article_id ],
[ 'comment_id', '=', $comment_id ],
// add filter args...
],
...$args will change 'args' to one array, but $args will remain 'args' as nest array, which is the operator 'where' want.
I try to build a grid view with many-to-many relations. So I need a query for the ActiveDataProvider .
I have a table 'ressource', a table 'type' and between them a table 'historique'.
I have the good relation in my models but I don't know how to create the dataProvider.
In my model Ressource :
public function getHistorique()
{
return $this->hasMany(Historique::className(), ['idType' => 'idType']);
}
public function getType()
{
return $this->hasMany(Type::className(), ['idType' => 'idType'])
->viaTable(Historique::className(), ['idRessource' => 'idRessource']);
}
In my model Historique :
public function getType()
{
return $this->hasOne(Type::className(), ['idType' => 'idType']);
}
public function getRessource()
{
return $this->hasOne(Ressource::className(), ['idRessource' => 'idRessource']);
}
and finally in my model Type :
public function getHistorique()
{
return $this->hasMany(Historique::className(), ['idType' => 'idType']);
}
public function getRessource()
{
return $this->hasMany(Ressource::className(), ['idRessource' => 'idRessource'])
->viaTable(Historique::className(), ['idType' => 'idType']);
}
So in the Controller (in fact my ModelSearch), I want to have ressources with type from the table historique. I don't know what I have to add after
Ressource::find();
I think you use RessourceSearch()->search() method. So inside it you have something like this:
$query = Ressource::find();
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
// Here is list of searchable fields of your model.
$query->andFilterWhere(['like', 'username', $this->username])
->andFilterWhere(['like', 'auth_key', $this->auth_key])
return $dataProvider;
So, basically, you need to add additional Where you your query and force to join relation table. You can do that using joinWith method to join additional relation and andFilterWhere using table.field notation for adding filter parameters. For example:
$query = Ressource::find();
$query->joinWith(['historique', 'type']);
$query->andFilterWhere(['like', 'type.type', $this->type]);
$query->andFilterWhere(['like', 'historique.historique_field', $this->historique_field]);
Also do not forget to add rules for additional filters in your search model. For example above, you should add to your rules() array something like that:
public function rules()
{
return [
// here add attributes rules from Ressource model
[['historique_field', 'type'], 'safe'],
];
}
You can use any additional validation rules for that fields