is there a way to disable mass assignment protection for all models across all tests without having to duplicate this over and over?
FooTest
Foo::unguard();
Bar::unguard();
Baz::unguard();
Foo::create(['column' => 'value']);
Bar::create(['column' => 'value']);
Baz::create(['column' => 'value']);
BarTest
Foo::unguard();
Bar::unguard();
Baz::unguard();
Foo::create(['column' => 'value']);
Bar::create(['column' => 'value']);
Baz::create(['column' => 'value']);
BazTest
Foo::unguard();
Bar::unguard();
Baz::unguard();
Foo::create(['column' => 'value']);
Bar::create(['column' => 'value']);
Baz::create(['column' => 'value']);
I figured it out using the TestCase every Test class extends, and the Eloquen\Model every model extends.
tests/TestCase.php
<?php
namespace Tests;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication, DatabaseMigrations;
public function setUp(): void
{
parent::setUp();
Model::unguard();
}
}
Related
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'],
]);
}
}
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;
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...'
];
}
}
I have an error when I run my test in laravel:
Illuminate\Database\QueryException: could not find driver (SQL: PRAGMA foreign_keys = ON;)
Here is the test code:
namespace Tests\Feature;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use App\Book;
class BookReservationTest extends TestCase
{
use RefreshDatabase;
/** #test */
public function a_book_can_be_added_to_the_library()
{
$this->withoutExceptionHandling();
$response = $this->post('/books', [
'title' => 'cool book title',
'author' => 'victor'
]);
$response->assertOk();
$this->assertCount(11, Book::all());
}
}
I created a new class under /app/UserRepositories/UserRepository.php.
Now i want to use it in my AuthenticateUser.php under /app.
I tried to import it like that use App\Repositories\UserRepository;
but I get still the same error: Class does not exist
UserRepository.php
<?php use App\Repositories;
use App\User;
class UserRepository {
public function updateOrCreate($userData)
{
return User::firstOrCreate([
'username' => $userData->username,
'email' => $userData->email,
'avatar' => $userData->avatar
]);
}
}
At the beginning of your file /app/UserRepositories/UserRepository.php, you will need to namespace it using:
namespace App\UserRepositories;
Then you can import it to be used anywhere by:
use App\UserRepositories\UserRepository;