Laravel 4: Eloquent soft deletes and relationships - laravel-4

I have 2 tables, clients and projects and a project is associated with a client. Both the clients and projects implement soft deletes to maintain the relationships for archival reasons i.e. even if I delete a client the project will still have the client information attached.
My problem is that when I delete the client, the reference becomes inaccessible from the project and throws an exception. What I would like to do is soft delete the client but retain the client data from the project relationship.
My blade code is as follows:
#if ($projects->count())
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Name</th>
<th>Client</th>
</tr>
</thead>
<tbody>
#foreach ($projects as $project)
<tr>
<td>{{{ $project->name }}}</td>
<td>{{{ $project->client->name }}}</td>
<td>{{ link_to_route('projects.edit', 'Edit', array($project->id), array('class' => 'btn btn-info')) }}</td>
<td>
{{ Form::open(array('method' => 'DELETE', 'route' => array('projects.destroy', $project->id))) }}
{{ Form::submit('Delete', array('class' => 'btn btn-danger')) }}
{{ Form::close() }}
</td>
</tr>
#endforeach
</tbody>
</table> #else There are no projects #endif
Here are the migrations:
Schema::create('clients', function(Blueprint $table) {
// Table engine
$table->engine = 'InnoDB';
// Increments
$table->increments('id');
// Relationships
// Fields
$table->string('name');
// Timestamps
$table->timestamps();
// Soft deletes
$table->softDeletes();
});
Schema::create('projects', function(Blueprint $table) {
// Table engine
$table->engine = 'InnoDB';
// Increments
$table->increments('id');
// Relationships
$table->integer ('client_id');
// Fields
$table->string('name');
// Timestamps
$table->timestamps();
// Soft deletes
$table->softDeletes();
// Indexes
$table->index('client_id');
});
Many thanks.

This was solved by using the withTrashed() method when defining the relation in the model.
Original code:
public function client() {
return $this->belongsTo('Client');
}
Solution:
public function client() {
return $this->belongsTo('Client')->withTrashed();
}
Many thanks to Glad to Help.

In my case I can't modify the function client as Wally proposed, cause it's being used within other models and controllers that I don't want it to get Clients ->withTrashed().
In this case, here is two solutions I propose:
Specify ->withTrashed() when eager loading client:
$projects = Project::with(['client' => function($query){ $query->withTrashed(); }])->get();
Or create a new client functions ->withTrashed()
public function client() {
return $this->belongsTo('Client');
}
// The new function
public function client_with_trash() {
return $this->belongsTo('Client')->withTrashed();
}
When eager loading now:
$projects = Project::with(['client_with_trash'])->get();

Related

Trying to get property 'name' of non-object. Model relationship not working

I am getting an error message relating to my model relationship and have an identical section of my other app that works fine. I've looked through everything many times and cannot see any reason my model relationship should not work. When I try to access the model relationship in the view, I get "Trying to get property 'name' of non-object"
I have the following in my Controller:
public function edit($id)
{
//
$plansubmission = PlanSubmission::find($id);
$hradmins = PSPlanToHRAdminMapping::where('plan_submission_plan_id', $id)->get();
return view('planbuilder.add-hr-administrators', compact('plansubmission', 'hradmins'));
I have the following in my View:
<h3 class="py-3">Current HR Administrators with Online Access</h3>
<table class="table">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Email Address</th>
</tr>
</thead>
<tbody>
#foreach($hradmins as $hradmin)
<tr>
<td> {{$hradmin->hradmin->name }}</td>
<td> {{$hradmin->hradmin->email }}</td>
#endforeach
</tr>
</tbody>
</table>
I have the following in my Model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class PSPlanToHRAdminMapping extends Model
{
//
protected $guarded = [];
public function hradmin()
{
return $this->belongsTo('App\HRAdmin');
}
}
I have the following Migration:
{
Schema::create('p_s_plan_to_h_r_admin_mappings', function (Blueprint $table) {
$table->unsignedBigInteger('plan_submission_plan_id');
$table->unsignedBigInteger('h_r_admin_id');
$table->timestamps();
$table->foreign('plan_submission_plan_id')->references('id')->on('plan_submissions');
$table->foreign('h_r_admin_id')->references('id')->on('h_r_admins');
});
}
It's possible that for some records you don't have any record for hradmin relationship so you can try adding optional helper like so:
<td> {{optional($hradmin->hradmin)->name }}</td>
<td> {{optional($hradmin->hradmin)->email }}</td>
for example to avoid error

Laravel 5.5 - Blade templates, querying deeper into a hasMany relationship

I am wondering if I can query a hasOne -> hasMany relationship in blade. I can currently get a count on how many models exist in my blade using "$participant->messages->count()" but I would like to check the model and count other things. For instance I would like to run the following query in blade:
{!! $participant->messages->where($this->messages->mediaURL, "=", null)->count() !!}
I get the following error:
Property [mediaURL] does not exist on this collection instance.
Here is my controller function
public function showParticipants()
{
$participants = Participant::all();
// $messages = $participants->participant_id->messages;
return view('home')->with(['participants'=> $participants, 'messages'=>'hi']);
}
Part of my Participant model:
public function messages()
{
return $this->hasMany('App\Message', 'message_id', 'participant_id');
}
Part of my Message model:
public function participant()
{
return $this->belongsTo(Participant::class);
}
My message table structure:
public function up()
{
Schema::create('messages', function (Blueprint $table) {
$table->engine = 'InnoDB';
$table->increments('id');
$table->integer('message_id')->unsigned();
$table->string('message_content')->nullable();
$table->string('mediaSID')->index()->nullable();
$table->string('messageSID')->index()->nullable();
$table->string('mediaURL')->index()->nullable();
$table->binary('media')->nullable();
$table->string('filename')->index()->nullable();
$table->string('MIMEType')->nullable();
$table->timestamps();
});
Schema::table('messages', function($table) {
$table->foreign('message_id')->references('participant_id')->on('participants')->onDelete('cascade');
});
}
My Participant DB structure:
public function up()
{
Schema::create('participants', function (Blueprint $table) {
$table->engine = 'InnoDB';
$table->string('participant_id')->unique();
$table->dateTime('appointmentDate')->nullable();
$table->dateTimeTz('timezoneOffset')->nullable();
$table->dateTime('appointmentDate_twoWeeks')->nullable();
$table->dateTime('notificationTime')->nullable();
$table->integer('notificationTally')->nullable();
$table->boolean('studyCompleted')->default(0);
$table->boolean('subscribed');
$table->timestamps();
});
}
My blade just to give all info:
#isset($participants)
#foreach ($participants as $participant)
<tr>
<td>
{!! $participant->participant_id !!}
</td>
<td>
{!! $participant->subscribed !!}
</td>
<td>
{!! $participant->notificationTime !!}
</td>
<td>
{!! $participant->notificationTally !!}
</td>
<td>
{!! $participant->studyCompleted !!}
</td>
<td>
{!! $participant->messages->count() !!}
</td>
<td>
{!! $participant->messages->where($participant->messages->mediaURL, "=", null)->count() !!}
</td>
</tr>
#endforeach
#endisset
I believe the issue is $this->messages->mediaURL. With the Query Builder, if you're wanting to refer to a column on a table you would just need to pass it a string. Also when you're querying a relationship you should use the method and not the property e.g. $participant->messages(). Lastly, when querying for a null column you can use the whereNull method.
{!! $participant->messages()->whereNull('mediaURL')->count() !!}
You are using the "magic" __get() on messages that returns a collection because it is a has many relationship.
$participant->messages->where($this->messages->mediaURL, "=", null)->count()
should be
$participant->messages()->where($participant->messages()->first()->mediaURL, "=", null)->count()

Changing menus order by dragging - Laravel

In my custom CMS I have created CRUD menu and in the frontend I'm displaying it successfully.
Now I'm trying to find any solution to change order by dragging these menu items in the admin section, but still no luck. Does anyone knows how to achieve this with Laravel? Also I use third party Laravel translatable, so I'll explain in my following code:
Menu model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Dimsav\Translatable\Translatable;
class Menu extends Model
{
use Translatable;
public $translatedAttributes = ['title', 'url', 'status'];
}
MenuTranslation model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class MenuTranslation extends Model
{
//
}
create_menus_table migration:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateMenusTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('menus', function (Blueprint $table) {
$table->increments('id');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('menus');
}
}
create_menus_translations_table migration:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateMenuTranslationsTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('menu_translations', function (Blueprint $table) {
$table->increments('id');
$table->integer('menu_id')->unsigned();
$table->string('locale')->index();
// The actual fields to store the content of your entity. You can add whatever you need.
$table->string('title');
$table->string('url')->nullable();
$table->string('status');
$table->unique(['menu_id', 'locale']);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('menu_translations');
}
}
My view index menu:
#extends('layouts.admin')
#section('content')
#if(session()->has('success_message'))
<div class="alert alert-success">
{{ session()->get('success_message') }}
</div>
#endif
<h1>Users</h1>
<a class="btn btn-primary" href="{{ route('menus.create') }}">Create new menu</a>
<table class="table">
<thead>
<tr>
<th>Id</th>
<th>Title</th>
<th>Status</th>
<th>Created</th>
<th>Operations</th>
</tr>
</thead>
<tbody>
#foreach($menus as $menu)
<tr>
<td>{{ $menu->id }}</td>
<td>{{ $menu->title }}</td>
<td>{{ $menu->status ? 'Active' : 'Disabled' }}</td>
<td>{{ $menu->created_at->diffForHumans() }}</td>
<td>
<a class="btn btn-primary btn-edit" href="{{ route('menus.edit' , $menu->id) }}">Edit</a>
<form action="{{ route('menus.destroy', $menu->id) }}" method="POST">
{{ csrf_field() }}
{{ method_field('DELETE') }}
<input type="submit" class="btn btn-danger btn-delete" value="Delete">
</form>
</td>
</tr>
#endforeach
</tbody>
</table>
#endsection
As you can see in the view menu index I'm displaying the created menu items. The key is to use some js and Ajax I guess and with dragging to change the id's in the menus table or to create a new column in the database (order).
So, the question again is: how can I drag these menu items and change order in the database by using Laravel?
Well, it is all not laravel specific question but here is the answer.
So, For the frontend part, you'll need to use a plugin for Drag and Drop. E.g. Sortable
Sortable plugin gives you all the events of drag and drop. So you can simply create a hidden field or just create a variable in the javascript and pass that sorted value in the POST request when click on save function.
Now, you will need to add that data in a table sorted by menu_id.
And by clicking on save order, you will send all the newly ordered elements with their respective id.
And on the backend side, you will just need to loop through and update those elements.
Like this:
foreach($newSort as $key => $value):
$menuTranslation = MenuTranslation::findOrFail($key);
$menuTranslation->update(['menu_id' => $value]);
#endforeach;
All the best.
I've finally solved the problem!
I'm using this plugin for Laravel:
https://github.com/boxfrommars/rutorika-sortable
It's great for sorting eloquent. With some tweaks, lot of reading and testing, now it works great. :)

create authentication laravel 5

I have these 2 tables with many to many relationship connected using a junction table. The idea is that I can get the user data to make the user an author in a journal data and it works so far.
User table :
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email')->unique();
$table->integer('phone')->nullable();
$table->string('address')->nullable();
$table->string('password');
$table->rememberToken();
$table->enum('level', ['admin', 'author']);
$table->timestamps();
});
}
Journal table :
public function up()
{
Schema::create('journal', function (Blueprint $table) {
$table->increments('id');
$table->string('title', 255);
$table->text('abstract');
$table->text('file');
$table->integer('id_edition')->unsigned();
$table->timestamps();
});
}
Junction table :
public function up()
{
Schema::create('penulis', function (Blueprint $table) {
// Create tabel penulis
$table->integer('id_user')->unsigned()->index();
$table->integer('id_journal')->unsigned()->index();
$table->timestamps();
// Set PK
$table->primary(['id_user', 'id_journal']);
// Set FK penulis --- user
$table->foreign('id_user')
->references('id')
->on('users')
->onDelete('cascade')
->onUpdate('cascade');
// Set FK penulis --- journal
$table->foreign('id_journal')
->references('id')
->on('journal')
->onDelete('cascade')
->onUpdate('cascade');
});
}
Now I have this view that shows journals data along with the buttons to edit or delete it. What I want to make is that only the user that are entitled as the author of the journal that has the capacity to access these buttons. How do I make it ? below is the view code :
<tbody>
<?php foreach ($journal_list as $journal): ?>
<tr>
<td style="">{{ $journal->title }}</td>
#if (Auth::check())
<td style="width: 130px; overflow: hidden;">
<div class="box-button">
{{ link_to('journal/' . $journal->id . '/edit?edition=' . $edition->id, 'Edit', ['class' => 'btn btn-warning btn-sm']) }}
</div>
<div class="box-button">
{!! Form::open(['method' => 'DELETE', 'action' => ['JournalController#destroy', $journal->id]]) !!}
{!! Form::submit('Delete', ['class' => 'btn btn-danger btn-sm']) !!}
{!! Form::close() !!}
</div>
</td>
#endif
</tr>
<?php endforeach ?>
</tbody>
Sorry for my bad English and if my question is stupid. Thanks!
You need to use a combination of middleware and Gate facade.
Generate a policy
Write a policy
Like this:
public function edit-journal(User $user, Journal $journal)
{
return $user->id === $journal->user_id;
}
public function delete-journal(User $user, Journal $journal)
{
return $user->id === $journal->user_id;
}
3. You can now use the Gate facade with blade
Like this:
#can('edit-journal', $journal)
<div class="box-button">
{{ link_to('journal/' . $journal->id . '/edit?edition=' . $edition->id, 'Edit', ['class' => 'btn btn-warning btn-sm']) }}
</div>
#endcan
#can('delete-journal', $journal)
<div class="box-button">
{!! Form::open(['method' => 'DELETE', 'action' => ['JournalController#destroy', $journal->id]]) !!}
{!! Form::submit('Delete', ['class' => 'btn btn-danger btn-sm']) !!}
{!! Form::close() !!}
</div>
#endcan
You will have to register a middleware for your edit and delete route. Your routes should look like:
//Routes
Route::get('journal/' . {$journal_id} . '/edit', ['as'=>'editJournal','middleware' => 'journal:edit', 'uses'=>'JournalController#edit']
//You need to change your delete form so the action points to that route
Route::delete('journal/' . {$journal_id}, ['as'=>'deleteJournal','middleware' => 'journal:delete', 'uses'=>'JournalController#destroy']
In your middleware, you should have something like:
//Journal Middleware
public function handle($request, Closure $next, $role)
{
$parameters = $request->route()->parameters();
$journal = Journal::findOrFail($parameters['journal_id']);
if (Gate::allows($role.'-journal', $journal)) {
return $next($request);
}else{
abort(403, "You do not have the permission to ".$role." this journal")
}
}
What I;ve done in similar cases is to add a function inside the model that you want to check with all the logic. So for example in your case would be something like:
/Model/Journal.php
public function canBeModifiedByUser($user_id){
//Check all the things that you want
}
Then in the view you can do something like:
if($journal->canBeModifiedByUser($journal->user->id))
Also I would suggest you to check some ACL packages, it might be an overkill for you atm but it might just be what you need.
I would suggest using gates
in your auth service provider you can do
$gate->define('can-modifiy', function ($user) {
// whatever code you want to determine if the user can eg
return $user->hasRole('admin');
});
then in your views you can use #can
#can ('can-modify')
<button>delete</button>
#endcan
This can also be used in your controllers with
$this->authorize('can-modify');
or
Gate::allows('can-modify');
This is in the docs at https://laravel.com/docs/5.3/authorization#writing-gates

Many to Many form binding

How to binding a input with many to many relationship data?
My relationship is: a Model has many Damages, and a Damage has many Models. in pivot table exists a price field.
I need populate a input with price data.
{{ Form::input('number', "prices[{$model->id}][{$damage->id}]") }}
My Model:
class Model extends \BaseModel {
public function damages()
{
return $this->belongsToMany('Damage', 'prices_damages', 'model_id', 'damage_id')
->withPivot('price')
->withTimestamps();
}
}
Pivot table
Schema::create('prices_damages', function(Blueprint $table)
{
$table->increments('id');
$table->integer('model_id')->unsigned();
$table->integer('damage_id')->unsigned();
$table->float('price')->nullable();
$table->timestamps();
});
Controller
/**
* Display a index dashboard page.
*
* #return \Illuminate\Http\Response
*/
public function getDamages()
{
$models = \Model::orderBy('order')->get();
$damages = \Damage::orderBy('order')->get();
return $this->render('Avarias', 'prices.damages', compact('models', 'damages'));
}
View:
<table class="table-striped table-header-rotated">
<thead>
<tr>
<th></th>
#foreach ($damages as $damage)
<th class="vertical"><div><span>{{ $damage->name }}</span></div></th>
#endforeach
</tr>
</thead>
<tbody>
#foreach ($models as $model)
<tr>
<td>{{ $model->fullname }}</td>
#foreach ($damages as $damage)
<td>
{{ Form::input('number', "prices[{$model->id}][{$damage->id}]", null, ['min' => 0, 'step' => 0.01]) }}
</td>
#endforeach
</tr>
#endforeach
</tbody>
</table>
You can't bind a collection (in the sense of laravel form model binding), so you can do this:
#foreach ($model->damages as $damage)
{{ Form::input('number', "damages[{$damage->id}][price]", $damage->pivot->price) }}
#endforeach

Resources