Laravel 7 - Pagination on Collection - laravel

I need to get my data with pagination when I use collection.
Couldn't find any way, and nothing works that written on documents.
Here's my controller;
...
$data = $process->paginate(30);
$data = OrderResource::collection($data);
And here's my resource:
<?php
namespace App\Http\Resources;
use Carbon\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Resources\Json\JsonResource;
class OrderResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
$user = Auth::user();
return [
"id" => $this->id,
"customer" => $this->customer,
"vehicle" => $this->vehicle,
"basket" => $this->basket,
"total" => money_formatter($this->total),
"discount" => $this->discount,
"net_total" => money_formatter($this->net_total),
"status" => $this->status,
"payment_type" => $this->payment_type,
"main_name" => $this->vehicle->fleet_id ? $this->vehicle->fleet->title : ($this->customer->company_id ? $this->customer->company->title : $this->customer->fullname),
"sub_name" => $this->vehicle->fleet_id ? ($this->customer->company_id ? $this->customer->company->title : $this->customer->fullname) : '',
"created_at" => Carbon::parse($this->created_at)->formatLocalized('%a, %d %B %Y'),
];
}
}

You can add a macro inside your AppServiceProvider.php for this, inside the boot method.
/**
* Paginate a standard Laravel Collection.
*
* #param int $perPage
* #param int $total
* #param int $page
* #param string $pageName
* #return array
*/
Collection::macro('paginate', function ($perPage = 15, $total = null, $page = null, $pageName = 'page') {
$page = $page ?: LengthAwarePaginator::resolveCurrentPage($pageName);
return new LengthAwarePaginator(
$this->forPage($page, $perPage),
$total ?: $this->count(),
$perPage,
$page,
[
'path' => LengthAwarePaginator::resolveCurrentPath(),
'pageName' => $pageName,
]
);
});
And then you can use it like this (assuming $data is a regular laravel collection)
$data = $data->paginate(50);
dd($data);

You can't add any metadata (pagination links) with the collection method. First create a ResourceCollection with php artisan make:resource -c OrderCollection.
Then, in that newly created file, you can do the following.
class OrderCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
$collection = [
'data' => OrderResource::collection($this->collection)
];
if ($this->resource instanceof \Illuminate\Pagination\LengthAwarePaginator) {
$collection['pagination'] = [
'current_page' => $this->resource->currentPage(),
'last_page' => $this->resource->lastPage(),
'first_page_url' => $this->resource->url(1),
'last_page_url' => $this->resource->url($this->resource->lastPage()),
'prev_page_url' => $this->resource->previousPageUrl(),
'next_page_url' => $this->resource->nextPageUrl(),
'from' => $this->resource->firstItem(),
'to' => $this->resource->lastItem(),
'total' => $this->resource->total(),
'per_page' => $this->resource->perPage(),
'path' => $this->resource->path(),
];
}
return $collection;
}
}
dd(json_encode(new OrderCollection(Order::paginate(3)), JSON_PRETTY_PRINT));

Related

CakePHP 4 - Validate password when not empty

While editing a user in CakePHP 4, I want to validate the fields 'password' and 'password_check' only when the 'password' field is not empty.
When 'password' is not empty, those validation rules should be active:
'password' should count at least 8 characters.
'password' should count at most 60 characters.
'password_check' should be required.
'password_check' should count at least 8 characters.
'password_check' should count at most 60 characters.
'password_check should be identical to 'password'.
In UsersController.php, I tried to remove the 'password' value out of the request data so it's not validated when the entity is patched, but apperently that's not possible:
if (empty($this->request->getData('password'))) {
unset($this->request->getData('password')); // error: can't use method return value in write context
unset($this->request->getData('password_check')); // error: can't use method return value in write context
};
$user = $this->Users->patchEntity($user, $this->request->getData()); // validator is called here
Can somebody guide me the way so my preferred validation happens in an efficient way?
Thanks a lot!
Oh yes, here's my UsersTable.php code:
<?php
declare(strict_types=1);
namespace App\Model\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
/**
* Users Model
*
* #property \App\Model\Table\LanguagesTable&\Cake\ORM\Association\BelongsTo $Languages
* #property \App\Model\Table\RolesTable&\Cake\ORM\Association\BelongsTo $Roles
* #property \App\Model\Table\ArticleCategoriesTable&\Cake\ORM\Association\HasMany $ArticleCategories
* #property \App\Model\Table\ArticleImagesTable&\Cake\ORM\Association\HasMany $ArticleImages
* #property \App\Model\Table\ArticleTagsTable&\Cake\ORM\Association\HasMany $ArticleTags
* #property \App\Model\Table\ArticlesTable&\Cake\ORM\Association\HasMany $Articles
* #property \App\Model\Table\CategoriesTable&\Cake\ORM\Association\HasMany $Categories
* #property \App\Model\Table\LinksTable&\Cake\ORM\Association\HasMany $Links
* #property \App\Model\Table\MenusTable&\Cake\ORM\Association\HasMany $Menus
* #property \App\Model\Table\ModulePartsTable&\Cake\ORM\Association\HasMany $ModuleParts
* #property \App\Model\Table\ModulesTable&\Cake\ORM\Association\HasMany $Modules
* #property \App\Model\Table\PagesTable&\Cake\ORM\Association\HasMany $Pages
* #property \App\Model\Table\PartsTable&\Cake\ORM\Association\HasMany $Parts
* #property \App\Model\Table\TagsTable&\Cake\ORM\Association\HasMany $Tags
*
* #method \App\Model\Entity\User newEmptyEntity()
* #method \App\Model\Entity\User newEntity(array $data, array $options = [])
* #method \App\Model\Entity\User[] newEntities(array $data, array $options = [])
* #method \App\Model\Entity\User get($primaryKey, $options = [])
* #method \App\Model\Entity\User findOrCreate($search, ?callable $callback = null, $options = [])
* #method \App\Model\Entity\User patchEntity(\Cake\Datasource\EntityInterface $entity, array $data, array $options = [])
* #method \App\Model\Entity\User[] patchEntities(iterable $entities, array $data, array $options = [])
* #method \App\Model\Entity\User|false save(\Cake\Datasource\EntityInterface $entity, $options = [])
* #method \App\Model\Entity\User saveOrFail(\Cake\Datasource\EntityInterface $entity, $options = [])
* #method \App\Model\Entity\User[]|\Cake\Datasource\ResultSetInterface|false saveMany(iterable $entities, $options = [])
* #method \App\Model\Entity\User[]|\Cake\Datasource\ResultSetInterface saveManyOrFail(iterable $entities, $options = [])
* #method \App\Model\Entity\User[]|\Cake\Datasource\ResultSetInterface|false deleteMany(iterable $entities, $options = [])
* #method \App\Model\Entity\User[]|\Cake\Datasource\ResultSetInterface deleteManyOrFail(iterable $entities, $options = [])
*
* #mixin \Cake\ORM\Behavior\TimestampBehavior
*/
class UsersTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('users');
$this->setDisplayField('username');
$this->setPrimaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsTo('Languages', [
'foreignKey' => 'language_id',
'joinType' => 'INNER',
]);
$this->belongsTo('Roles', [
'foreignKey' => 'role_id',
'joinType' => 'INNER',
]);
$this->hasMany('ArticleCategories', [
'foreignKey' => 'user_id',
]);
$this->hasMany('ArticleImages', [
'foreignKey' => 'user_id',
]);
$this->hasMany('ArticleTags', [
'foreignKey' => 'user_id',
]);
$this->hasMany('Articles', [
'foreignKey' => 'user_id',
]);
$this->hasMany('Categories', [
'foreignKey' => 'user_id',
]);
$this->hasMany('Links', [
'foreignKey' => 'user_id',
]);
$this->hasMany('Menus', [
'foreignKey' => 'user_id',
]);
$this->hasMany('ModuleParts', [
'foreignKey' => 'user_id',
]);
$this->hasMany('Modules', [
'foreignKey' => 'user_id',
]);
$this->hasMany('Pages', [
'foreignKey' => 'user_id',
]);
$this->hasMany('Parts', [
'foreignKey' => 'user_id',
]);
$this->hasMany('Tags', [
'foreignKey' => 'user_id',
]);
}
/**
* Default validation rules.
*
* #param \Cake\Validation\Validator $validator Validator instance.
* #return \Cake\Validation\Validator
*/
public function validationDefault(Validator $validator): Validator
{
$validator
->scalar('first_name', __('Valid first_name is required.'))
->maxLength('first_name', 30, __('First name should count at most 30 characters.'))
->requirePresence('first_name', 'create')
->notEmptyString('first_name', __('First name is required.'));
$validator
->scalar('last_name', __('Valid last name is required.'))
->maxLength('last_name', 30, __('Last name should count at most 30 characters.'))
->requirePresence('last_name', 'create')
->notEmptyString('last_name', __('Last name is required.'));
$validator
->scalar('username', __('Valid username is required.'))
->maxLength('username', 30, __('Username should count at most 30 characters.'))
->requirePresence('username', 'create')
->notEmptyString('username', __('Username is required.'));
$validator
->email('email', true, __('Valid email is required.'))
->requirePresence('email', 'create')
->notEmptyString('email', __('Email is required.'));
$validator
->scalar('password', __('Valid password is required.'))
->minLength('password', 8, __('Password should count at least 8 characters.'))
->maxLength('password', 60, __('Password should count at most 60 characters.'))
->requirePresence('password', 'create')
->notEmptyString('password', __('Password is required.'));
$validator
->scalar('password_check', __('Password check is required.'))
->minLength('password_check', 8, __('Password check should count at least 8 characters.'))
->maxLength('password_check', 60, __('Password check should count at most 60 characters.'))
->requirePresence('password_check', 'create')
->notEmptyString('password_check', __('Password check is required.'))
->sameAs('password_check', 'password', __('Password check and password should be identical.'));
$validator
->integer('role_id', __('Valid role is required.'))
->notEmptyString('role_id', __('Role is required.'));
$validator
->boolean('is_active', __('Valid is active is required.'))
->notEmptyString('is_active', __('Is active is required.'));
$validator
->integer('language_id', __('Valid language is required.'))
->notEmptyString('language_id', __('Language is required.'));
return $validator;
}
/**
* Returns a rules checker object that will be used for validating
* application integrity.
*
* #param \Cake\ORM\RulesChecker $rules The rules object to be modified.
* #return \Cake\ORM\RulesChecker
*/
public function buildRules(RulesChecker $rules): RulesChecker
{
$rules->add($rules->isUnique(['username']), ['errorField' => 'username']);
$rules->add($rules->isUnique(['email']), ['errorField' => 'email']);
$rules->add($rules->existsIn('role_id', 'Roles'), ['errorField' => 'role_id']);
$rules->add($rules->existsIn('language_id', 'Languages'), ['errorField' => 'language_id']);
return $rules;
}
}
There are exceptions, but usually when you feel the need to modify the data in the actual request object, you're most likely doing something wrong.
The intended way to modify (request) data before marshalling when creating/patching entities, is the beforeMarshal event/callback.
// in `UsersTable` class
public function beforeMarshal(
\Cake\Event\EventInterface $event,
\ArrayAccess $data,
\ArrayObject $options
): void {
if (empty($data['password'])) {
unset($data['password']);
}
if (empty($data['password_check'])) {
unset($data['password_check']);
}
}
See also
Cookbook > Database Access & ORM > Saving Data > Modifying Request Data Before Building Entities

Laravel api resources and LengthAwarePaginator issue

I'm making an API using Eloquent: API Resources.
This is my article Resource:
class Article extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'slug' => $this->slug,
'name' => $this->name,
'comments' => $this->when($this->showComments(), function () {
$comments = config('eblogger.models.comment')::where([
'commentable_type' => get_class($this),
'commentable_id' => $this->id,
'parent_id' => 0,
])->orderBy('created_at', 'desc')->get();
$paginator = makePaginationCollection($comments, route('blog.comments'));
return CommentResource::collection($paginator);
}),
];
}
}
It's an article with comments.
I want to get pagination with my comments, so i call a custom helper
function makePaginationCollection($collection, $path)
{
$request = request();
$page = request('page', 1);
$perPage = config('settings.items_by_pages');
$paginate = new \Illuminate\Pagination\LengthAwarePaginator(
$collection->forPage($page, $perPage),
$collection->count(),
$perPage,
$page,
['path' => $path]
);
return $paginate;
}
Update : this my resource collection
class CommentCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'data' => $this->collection,
];
}
}
but when i inspect the response in the devtools, i see
I tried with CommentCollection class without success.
Do you have an idea?
Thanks
My solution
finally, thanks to #Rwd, i found this solution but I think it is possible to do better:
This is my article Resource:
public function toArray($request)
{
return [
// ...
'comments' => $this->when($this->showComments(), function() {
return new CommentCollection(
$this->comments()
->orderBy('created_at', 'desc')
->paginate(config('settings.items_by_pages'))
->withPath(route('blog.comments'))
);
})
];
}
And this is my resource collection :
public function toArray($request)
{
return [
'data' => $this->collection,
'links' => $this->resource,
'meta' => $this->resource
];
}

"Trying to get property of non-object" Laravel

i have some problem...
here is my code..
i can't get what i swrong with my code.....
here is the error
here is my user class
this is the full DashboardController
/**
* '/home' calls this route
*
* #param none
* #return view dashboard
*/
public function index()
{
$this->permission();
$data = [
'pagetitle' => 'Dashboard',
'permission' => Session()->get('permission'),
'name' => Auth::user()->name,
];
return view('dashboard',$data);
}
/**
* Checks if session has permission in it if not they adds to it
*
* #param null
* #return null
*/
private function permission()
{
if (!Session()->has('permission')) {
$permission = User::find(Auth::user()->id)->details;
$permission_arr = [
'department' => $permission->permission_department,
'asset' => $permission->permission_asset,
'users' => $permission->permission_users,
];
Session()->put('permission', $permission_arr);
}
}
}
i have no idea how solve it..
any help would be great..
You get this kind of problem for you are getting only access of details column From your User table . Remove the details from $permission = User::find(Auth::user()->id);
private function permission(){
if (!Session()->has('permission')){
$permission = User::find(Auth::user()->id);
$permission_arr = [
'department' => $permission->permission_department,
'asset' => $permission->permission_asset,
'users' => $permission->permission_users,
];
Session()->put('permission', $permission_arr);
}
}
Note I have only remove the details object from your permission variable

Laravel Resource

I have multiple resources and mostly resource content few fields that are same for all other resource and it's very difficult to modify all the resource in case I need to update/add key/value in the resource.
Is there any way that I can create one main resource that will contain all common fields and then call the main resource in my another resource and add few additional fields.
Here is my controller where I am calling CitizenFeedResource file.
if ($events->total()) {
return CitizenFeedResource::collection($events);
}
This is my CitizenFeedResource file.
use Illuminate\Http\Resources\Json\JsonResource;
class CitizenFeedResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'description' => $this->description,
'start_timestamp' => optional($this->start_timestamp)->toDateTimeString(),
'end_timestamp' => optional($this->end_timestamp)->toDateTimeString(),
'location' => [
'name' => $this->location,
'landmark' => $this->landmark,
'coordinates' => $this->coordinates,
'city' => $this->city,
],
'open_event' => $this->open_event,
'full_day_event' => $this->full_day_event,
'banner' => $this->banner,
'url' => $this->path(),
'web_url' => $this->webUrl(),
'categories' => $this->categories,
'timestamp' => $this->created_at->toDateTimeString(),
'timestamp_ago' => $this->created_at->diffForHumans(),
'statistics' => $this->statistics,
'additional_details' => $this->additionalDetails(),
'municipal_details' => $this->municipal_details,
'user' => optional($this->user)->getProfile($this->channel, '1.1'),
'complaint_id' => $this->complaint_id,
'volunteers' => (isset($this->volunteers) && $this->volunteers) ? $this->user->getVolunteerProfile($this->volunteers, '1.1') : array(),
'share_count' => (isset($this->statistics) && isset($this->statistics['share_count'])) ? array_sum($this->statistics['share_count']) : 0,
'volunteer_status' => $this->getVolunteerStatus($request),
'editable' => $this->editable(),
'type' => 'Event',
];
}
}
You don't have to extend directly from JsonResponse, so you can create one main object let's say like this:
use Illuminate\Http\Resources\Json\JsonResource;
class BaseResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'description' => $this->description,
];
}
}
and then
class CitizenFeedResource extends BaseResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
$data = parent::toArray($request);
$data['custom_field'] = $this->custom_field;
// ...
return $data;
}
}

How to build rule exist in or equal to a number in cakephp 3?

I have table comments with column parent_id.
And this is content of CommentsTable.php:
namespace App\Model\Table;
use App\Model\Entity\Comment;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
/**
* Comments Model
*/
class CommentsTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
$this->table('comments');
$this->displayField('id');
$this->primaryKey('id');
$this->belongsTo('Users', [
'foreignKey' => 'user_id',
'joinType' => 'INNER'
]);
$this->belongsTo('Posts', [
'foreignKey' => 'post_id',
'joinType' => 'INNER'
]);
$this->belongsTo('ParentComments', [
'className' => 'Comments',
'foreignKey' => 'parent_id'
]);
$this->hasMany('ChildComments', [
'className' => 'Comments',
'foreignKey' => 'parent_id'
]);
}
/**
* Default validation rules.
*
* #param \Cake\Validation\Validator $validator Validator instance.
* #return \Cake\Validation\Validator
*/
public function validationDefault(Validator $validator)
{
$validator
->add('id', 'valid', ['rule' => 'numeric'])
->allowEmpty('id', 'create')
->requirePresence('body', 'create')
->notEmpty('body')
->requirePresence('path', 'create')
->notEmpty('path')
->add('status', 'valid', ['rule' => 'numeric'])
->requirePresence('status', 'create')
->notEmpty('status')
->add('created_at', 'valid', ['rule' => 'datetime'])
->requirePresence('created_at', 'create')
->notEmpty('created_at')
->add('updated_at', 'valid', ['rule' => 'datetime'])
->requirePresence('updated_at', 'create')
->notEmpty('updated_at');
return $validator;
}
/**
* Returns a rules checker object that will be used for validating
* application integrity.
*
* #param \Cake\ORM\RulesChecker $rules The rules object to be modified.
* #return \Cake\ORM\RulesChecker
*/
public function buildRules(RulesChecker $rules)
{
$rules->add($rules->existsIn(['user_id'], 'Users'));
$rules->add($rules->existsIn(['post_id'], 'Posts'));
$rules->add($rules->existsIn(['parent_id'], 'ParentComments'));
return $rules;
}
}
I want to build rule for field parent_id: exist in ParentComments or equal to 0.
Can you help me?
Thank you very much.
Rules are just callable functions or callable classes. The existsIn() function is just an alias for the ExistsIn class. We can use the to our advantage:
...
use Cake\ORM\Rule\ExistsIn;
class CommentsTable extends Table
{
...
public function buildRules(RulesChecker $rules)
{
...
$rules->add(
function ($entity, $options) {
$rule = new ExistsIn(['parent_id'], 'ParentComments');
return $entity->parent_id === 1 || $rule($entity, $options);
},
['errorField' => 'parent_id', 'message' => 'Wrong Parent']
);
return $rules;
}
}

Resources