Reading data from in vue/cli from Laravel ResourceCollection - laravel

In Laravel 6 backend rest api app I use ResourceCollection and Resourcem defintion like :
<?php
namespace App\Http\Resources;
use App\Facades\MyFuncsClass;
use Illuminate\Http\Resources\Json\ResourceCollection;
class TaskCollection extends ResourceCollection
{
public function toArray($task)
{
return [
$this->collection->transform(function($task){
return [
'id' => $task->id,
'name' => $task->name,
'slug' => $task->slug,
...
'events' => !empty($task->events) ? $task->events : [],
'events_count' => !empty($task->events_count) ? $task->events_count : 0,
'created_at' => $task->created_at,
'updated_at' => $task->updated_at,
];
}),
];
}
public function with($task)
{
return [
'meta' => [
'version'=>MyFuncsClass::getAppVersion()
]
];
}
}
I found this decision at
Laravel 5.5 API resources for collections (standalone data)
and it works for me, but I dislike the way I got data on client part, so for listing of data defined in control :
return (new TaskCollection($tasks));
I have to write in vue/cli app :
axios.post(this.apiUrl + '/adminarea/tasks-filter', filters, this.credentialsConfig)
.then((response) => {
this.tasks = response.data.data[0]
this.tasks_total_count = response.data.meta.total
this.tasks_per_page = response.data.meta.per_page
and when I need to get 1 item I define in laravel's control :
return (new TaskCollection([$task])); // I have to wrap it as array
and I have to write in vue/cli app :
axios.get(this.apiUrl + '/adminarea/tasks/' + this.task_id, this.credentialsConfig)
.then((response) => {
// console.log('response::')
// console.log(response)
//
this.taskRow = response.data.data[0][0]
I dislike syntax like data.data[0] and data.data[0][0] and even that works for me
In my app/Providers/AppServiceProvider.php I have lines :
<?php
namespace App\Providers;
use Auth;
use Validator;
use Illuminate\Http\Resources\Json\Resource;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Resource::withoutWrapping(); // looks like that does not work for ResourceCollection!
<?php
namespace App\Providers;
use Auth;
use Validator;
use Illuminate\Http\Resources\Json\Resource;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Resource::withoutWrapping();
...
If there is a way to get rid of data.data[0] and data.data[0][0] in vue/cli part ?
MODIFIED 2:
I created new resource with few columns as app/Http/Resources/Skill.php :
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class Skill extends JsonResource
{
public static $wrap = 'skills';
public function toArray($request)
{
return [
'id' => $request->id,
'name' => $request->name,
'user_id' => $request->user_id,
'user_name' => $request->user_name,
'skill_id' => $request->skill_id,
'skill_name' => $request->skill_name,
'rating' => $request->rating,
'created_at' => $request->created_at,
];
}
}
and app/Http/Resources/SkillCollection.php :
<?php
namespace App\Http\Resources;
use App\Facades\MyFuncsClass;
use App\Http\Resources\Skill;
use Illuminate\Http\Resources\Json\ResourceCollection;
class SkillCollection extends ResourceCollection
{
public static $wrap = 'skills';
public function toArray($request)
{
return $this->collection->transform(function($request){
parent::toArray($request);
});
}
and resulting I have empty "skills":[null,null,null,null] results. What is wrong ?
Thanks!

The first thing you can do is to use destructuring assignment to get the response data from your Axios request:
axios.get(this.apiUrl + '/adminarea/tasks/' + this.task_id, this.credentialsConfig)
.then(({ data }) => {
this.taskRow = data.data[0][0]
So now response.data.data[0][0] is shortened to data.data[0][0].
The second thing you can do is, in the toArray function of your TaskCollection class, to return the transformed collection directly instead of wrapping it in an array:
public function toArray($task)
{
return $this->collection->transform(function($task){
return [
'id' => $task->i
...
];
});
}
Now you don't need to use [0] everywhere. So instead of data.data[0][0], you can access a single task with data.data[0].
To avoid having to use [0] for singular responses, you should return a JsonResource instead of a ResourceCollection. For this you would need to move your code from inside $this->collection->transform to a new JsonResource class. For example:
app/Http/Resources/TaskResource.php:
<?php
namespace App\Http\Resources;
use App\Facades\MyFuncsClass;
use Illuminate\Http\Resources\Json\JsonResource;
class TaskResource extends JsonResource
{
public function toArray($task)
{
return [
'id' => $this->id,
'name' => $this->name,
'slug' => $this->slug,
...
];
}
public function with($task)
{
return [
'meta' => [
'version'=>MyFuncsClass::getAppVersion()
]
];
}
}
Then you can return a single resource with
return new TaskResource($task);
In Javascript you will be able to get a single task with data.data now:
axios.get(this.apiUrl + '/adminarea/tasks/' + this.task_id, this.credentialsConfig)
.then(({ data }) => {
this.taskRow = data.data;
You can refactor your TaskCollection class to implicitly use your new TaskResource class for each task:
<?php
namespace App\Http\Resources;
use App\Facades\MyFuncsClass;
use Illuminate\Http\Resources\Json\ResourceCollection;
class TaskCollection extends ResourceCollection
{
public function toArray($request)
{
return parent::toArray($request);
}
public function with($task)
{
return [
'meta' => [
'version'=>MyFuncsClass::getAppVersion()
]
];
}
}
Like I tried to explain in the comments, you won't be able to get away from having to wrap your results in a key (like 'data') while returning non-null values from the with function. If you don't believe me, temporarily remove your with function and you'll see that you'll be able to access your tasks simply with data (if you have disabled wrapping with Resource::withoutWrapping();).
However, one thing you can do is to customise the "wrap" key. For example, for a collection of tasks you might want it to be tasks and for a single task, just task. You can simply add a $wrap property to your TaskCollection and TaskResource classes:
class TaskCollection extends ResourceCollection
{
public static $wrap = 'tasks';
...
class TaskResource extends JsonResource
{
public static $wrap = 'task';
Now you will be able to access a list of tasks from Javascript with data.tasks and a singular task with data.task:
axios.post(this.apiUrl + '/adminarea/tasks-filter', filters, this.credentialsConfig)
.then(({ data }) => {
this.tasks = data.tasks;
axios.get(this.apiUrl + '/adminarea/tasks/' + this.task_id, this.credentialsConfig)
.then(({ data }) => {
this.taskRow = data.task;

Related

Why my resource do not return all meta data having "with" method?

In lumen 8 app I use resources and reading here
https://laravel.com/docs/8.x/eloquent-resources
I try to use “with” method, as I want to add some meta data to any request and I have no
this meta data in returned data :
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
use App\Models\Page As PageModel;
use App\Http\Resources\User as UserResource;
class Page extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
...
'created_at' => $this->created_at,
];
}
public function with($request)
{
\Log::info( '-1 unction with ::' . print_r( 1, true ) ); // I DO NOT SEE THIS LOGGINHG line
return [
'meta' => [
'version'=>getAppVersion()
]
];
}
}
In the referenced docs resource is declared a bit different from ResourceCollection:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class UserCollection extends ResourceCollection
{
public function toArray($request)
{
return parent::toArray($request);
}
public function with($request)
{
return [
'meta' => [
'key' => 'value',
],
];
}
}
Could it be the issue and how can fix my resource to get all meta data ?
Updated block:
UserCollection - that is collection https://laravel.com/docs/8.x/eloquent-resources
my collection is Page and I use it in controller as :
namespace App\Http\Controllers;
use Carbon\Carbon;
use App\Models\Page;
use Illuminate\Http\Request;
use App\Http\Resources\Page as PageResource;
use Config;
use App\Http\Requests\PageRequest;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
use Validator;
class PageController extends Controller
{
public function index()
{
$pages = Page
...
->get();
return $this->sendOkResponse(PageResource::collection($pages), '');
}
sendOkResponse defined in Http/Controllers/Controller.php :
class Controller extends BaseController
{
protected $requestData;
public function __construct()
{
$request = request();
$this->requestData = $request->all();
}
public function sendOkResponse($responseResult, $message)
{
$response = [
'success' => true,
'data' => $responseResult,
'message' => $message,
];
return response()->json($response, HTTP_RESPONSE_OK);
}
I suppose PageResource is destroyed at PageController controller index method exit...
Updated block # 2:
After some tests I found that Resource method “with” does not work if collection is returned
and I need to use ->additional in controller like:
return (PageResource::collection($pages))
->additional([
'meta' => [
'version' => getAppVersion()
]
]);
But in cases when I return sinopgle element(ex store method) like
return (new PageResource($page));
method “with” works ok.
That exludes using of wrapper like sendOkResponse.
Is is the only proper way?
Thanks in advance!
Laravel resources are intended to be returned directly from your controller's action method, not as part of an associative array representing JSON.
When wrapping your responses with the sendOkResponse method, the resource is not being returned directly from the method and thus toArray is being called on your resource. The with method on your resources is being ignored.
Try returning the resources directly from your controller's method. Use the additional method when constructing your resources to pass the extra attributes in the response. See: https://laravel.com/docs/8.x/eloquent-resources#adding-meta-data-when-constructing-resources.
If you can control the API contracts, I'd recommend changing them to omit success entirely, this can be derived from the HTTP status code.

How can I display my imported Excel data?

Currently working on an Excel import functionality. I want to import the Excel sheet and display its information on another page using a foreach loop. However it seems to be rather hard for some reason. When I die and dump the collection data (used this because import would error) it shows everything correctly. The data in the spreadsheet is where it should be. So I feel that is fine. However I cannot get it inserted into my database for some odd reason. I have a header row so I use the WithHeaderRow functionality.
DataImport class:
<?php
namespace App\Imports;
use App\Data;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Maatwebsite\Excel\Imports\HeadingRowFormatter;
HeadingRowFormatter::default('none');
class DataImport implements ToModel, WithHeadingRow
{
/**
* #param array $row
*
* #return \Illuminate\Database\Eloquent\Model|null
*/
public function model(array $row)
{
return new Data([
'cliëntnummer' => $row['clientnummer'],
'zoeknaam' => $row['zoeknaam'],
'naam' => $row['naam'],
'omschrijving' => $row['omschrijving'],
'plaats' => $row['plaats'],
'week' => $row['week'],
'vennoot' => $row['vennoot'],
'relatiebeheerder' => $row['relatiebeheerder'],
'samensteller' => $row['samensteller'],
'ADVer' => $row['adver'],
'cliëntgroepcode' => $row['clientgroepcode'],
'accountant' => $row['accountant'],
'samenstellen' => $row['samenstellen'],
'ADV jaarwerk' => $row['ADVJaarwerk'],
'periodieke ADV' => $row['periodiekeADV'],
'fiscaliteiten' => $row['fiscaliteiten'],
]);
}
}
Datacontroller.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Imports\DataImport;
use Maatwebsite\Excel\Facades\Excel;
use App\Http\Controllers\Controller;
class DataController extends Controller
{
public function index(){
return view('importeren');
}
public function import(Request $request){
$datas = Excel::toCollection(new DataImport(), $request->file('import_file'));
dd($datas);
return redirect()->route('/home');
}
}
Data Model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Data extends Model
{
protected $fillable = [
'id', 'cliëntnummer', 'zoeknaam', 'naam', 'omschrijving', 'plaats', 'week', 'vennoot', 'relatiebeheerder', 'samensteller', 'ADVer', 'cliëntgroepcode', 'accountant', 'samenstellen', 'ADV jaarwerk', 'periodieke ADV', 'fiscaliteiten',
];
}
Spreadsheet data:
Database table layout:
Datadump results:
If I left anything out feel free to tell me. I hope this is sufficient.
Turns out I forgot to reference my datamodel in the file, after finalizing the code of my controller like below it now works (almost) seamlessly.
<?php
namespace App\Imports;
use App\Data;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Maatwebsite\Excel\Imports\HeadingRowFormatter;
HeadingRowFormatter::default('none');
class DataImport implements ToModel, WithHeadingRow
{
/**
* #param array $row
*
* #return \Illuminate\Database\Eloquent\Model|null
*/
public function model(array $row)
{
return new Data([
'cliëntnummer' => $row['Cliëntnummer'],
'zoeknaam' => $row['Zoeknaam'],
'naam' => $row['Naam'],
'omschrijving' => $row['Omschrijving'],
'plaats' => $row['Plaats'],
'week' => $row['Week'],
'vennoot' => $row['Vennoot'],
'relatiebeheerder' => $row['Relatiebeheerder'],
'samensteller' => $row['Samensteller'],
'ADVer' => $row['ADVer'],
'cliëntgroepcode' => $row['Cliëntgroepcode'],
'accountant' => $row['Accountant'],
'samenstellen' => $row['Samenstellen'],
'ADVJaarwerk' => $row['ADVJaarwerk'],
'PeriodiekeADV' => $row['PeriodiekeADV'],
'fiscaliteiten' => $row['Fiscaliteiten'],
]);
}
}

Laravel 6 - Best way to reuse existing request validators within others request validators

Let's say there are two Laravel API resources Book and Author, each with some rules for validating the store request.
Then, there's a third API resource where API consumers can post a certain thing along with a Book and an Author. That is to say, the store request must accept a Book object, an Author object, and a third FooBar object:
// sample POST body
{
"book": {...book object...},
"author": {...author object...},
"foobar": {...foobar object...}
}
In the FooBarRequest validation rules(), would make sense to reuse BookRequest::rules() and AuthorRequest::rules(), but it's not working:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class FooBarRequest extends FormRequest
{
public function rules()
{
$bookRules = BookRequest::$validationRules;
$authorRules = AuthorRequest::$validationRules;
return [
'book' => $bookRules,
'authorRules' => $authorRules,
'foobar' => 'required|...some rules...'
];
}
}
Perhaps using Laravel's custom rules, but it doesn't help so much since the passes method must return a boolean (i.e. we should re-write all the validation logic for each attribute).
Is there any elegant/official way of doing this?
book and title attributes will fall under the Validating Nested Array Input rules.
For example, if you have:
$bookRules = ['title' => 'required', 'body' => 'text'];
$authorRules = ['name' => 'required'];
You want the rules to be like:
[
'book.title' => 'required',
'book.body' => 'text',
'author.name' => 'required',
'foobar' => 'required|...some rules...',
];
Assuming BookRequest::$validationRules and AuthorRequest::$validationRules already return what you want, you need to do the following to get there:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class FooBarRequest extends FormRequest
{
public function rules()
{
$bookRules = BookRequest::$validationRules;
$authorRules = AuthorRequest::$validationRules;
return array_merge(
$this->getNestedArrayRules($bookRules, 'book'),
$this->getNestedArrayRules($authorRules, 'author'),
['foobar' => 'required|...some rules...']
);
}
protected function getNestedArrayRules($rules, $key) {
return array_combine(
array_map(fn ($attribute) => "$key.$attribute", array_keys($rules)),
$rules
);
}
}
Check this answer out on Laracasts:
If you want to use the same validation rules in multiple places, perhaps use a trait and then use it in multiple form requests. So for book rules:
<?php
namespace App\Traits;
trait BookValidationTrait
{
protected function bookRules ()
{
return [
'book_input_1' => 'your rules here',
'book_input_2' => 'your rules here',
...
]
}
}
Then for author rules:
<?php
namespace App\Traits;
trait AuthorValidationTrait
{
protected function authorRules ()
{
return [
'author_input_1' => 'your rules here',
'author_input_2' => 'your rules here',
...
]
}
}
Then in your book form request class:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use App\Traits\BookValidationTrait;
class BookRequest extends FormRequest
{
use BookValidationTrait;
public function rules()
{
return $this->bookRules();
}
}
In your author form request class:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use App\Traits\AuthorValidationTrait;
class AuthorRequest extends FormRequest
{
use AuthorValidationTrait;
public function rules()
{
return $this->authorRules();
}
}
And finally, in your FooBar form request class:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use App\Traits\BookValidationTrait;
use App\Traits\AuthorValidationTrait;
class FooBarRequest extends FormRequest
{
use BookValidationTrait, AuthorValidationTrait;
public function rules()
{
return array_merge(
$this->bookRules(),
$this->authorRules(),
[
'foobar_input_1' => 'your rules here',
'foobar_input_2' => 'your rules here',
...
]
);
}
}
I haven't tested this, but it looks like that could work.
In your case, I see one way how to solve this problem, but also I'd like to demonstrate an example that can help in easier cases when you need to validate just one entity, for example, book:
Use BookRequest through DI:
class FooBarRequest extends FormRequest
{
public function rules(BookRequest $request)
{
return [
'foobar' => 'required|...some rules...'
];
}
}
Specifically for your case, I advise you to use the rules. You can use them in FooBarRequest, and also reuse them in other FormRequest classes:
class FooBarRequest extends FormRequest
{
public function rules()
{
return [
'book' => [new BookValidationRule()],
'authorRules' => [new AuthorValidationRule()],
'foobar' => 'required|...some rules...'
];
}
}

Problem with overwrite User3::all() method in User Mode

I have a User Model as:
<?php
namespace App;
use App\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Notification;
use Illuminate\Notifications\RoutesNotifications;
use Notifications\EmailClientOfAccount;
class User3 extends User
{
use Notifiable;
public $emailList;
protected $fillable = [
'name', 'email',
];
public function __construct($emails)
{
$this->emailList = $emails;
}
public function getEmailList()
{
return $this->emailList;
}
public static function all(array $columns=[])
{
return $emailList;
}
public function routeNotificationForMail($notification)
{
return $this->email;
}
}
Then,and in Controller:
$collection = collect([
[ 'name' => 'user1', 'email' => 'user1#gmail.com', ], [ 'name' => 'user2',
'email' => 'user2#gmail.com', ], [ 'name' => 'user1000', 'email' =>
'user1000#gmail.com', ],
]);
Second Stage:
$u4 = new User3($collection);
when I use :
dd($u4::all());
It show below error:
ErrorException: Declaration of App\User3::all(array $columns = Array)
should be compatible with
Illuminate\Database\Eloquent\Model::all($columns = Array)
I very try for solve it, but dont,
very Thanks for any help to me,
reference from: Laravel error "Declaration of model/model_name should be compatible with Illuminate\Database\Eloquent\Model"
When overriding a method from parent class - the signature of the method must be exactly the same in terms of parameters and their types
In the parent class, both $attributes and $options are set to be of type array, so you must also set set them this way in your class
namespace App\Models;
class User extends \Illuminate\Database\Eloquent\Model {
...
public function update(array $attributes = [], array $options = []) {
// ... your implementation
return parent::update($attributes, $options);
}
...
}
It show below error: ErrorException: Declaration of App\User3::all (array $columns = Array) should be compatible with Illuminate\Database\Eloquent\Model::all ($columns = Array)
Check that potions. they are not same. May be this is the reason. focus on this may helps you.

How to Create Model with Notifiable Trait

I want create a Model with Notifiable feature,
First,in my controller :
$collection = collect([
[
'name' => 'user1',
'email' => 'user1#gmail.com',
],
[
'name' => 'user2',
'email' => 'user2#gmail.com',
],
[
'name' => 'user1000',
'email' => 'user1000#gmail.com',
],
]);
$u3 = new User3($collection);
when I return $u3->getEmailList(); , output is :
[{"name":"user1","email":"user1#gmail.com"},{"name":"user2","email":"user2#gmail.com"},{"name":"user1000","email":"user1000#gmail.com"}]
my class for User3 is:
namespace App;
use App\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Notification;
use Illuminate\Notifications\RoutesNotifications;
use Notifications\EmailClientOfAccount;
class User3 extends User
{
use Notifiable;
public $emailList;
public function __construct($emails)
{
$this->emailList = $emails;
}
public function getEmailList()
{
return $this->emailList;
}
public function routeNotificationForMail($notification)
{
return $this->emailList['email'];
}
}
Then, I pass $u3 to Notification as:
Notification::send($u3->getEmailList(), new
SendMailNotification($template,$subject,$request->input('mailFromTitle'),$attachments));
It show below error:
Symfony\Component\Debug\Exception\FatalThrowableError: Call to a member function routeNotificationFor() on array
can you help me for solve this problem,Please?
Thanks in Advance,
//-------------------
I correct to :
Notification::send($u3, new SendMailNotification($template,$subject,$request->input('mailFromTitle'),$attachments));
In my My Notification:
public function toMail($notifiable)
{
return new EmailTo($notifiable,$this->view,$this->topic,$this-
>mailFrom,$this->attaches);
}
and in Build():
public function build()
{
$email= $this->view($this->view);
return $email;
}
But it not work, I dont know where is mistake?
Notification send expects a Notifiable object, not the email list itself, if you change it to this, you should get further.
Notification::send($u3, new SendMailNotification($template,$subject,$request->input('mailFromTitle'),$attachments));

Resources