mass assignment on [App\comment] in laravel - laravel

trying to add a comment in my blog , so i got this new error :
Add [body] to fillable property to allow mass assignment on [App\comment].
this is thr controller :
public function store (blog $getid)
{
comment::create([
'body' =>request('body'),
'blog_id'=> $getid->id
]);
return view('blog');
}
and this is the form :
<form method="POST" action="/blog/{{$showme->id}}/store" >
#csrf
<label> Commentaire </label> </br>
<textarea name="body" id="" cols="30" rows="2"></textarea> </br>
<button type="submit"> Ajouter commentaire</button>
</form>
web.php :
Route::post('blog/{getid}/store', 'commentcontroller#store');

To avoid filling in any given property, Laravel has mass asignment protection. The properties you want to be filled should be in the $fillable property on the model.
class Comment {
$fillable = ['body', 'blog_id'];
}
Bonus
For keeping up with standards. You should not name your classes in lower case, it should be Blog and Comment, both in PHP code and in file name.
Id should not be filled but associated, so they will be loaded properly on the model. So imagine your Comment model having the blog relationship.
class Comment {
public function blog() {
return $this->belongsTo(Blog::class);
}
}
You should assign it instead. Where you would get the Blog by using Model binding, therefor you should name the parameter $blog so the binding will work. Additionally using Request dependency injected, is also a good approach.
use Illuminate\Http\Request;
public function store(Request $request, Blog $blog) {
$comment = new Comment(['body' => $request->input('body')]);
$comment->blog()->associate($blog);
$comment->save();
}

Related

How to dynamically add input fields with livewire

I want add new Inputbox to be created when the user clicks the add button.
I tried the following code but it didn't work, And I get the following error:
get_class(): Argument #1 ($object) must be of type object, string
given
class Test extends Component
{
public $tests=[''];
public function render()
{
return view('livewire.test');
}
public function mount()
{
$this->tests = Test::all();
}
public function add()
{
$this->tests[] = '';
}
}
<div>
<form wire:submit.prevent="submit">
#foreach($tests as $index=>$test)
<div>
<label for="title">
<input wire:model="title.{{$index}}" type="text">
</label>
</div>
#endforeach
<button type="submit">Save</button>
</form>
<button wire:click="add()">ADD MORE</button>
</div>
There are a few issues to address here, so I will provide some background information and try to be as descriptive as possible.
In your mount function you assign a collection of Test objects to your tests array property:
public function mount()
{
$this->tests = Test::all(); // <-- returns a collection of Test models
}
This overrides the default value and type you have assigned to tests which is an array:
/*
* This declares $tests to be an array
* but this becomes a Collection due to your mount method
*/
public $tests = [''];
Yet in your add function you're adding a string:
public function add()
{
$this->tests[] = '';
}
So you're getting the error because you're trying to add a string to a variable that is a collection of Test models. What you likey want to do is add a new empty Test model.
public function add()
{
$this->tests->push(Test::make());
}
However, the above will not work as you're working with an existing database collection and so you'll get the following error:
Queueing collections with multiple model connections is not supported.
Therefore to achieve your goal of adding a new Test we need to approach this differently.
app\Http\Livewire\Test.php
<?php
namespace App\Http\Livewire;
use Illuminate\Support\Collection;
use Livewire\Component;
class Test extends Component
{
// A collection of your models from the database
public Collection $tests;
// A conditional variable that we use to show/hide the add new model inputs
public bool $isAdding = false;
// A variable to hold our new empty model during creation
public \App\Models\Test $toAdd;
// Validation rules
// These are required in order for Livewire to successfull bind to model properties using wire:model
// Add any other validation requirements for your other model properties that you bind to
protected $rules = [
'tests.*.title' => ['required'],
'toAdd.title' => ['required']
];
// Livewires implementation of a constructor
public function mount()
{
// Set some default values for the component
$this->prepare();
}
public function render()
{
return view('livewire.test');
}
//
public function add()
{
// Set the show/hide variable to true so that the new inputs section is shown
$this->isAdding = true;
}
// Save the new model data
public function save()
{
// Save the model to the database
$this->toAdd->save();
// Clean things up
$this->prepare();
}
// Just a helper method for performing repeated functionality
public function prepare()
{
// Get all the required models from the database and assign our local variable
$this->tests = \App\Models\Test::all();
// Assign an empty model to the toAdd variable
$this->toAdd = \App\Models\Test::make();
// Set the isAdding show/hide property to false, which will hide the new model inputs
$this->isAdding = false;
}
}
resources\views\livewire\test.blade.php
<div>
<!-- loop over all your test models -->
#foreach ($tests as $index => $test)
<!-- the :key attribute is essential in loops so that livewire doesnt run into DOM diffing issues -->
<div :key="tests_{{ $index }}">
<label for="tests_{{ $index }}_title">Title {{ $index }}
<input type="text" id="tests_{{ $index }}_title" name="tests_{{ $index }}_title"
wire:model="tests.{{ $index }}.title" :key="tests_{{ $index }}_title" />
</label>
</div>
#endforeach
<!-- Only show the new model inputs if isAdding is true -->
#if ($isAdding)
<!-- this doesnt need a :key as its not in a loop -->
<div>
<label for="title">Title
<input type="text" id="title" name="title" wire:model="toAdd.title" />
</label>
<!-- triggers the save function on the component -->
<button type="button" wire:click="save">Save</button>
</div>
#endif
<!-- triggers the add function on the component -->
<button type="button" wire:click="add">Add More</button>
</div>
Hopefully, with the comments I have added the above makes sense.
Update
Is it possible to create several input boxes and finally save all the data at once?
That is entirely possible yes. Due to some limitations with Livewire, specifically it not knowing how to hydrate Eloquent Models in a Collection correctly, we need to use a little wizardry to make it work.
app\Http\Livewire\Test.php
<?php
namespace App\Http\Livewire;
use Illuminate\Support\Collection;
use Livewire\Component;
class Test extends Component
{
// A collection of your models from the database
public Collection $tests;
// A conditional variable that we use to show/hide the add new model inputs
public bool $isAdding = false;
// A variable to hold our new empty models during creation
public Collection $toAdd;
// Validation rules
// These are required in order for Livewire to successfull bind to model properties using wire:model
// Add any other validation requirements for your other model properties that you bind to
protected $rules = [
'tests.*.title' => ['required'],
'toAdd.*.title' => ['required']
];
// Livewires implementation of a constructor
public function mount()
{
$this->cleanUp(true);
}
public function hydrate()
{
// Get our stored Test model data from the session
// The reason for this is explained further down
$this->toAdd = session()->get('toAdd', collect());
}
public function render()
{
return view('livewire.test');
}
public function add()
{
$this->isAdding = true;
// Push (add) a new empty Test model to the collection
$this->toAdd->push(\App\Models\Test::make());
// Save the value of the toAdd collection to a session
// This is required because Livewire doesnt understand how to hydrate an empty model
// So simply adding a model results in the previous elements being converted to an array
session()->put('toAdd', $this->toAdd);
}
public function save($key)
{
// Save the model to the database
$this->toAdd->first(function ($item, $k) use ($key) {
return $key == $k;
})->save();
// Remove it from the toAdd collection so that it is removed from the Add More list
$this->toAdd->forget($key);
// Clean things up
$this->cleanUp(!$this->toAdd->count());
}
public function saveAll()
{
$this->toAdd->each(function ($item) {
$item->save();
});
$this->cleanUp(true);
}
// Just a helper method for performing repeated functionality
public function cleanUp(bool $reset = false)
{
// Get all the required models from the database and assign our local variable
$this->tests = \App\Models\Test::all();
// If there are no items in the toAdd collection, do some cleanup
// This will reset things on page refresh, although you might not want that to happen
// If not, consider what you want to happen and change accordingly
if ($reset) {
$this->toAdd = collect();
$this->isAdding = false;
session()->forget('toAdd');
}
}
}
resources\views\livewire\test.blade.php
<div>
<!-- loop over all your test models -->
#foreach ($tests as $index => $test)
<!-- the :key attribute is essential in loops so that livewire doesnt run into DOM diffing issues -->
<div :key="tests_{{ $index }}">
<label for="tests_{{ $index }}_title">Title {{ $index }}
<input type="text" id="tests_{{ $index }}_title" name="tests_{{ $index }}_title"
wire:model="tests.{{ $index }}.title" :key="tests_{{ $index }}_title" />
</label>
</div>
#endforeach
<!-- Only show the new model inputs if isAdding is true -->
#if ($isAdding)
<div>
#foreach ($toAdd as $index => $value)
<div :key="toAdd_{{ $index }}">
<label for="toAdd_{{ $index }}_title">New Title {{ $index }}
<input type="text" id="toAdd_{{ $index }}_title"
name="toAdd_{{ $index }}_title" wire:model="toAdd.{{ $index }}.title"
:key="toAdd_{{ $index }}_title" />
</label>
<!-- triggers the save function on the component -->
<button type="button" wire:click="save({{ $index }})" :key="toAdd_{{ $index }}_save">Save</button>
</div>
#endforeach
<button type="button" wire:click="saveAll">Save All</button>
</div>
#endif
<!-- triggers the add function on the component -->
<button type="button" wire:click="add">Add More</button>
</div>
I've not removed some of the previous comments and not commented on the code that should be pretty obvious what it is doing.
I did rename the prepare function to cleanUp and it seemed more appropriate.
I also provided functions for saving an individual Test model, or all at the same time. You might want to be able to do one or the other or both at some point so seemed useful.

Updating form in Laravel goes wrong

This may be a very simple question, but I can't figure it out! and that's frustrating.
I do my best to explain everything step by step.
This a small Todo list project in Laravel 8
A user can create a Project.
When user clicks on a created project, he goes to the project page (show page)
ShowController.php
public function show(Project $project)
{
return view('projects.show', compact('project'));
}
In the show page the user can add comments via a textarea form field.
show.blade.php
<form action="{{ route('project.update',['project' => $project]) }}" method="post">
#csrf
#method('PUT')
<textarea name="notes" placeholder="Add notes">{{ $project->notes ?? '' }}</textarea>
<button type="submit">Save</button>
</form>
Where it goes wrong is here, by updating the project! as soon as the user enters something in the comments field and clicks save, the form indicates that the following items are required:
The owner_id, title, description field are required. While the model is sent to the show blade page and then in the form action route.
What am I doing wrong here?
UpdateController.php
public function update(ProjectRequest $request, Project $project)
{
$validated = $request->validated();
$project->update($validated);
return redirect($project->path());
}
ProjectRequest.php
public function rules(): array
{
return [
'owner_id' => 'required',
'title' => 'required',
'description' => 'required',
'notes' => 'nullable',
];
web.php
use App\Http\Controllers\Projects\CreateController;
use App\Http\Controllers\Projects\IndexController;
use App\Http\Controllers\Projects\ShowController;
use App\Http\Controllers\Projects\StoreController;
use App\Http\Controllers\Projects\UpdateController;
use Illuminate\Support\Facades\Route;
Route::get('/', [IndexController::class, 'index'])->name('project.index');
Route::get('/projects/create', [CreateController::class, 'create'])->name('project.create');
Route::post('/projects', [StoreController::class, 'store'])->name('project.store');
Route::get('/projects/{project}', [ShowController::class, 'show'])->name('project.show');
Route::put('/projects/{project}', [UpdateController::class, 'update'])->name('project.update');
migration
public function up()
{
Schema::create('projects', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('owner_id');
$table->string('title');
$table->text('description');
$table->text('notes')->nullable();
$table->timestamps();
$table->foreign('owner_id')
->references('id')
->on('users')
->onDelete('cascade');
});
}
You have required rules for fields that does not exist in form. So validation correctly fails.
If you use these rules for storing data, and want different one for updating, then you have at least three solutions:
Make separate form request file. So instead ProjectRequest do for
ex. ProjectUpdateRequest and ProjectStoreRequest.
Use single Request, but detect if it is Update or Store inside rules() function, and return different rules array based on it. Related question: Laravel form request validation on store and update use same validation
Don't use custom FormRequest at all for Update, just make this single validation inside controller update() function.
https://laravel.com/docs/8.x/validation#quick-writing-the-validation-logic
Option 2 seems to be best solution, because you will not have to repeat validation rules for "notes" input in multiple places - everything will be in single file.
If the fields are not required then take them out of your $required array and it should work.
When injecting a model ID to a route or controller action, you will
often query the database to retrieve the model that corresponds to
that ID. Laravel route model binding provides a convenient way to
automatically inject the model instances directly into your routes.
For example, instead of injecting a user's ID, you can inject the
entire User model instance that matches the given ID.
Reference
show.blade.php
<form action="{{ route('project.update',['project' => $project->id]) }}" method="post">
#csrf
#method('PUT')
<textarea name="notes" placeholder="Add notes">{{ $project->notes ?? '' }}</textarea>
<button type="submit">Save</button>
</form>
Also, to update a column, you do not need to validate and update all the columns.
UpdateController.php
public function update(Request $request, Project $project)
{
$request->validate([
'title' => 'nullable|string',
]);
$project->update(['notes' => $request->notes ?? '']);
return redirect($project->path());
}
Note: Add use Illuminate\Http\Request; to the first UpdateController.php file.

how to hide id in url in laravel?

I am creating ecommerce website in Laravel and I want to hide productdetails id in url. What can I change in code plz need solution.
Web.php
Route::get('/productdetail{id}','ProductDetailController#display_products_details');
controller code:
public function display_products_details($id)
{
$data = DB::select('select * from subcategory inner join product_details on subcategory.sub_id=product_details.sub_id where product_details.sub_id = ?',[$id]);
return view('productdetails',['data'=>$data]);
}
link tag code:
{{ $value->name_of_subcategory }}
path I getting:
localhost:8000/productdetail12
But actually I want Localhost:8000/productdetail
You can do this by having the id as part of the request body so it will not be visible in the URL (it will still be visible to anyone inspecting the request body via their browser developer tools).
You can do this by using a form to send a post request:
Route::post('/productdetail','ProductDetailController#display_products_details');
The link will change to a form:
<form method="POST" action="{{ action('ProductDetailController#display_products_details') }}>
#csrf
<input type="hidden" name="id" value="{{ $value->sub_id }}" />
<button type="submit">{{ $value->name_of_subcategory }}</button>
</form>
You may need to style the button to look like a link.
The controller becomes:
public function display_products_details(Request $request)
{
$request->validate([ 'id' => 'required|int' ]);
$id = $request->input('id');
$data = DB::select('select * from subcategory inner join product_details on subcategory.sub_id=product_details.sub_id where product_details.sub_id = ?',[$id]);
return view('productdetails',['data'=>$data]);
}
Note that while this is theoretically possible it does result in very bad user experience. People can't share product links with others for example

how do i pass data value to another page via link in laravel?

i am trying to make a list of locations that you can rent. but to rent the place you need to fill in some information. to fill in this information you excess another page. how do i make it so laravel knows the page belongs to a certain location
this is what ive done now but i keep getting the error:
Call to undefined method App\Reservation::location()
as soon as i have filled in the fields of information
this is the blade file that links to the the create reservation file
#foreach
($locations as $location => $data)
<tr>
<th>{{$data->id}}</th>
<th>{{$data->name}}</th>
<th>{{$data->type}}</th>
<th><a class="btn" href="{{route('Reservation.index', $data->id)}}">rent</a></th>
</tr>
#endforeach
this is the create reservations blade
<form action="{{ route('location.store') }}" method="post">
#csrf
<label>title</label>
<input type="text" class="form-control" name="name"/>
<label>type</label>
<select>
<option value="0">klein</option>
<option value="1">groot</option>
</select>
<button type="submit" class="btn">inschrijven</button>
</form>
this is what the location controller looks like
public function store(Request $request)
{
$location = new Reservation;
$location->name = $request->get('name');
$location->type = $request->get('type');
$location->location()->associate($request->location());
$location->save();
return redirect('/location');
}
and the relationships in my models should also work
class Reservation extends Model
{
public function locations()
{
return $this->belongsTo('Location::class');
}
}
class Location extends Model
{
public function reservations()
{
return $this->hasMany('Registration::class');
}
}
ive been stuck at this all day and i really dont know where to look anymore
The error you are getting is because of the wrong function name, you are calling location, while it is locations.
public function locations(){}
&
$location->location()->associate($request->location());
and you can pass the variable as a query parameter, you'll need to pass this data as an array in your blade file.
Web.php
Route::get('/somewhere/{id?}, function(){
//do something
})->name('test');
Blade
route('test', ['id' => $id]);
Controller Method
public function store(Request $request, $id) //Adding the query parameter for id passed in Route.
{
$location = new Reservation;
$location->name = $request->get('name');
$location->type = $request->get('type');
$location->location()->associate($id);
$location->save();
return redirect('/location');
}

How to use actuallymab\laravel-comment comments system?

I'm new to Laravel and I'm trying to implement a comments system on some posts. I found a package which seems well supported and I thought I could save some time by using it rather than starting from scratch.
The packing is here:
https://github.com/actuallymab/laravel-comment
There are some usage details but they don't seem quite clear enough for someone at my level.
Where I'm at:
Composer done
Migrations done
in my "User" model:
use Actuallymab\LaravelComment\CanComment;
In my "Post" model:
use Actuallymab\LaravelComment\Commentable;
class Posts extends Model {
use Commentable;
protected $canBeRated = true;
//etc
}
In my PostsController I have:
public function comment()
{
$userid = Auth::id();
$postid = "1"; //static post id for testing
$user = User::where('id','=',$userid);
$post = Post::where('id','=',$postid)->first();
$user->comment($post, 'Lorem ipsum ..', 3); // static comment for testing
}
And finally, my posts.blade:
<form action="{{ route('posts.comment') }}" method="POST">
{{ csrf_field() }}
<input type="text" id="commentdata"/> //not using this yet
<div class="form-group">
<input type="submit" class="btn btn-primary" value="Publish" />
<a class="btn btn-primary" href="{{ route('posts.comment') }}">Cancel</a>
</div>
Unfortunately, when I hit by submit button I get:
"Call to undefined method Illuminate\Database\Query\Builder::comment()"
So it seems I need to define a function in my User model? I'm not sure what to do here. I'm hoping some has used this package before.
Update 1:
I'm now using the following PostsController code:
public function comment()
{
$userid = "1"; //static user id for testing
$postid = "1"; //static post id for testing
$user = User::find($userid);
$post = Post::where('id','=',$postid)->first();
$user->comment($post, 'Lorem ipsum ..', 3); // static comment for testing
}
The error remains as "Call to undefined method Illuminate\Database\Query\Builder::comment()"
Call to undefined method Illuminate\Database\Query\Builder
You can be almost certain when you receive this error it's because you've missed out a step: you're calling a method on the query builder not the model. You need to retrieve the model(s) from the query.
Here's your current code:
$user = User::where('id','=',$userid);
You need to retrieve the first model from the results, e.g:
$user = User::where('id','=',$userid)->first();
Although you can improve this by using the find method which accepts a primary key and returns the model, e.g:
$user = User::find($userid);
Then from there you're ready to create the comment:
$user = User::find($userid);
$user->comment($post, 'Lorem ipsum ..', 3);
Example of using the trait:
<?php
namespace App;
use Actuallymab\LaravelComment\CanComment;
class User
{
use CanComment;
}

Resources