I've been creating some tests to try my create delete edit functions on laravel from my database, this is my code:
ConstituencyController.php :
<?php
namespace App\Http\Controllers;
use App\Http\Requests\StoreConstituencyRequest;
use App\Http\Resources\ConstituencyResource;
use App\Models\Constituency;
use Illuminate\Http\Request;
use phpDocumentor\Reflection\Types\Collection;
class ConstituencyController extends Controller
{
/**
* Display a listing of the constituencies.
*
*
*/
public function index()
{
$constituency = Constituency::all();
return ConstituencyResource::collection($constituency);
}
/**
* Show the form for creating a new resource.
*
*
*/
public function create()
{
//
}
/**
* Store a newly created constituency in storage.
*
* #param Request $request
*
*/
public function store(Request $request)
{
$name = $request->name;
$data = array("name"=>$name);
Constituency::insert($data);
}
/**
* Display the specified constituency.
*
* #param int $id
*
*/
public function show(int $id)
{
$constituency = Constituency::find($id);
return new ConstituencyResource($constituency);
}
/**
* Show the form for editing the specified resource.
*
* #param int $id
*
*/
public function edit(int $id)
{
//
}
/**
* Update the specified constituency in storage.
*
* #param Request $request
* #param int $id
*
*/
public function update(Request $request, int $id)
{
$constituency = Constituency::find($id);
$constituency->name = $request->name;
$constituency->update();
}
/**
* Remove the specified constituency from storage.
*
* #param int $id
*
*/
public function destroy(int $id)
{
Constituency::find($id)->delete();
}
}
Constituency.php:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Constituency extends Model
{
use HasFactory;
public function candidate()
{
return $this->hasMany(Candidate::class);
}
public function town()
{
return $this->hasMany(Town::class);
}
}
ConstituencyResource.php :
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class ConstituencyResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}
ConstituencyFactory.php :
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* #extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Constituency>
*/
class ConstituencyFactory extends Factory
{
/**
* Define the model's default state.
*
* #return array<string, mixed>
*/
public function definition()
{
return [
'name' => $this->faker->word(),
];
}
}
Now this is my test to update a constituency:
public function test_a_constituency_can_be_modified()
{
$constituency = Constituency::factory()->create();
$constituency_id = $constituency->id;
$response = $this->put('api/constituencies/'.$constituency_id);
$this->assertDatabaseHas('constituencies', [
'id' => $constituency->id,
'name' => $constituency->name,
'created_at' => $constituency->created_at,
'updated_at' => $constituency->updated_at,
]);
}
Now of course the test passes, but i'm not actually giving it some new parameters to change... I've been trying to give some parameters to the function to actually change some data but i can't figure out how to do that.... I don't think i'm gonna have to put the parameters in the URI but where then?
If you are using PHPUnit you likely want to make use of Data Providers:
Example from docs
/**
* #dataProvider additionProvider
*/
public function testAdd(int $a, int $b, int $expected): void
{
$this->assertSame($expected, $a + $b);
}
public function additionProvider(): array
{
return [
'adding zeros' => [0, 0, 0],
'zero plus one' => [0, 1, 1],
'one plus zero' => [1, 0, 1],
'one plus one' => [1, 1, 3]
];
}
The smart folks over at Tighten also have an excellent tutorial on data providers.
If you're using PEST then you'll want Data Sets.
Example from docs
dataset('emails', [
'enunomaduro#gmail.com',
'other#example.com'
]);
it('has emails', function ($email) {
expect($email)->not->toBeEmpty();
})->with('emails'); // <-- use the dataset
Using data providers and data sets allows you to reuse data, but also test against multiple inputs for your unit test. You could if you wanted just hard code a value after you're arrange statement (where you create the DB record) but that has limitations and providers are far more flexible.
Update - Example test
The following is an example of how you might go about things. Note this is not exhaustive and things like using $request->all() to update your model are not advisable but I have done so to keep things simple for illustritive purposes. This should give you an idea of where/how you could go about performing your testing. There are many ways/opinions on such things.
api.php
Route::put('/constituencies/{constituency}',
[ConstituencyController::class, 'update']
)->name('api.constituencies.update');
ConstituencyController.php
<?php
namespace App\Http\Controllers;
use App\Models\Constituency;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class ConstituencyController extends Controller
{
public function update(Request $request, Constituency $constituency)
{
$constituency->update($request->all());
return response()->json($constituency, Response::HTTP_OK);
}
}
ExampleTest.php
<?php
namespace Tests\Feature;
use Tests\TestCase;
use App\Models\Constituency;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Foundation\Testing\RefreshDatabase;
class ExampleTest extends TestCase
{
/**
* A basic test example.
*
* #test
* #dataProvider constituencyNameProvider
* #return void
*/
public function it_can_update_constituency_name_successfully($constituencyName)
{
// Arrange
$constituency = Constituency::factory()->create();
$payload = ['name' => $constituencyName];
// Act
$response = $this->put(route('api.constituencies.update', $constituency->id), $payload);
// Assert
$response->assertStatus(Response::HTTP_OK)
->assertJson([
'id' => $constituency->id,
'name' => $constituencyName
])
->assertJsonStructure([
'id', 'name', 'created_at', 'updated_at'
]);
}
public function constituencyNameProvider(): array
{
return [
['Ostwald'],
['Springtown'],
['Baybarrow'],
['Blackhaven'],
['Lochspring'],
];
}
}
Related
I am trying to attach documents to a model when saving. I am getting array to string conversion erro. Because, it puts the documents Ids in to an array.
Here is my model.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Orchid\Screen\AsSource;
use App\Models\Seller;
use Orchid\Attachment\Models\Attachment;
use Orchid\Attachment\Attachable;
use Orchid\Filters\Filterable;
use Orchid\Metrics\Chartable;
use App\Orchid\Presenters\PropertyPresenter;
use Laravel\Scout\Searchable;
class Property extends Model
{
use HasFactory;
use AsSource, Attachable,Filterable;
use Chartable;
/**
* #var array
*/
protected $fillable = [
'property_name',
'property_type',
'property_city',
'property_address',
'property_area',
'seller_id',
'property_cost',
'property_price',
'property_rent_price',
'status',
'contracts',
'images'
];
/**
* Get the parent seller of the property.
*/
public function seller()
{
return $this->belongsTo(Seller::class);
}
/**
* Name of columns to which http sorting can be applied
*
* #var array
*/
protected $allowedSorts = [
'property_name',
'property_type',
'property_city',
'status',
'created_at',
'updated_at'
];
/**
* #param Builder $query
*
* #return Builder
*/
public function scopeActive(Builder $query)
{
return $query->where('status', 'Available');
}
/**
* #param Builder $query
*
* #return Builder
*/
public function scopeStatus(Builder $query)
{
return $query->where('status', '!=', 'Sold');
}
// Many-to-Many (no foreign id on table, should be uploaded with groups() function)
public function documents()
{
return $this->hasMany(Attachment::class)->where('group','contracts');
}
/**
* Get the presenter for the model.
*
* #return PropertyPresenter
*/
public function presenter()
{
return new PropertyPresenter($this);
}
/**
* Get the indexable data array for the model.
*
* #return array
*/
public function toSearchableArray()
{
$array = $this->toArray();
// Customize array...
return $array;
}
}
Here are the form elements
Upload::make('property.contracts')
->title('Documents')
->maxFileSize(2)
->targetId()
->targetRelativeUrl()
->groups('documents')
->acceptedFiles('image/*,application/pdf,.psd'),
And here is the save/update function
/**
* #param Property $property
* #param Request $request
*
* #return \Illuminate\Http\RedirectResponse
*/
public function createOrUpdate(Property $property, Request $request)
{
$property->fill($request->get('property'))->save();
$property->attachment()->syncWithoutDetaching(
$request->input('property.contracts', [])
);
Alert::info('You have successfully created an property.');
return redirect()->route('platform.property.list');
}
Finally here is the migration file
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('properties', function (Blueprint $table) {
$table->id();
$table->string('property_name');
$table->string('property_type');
$table->string('property_city');
$table->text('property_address');
$table->double('property_area',15,2);
$table->unsignedBigInteger('seller_id');
$table->double('property_cost', 20.3);
$table->double('property_price');
$table->double('property_rent_price')->nullable();
$table->string('contracts')->nullable();
$table->string('images')->nullable();
$table->string('status')->default('Available');
$table->timestamps();
$table->foreign('seller_id')->references('id')->on('sellers');
});
}
It gives the array to string conversion error and here is the request body:
{ "property_name": "Villa02", "property_type": "residential", "property_city": "Garoowe", "property_address": "Test", "property_area": "12000", "seller_id": "1", "property_cost": "43000", "property_price": "50000", "property_rent_price": "300", "status": "Available", "contracts": [ "67" ], "images": "/storage/2022/04/15/e3cbed3acbfec1d40c54aa57aa651a05c80d6586.png" }
Thanks in advance.
Just unset the array before filling the model
public function createOrUpdate(Property $property, Request $request)
{
$propertyInputs = $request->get('property');
$contacts = $propertyInputs['contracts']??[];
unset($propertyInputs['contracts']);
$property->fill($propertyInputs)->save();
$property->attachment()->syncWithoutDetaching($contacts);
Alert::info('You have successfully created an property.');
return redirect()->route('platform.property.list');
}
With some Models, when I make a new Nova Resource for them, seems that Nova can't find the Model because they doesn't show on sidebar (i can't reach them also by URL, giving me a 404).
But this happens only for specific Models and if I try to modify the target Model in the Resource with another one (editing the $model variable), it works and shows the Resource in the sidebar (but with the wrong model). Nova isn't throwing me any error so the debugging is getting crazy difficult.
The Models that doesn't work in my project are named "Product" and "Company".
I'm using Laravel 7.28.3, Nova 3.9.1, MariaDB 10.4.11 and PHP 7.4.1 with Homestead.
Here's the code of Product resource:
<?php
namespace App\Nova;
use Illuminate\Http\Request;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Http\Requests\NovaRequest;
class Product extends Resource
{
/**
* The model the resource corresponds to.
*
* #var string
*/
public static $model = \App\Product::class;
/**
* The single value that should be used to represent the resource when being displayed.
*
* #var string
*/
public static $title = 'title';
/**
* The columns that should be searched.
*
* #var array
*/
public static $search = [
'id', 'name'
];
/**
* Get the fields displayed by the resource.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function fields(Request $request)
{
return [
ID::make()->sortable(),
];
}
/**
* Get the cards available for the request.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function cards(Request $request)
{
return [];
}
/**
* Get the filters available for the resource.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function filters(Request $request)
{
return [];
}
/**
* Get the lenses available for the resource.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function lenses(Request $request)
{
return [];
}
/**
* Get the actions available for the resource.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function actions(Request $request)
{
return [];
}
}
And here's the Model code:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
class Product extends Model implements HasMedia
{
use InteractsWithMedia;
public function visits()
{
return visits($this);
}
public function user() {
return $this->belongsTo('App\User');
}
public function company() {
return $this->belongsTo('App\Company');
}
public function productVariety() {
return $this->belongsTo('App\ProductVariety', 'product_variety_id');
}
public function productSpecies() {
return $this->belongsTo('App\ProductSpecies', 'product_species_id');
}
public function productNutrients() {
return $this->hasMany('App\ProductNutrient');
}
public function baseProduct() {
return $this->hasOne('App\Product', 'base_product_id');
}
public function recipes() {
return $this->hasMany('App\Recipe', 'base_product_id');
}
protected $fillable = [
'user_id', 'company_id', 'imageline_id', 'products_species_id', 'products_varieties_id', 'base_product_id',
'name', 'scientific_name', 'production_start', 'production_end', 'production_city', 'description', 'story', 'curiosities', 'advices', 'quantity_advices', 'why_good', 'who_good',
'is_base_product', 'show_related_recipes', 'show_related_products'
];
}
Check your AuthServiceProvider on app/Providers/AuthServiceProvider.php if there is a Policy set to this model. Then on your policy class (probably ProductPolicy which is bind to Product model, check view and viewAny methods, these methods must return true or conditional true.
I am unable to get my yajrabox-datatable to render in my view. I get the following error:
DataTables warning: table id=dataTableBuilder - Invalid JSON response. For more information about this error, please see http://datatables.net/tn/1
I have set extended the datatables class and included return $dataTable->render('activities/index'); in my controller.
ActivitiesController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Activity;
use DB;
use Yajra\Datatables\Datatables;
use Redirect,Response;
Use App\DataTables\ActivityDataTable;
use Session;
use Log;
class ActivitiesController extends Controller
{
public function index(ActivityDataTable $dataTable)
{
session(['source' => 'activities']);
Log::info('Visiting: index');
Log::info('Source: '.session('source'));
return $dataTable->render('activities/index');
}
}
ActivityDataTable.php
<?php
namespace App\DataTables;
use App\Activity;
use Yajra\DataTables\Services\DataTable;
use Yajra\DataTables\EloquentDataTable;
use Yajra\DataTables\DataTables;
class ActivityDataTable extends DataTable
{
/**
* Display ajax response.
*
* #return \Illuminate\Http\JsonResponse
*/
public function ajax()
{
return $this->datatables
->eloquent($this->query())
->make(true);
}
/**
* Build DataTable class.
*
* #param mixed $query Results from query() method.
* #return \Yajra\DataTables\DataTableAbstract
*/
public function dataTable($query, DataTables $dataTables)
{
return $dataTables->eloquent($query);
}
/**
* Get query source of dataTable.
*
* #param \App\Activity $model
* #return \Illuminate\Database\Eloquent\Builder
*/
public function query()
{
// $query=Activity::all()->take(50);
// return Datatables::of($query)
// ->addColumn('user', function ($query) {
// return $query->user->name;
// })->make(true);
return Activity::query();
}
/**
* Optional method if you want to use html builder.
*
* #return \Yajra\DataTables\Html\Builder
*/
public function html()
{
return $this->builder()
->columns($this->getColumns())
->parameters($this->getBuilderParameters());
}
/**
* Get parameters.
*
* #return array
*/
protected function getBuilderParameters()
{
return [
'dom' => 'Bfrtip',
'buttons' => ['excel'],
];
}
/**
* Get columns.
*
* #return array
*/
protected function getColumns()
{
return [
'id',
'month',
'activity',
'learned',
'role',
'hours',
'user',
];
}
/**
* Get filename for export.
*
* #return string
*/
protected function filename()
{
return 'Activity_' . date('YmdHis');
}
}
Okay,
I was using a guide that was for an older version of yajra-datatables.
Following this upgrade guide resolved my issue:
https://yajrabox.com/docs/laravel-datatables/7.0/upgrade
I have created a form request using php artisan make:request ValidateRegistration. It created a ValidateRegistration.php file under App\Http\Requests\ directory. After this I have made changes in the store() function of my registration controller ie UserController.php, means I have changed it
FROM
public function store(Request $request)
{
// Save the data
User::create(request(['fname','lname','phone','email','password']));
// redirect to home page
return redirect('/registration-success');
}
TO
public function store(ValidateRagistration $request)
{
// Save the data
User::create(request(['fname','lname','phone','email','password']));
// redirect to home page
return redirect('/registration-success');
}
And added use App\Http\Requests\ValidateRagistration; at the top of the UserController.php file. But when I submit the form without filling anything it shows me an error which is Class App\Http\Controllers\ValidateRegistraion does not exist
EDIT
Added UserController.php and ValidateRegistration.php files.
UserController.php
<?php
use App\Http\Requests\ValidateRegistration;
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\User;
class UserController extends Controller
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
//
}
/**
* Show the form for creating a new resource.
*
* #return \Illuminate\Http\Response
*/
public function create()
{
$title = "Registration";
return view('/registration', compact('title'));
}
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store(ValidateRegistration $request)
{
//// validate requested data
//$this->validate(request(), [
// 'fname' => 'required',
// 'lname' => 'required',
// 'phone' => 'required|size:10',
// 'email' => 'required',
// 'password' => 'required'
//]);
// Save the data
User::create(request(['fname','lname','phone','email','password']));
// redirect to home page
return redirect('/registration-success');
}
/**
* Display the specified resource.
*
* #param int $id
* #return \Illuminate\Http\Response
*/
public function show($id)
{
//
}
/**
* Show the form for editing the specified resource.
*
* #param int $id
* #return \Illuminate\Http\Response
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* #param \Illuminate\Http\Request $request
* #param int $id
* #return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
*
* #param int $id
* #return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
}
ValidateRegistration.php
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ValidateRegistration extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'fname' => 'required',
'lname' => 'required',
'phone' => 'required|size:10',
'email' => 'required',
'password' => 'required'
];
}
/**
* Get the error messages for the defined validation rules.
*
* #return array
*/
public function messages()
{
return [
'fname.required' => 'Firstname is mandatoy',
'lname.required' => 'Lastname is mandatory',
'phone.required' => 'Phone is mandatory',
'phone.size' => 'Phone must be 10 digit',
'email.required' => 'Email is mandatory',
'password.required' => 'Password is mandatory',
];
}
}
spot the difference in your class names:
ValidateRagistration
ValidateRegistraion
and I'm guessing it should read ValidateRegistration, clear up typos, they will only confuse things later
at the top of UserController.php swap the positions of the namespace and use lines, namespace should always be first
<?php
namespace App\Http\Controllers;
use App\Http\Requests\ValidateRegistration;
use Illuminate\Http\Request;
use App\User;
and ValidateRegistration.php is in your App\Http\Requests directory
in ValidateRegistration.php I modified the authorize() function. It was returning false. Changed it to true. It is working now.
From
public function authorize()
{
return false;
}
To
public function authorize()
{
return true;
}
I've been following the Laravel Authorization docs trying to build "is the user allowed to do this" functionality by using Policies, but I can't get it to work. I keep getting This action is unauthorized and I've tried with route middleware too.
PagePolicy.php:
namespace App\Policies;
use App\Models\User;
use App\Models\Page;
use Illuminate\Auth\Access\HandlesAuthorization;
class PagePolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view the page.
*
* #param App\Models\User $user
* #param App\Models\Page $page
* #return mixed
*/
public function view(User $user, Page $page)
{
return $user->id === $page->user_id;
}
/**
* Determine whether the user can create pages.
*
* #param App\Models\User $user
* #return mixed
*/
public function create(User $user)
{
}
/**
* Determine whether the user can update the page.
*
* #param App\Models\User $user
* #param App\Models\Page $page
* #return mixed
*/
public function update(User $user, Page $page)
{
//
}
/**
* Determine whether the user can delete the page.
*
* #param App\Models\User $user
* #param App\Models\Page $page
* #return mixed
*/
public function delete(User $user, Page $page)
{
//
}
}
PageController.php:
namespace App\Http\Controllers;
use Auth;
use Carbon\Carbon;
use App\Models\Page;
use App\Http\Requests\PageRequest;
class PageController extends ApiController
{
public function createNewPage(PageRequest $request)
{
$this->authorize('create', Page::class);
$request->merge([
'user_id' => Auth::id(),
'published_at' => Carbon::now(),
]);
if (Page::create($request->all())) {
return response()->json('success', 201);
}
return response()->json('error', 500);
}
}
AuthServiceProvidor.php:
namespace App\Providers;
use App\Models\Page;
use App\Policies\PagePolicy;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* #var array
*/
protected $policies = [
Page::class => PagePolicy::class,
];
/**
* Register any authentication / authorization services.
*
* #return void
*/
public function boot()
{
$this->registerPolicies();
//
}
}
I managed to figure it out. I wasn't using Route Model Binding. So I added authorize() after the page call and used the $page variable instead of Page::class.
public function update(PageUpdateRequest $request, $pageSlug)
{
$page = Page::where(['user_id' => Auth::id(), 'slug' => $pageSlug])->first();
$this->authorize('update', $page);
$page->update($request->all());
return fractal()->item($page, new PageTransformer())->toArray();
}
It's not totally clear to me which action you're attempting to authorize since you've provided the call to create in the controller but only provided a policy check in place for viewing a page. Having said that, I would be sure to var_dump/dd the values you're attempting to do a type comparison of to verify they're of the same type. If anything's been explicitly cast, it may cause issues with certain database drivers that return integers as strings.
I think the problem is not in your policies, rather in your PageRequest class. Make sure the authorize() method in your App\Http\Requests\PageRequest class returns true .
class PageRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true; // you can also check the authorization using PagePolicy here
}
}
Current code:
protected $policies = [
Task::class => TaskPolicy::class,
];
Solution code:
protected $policies = [
'App\Task' => 'App\Policies\TaskPolicy',
];
I experienced the same problem, while following the Intermediate Task List Tutorial on the Laravel website.
The solution is actually present in the Github code for this tutorial.