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

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()

Related

Order by activity date instead of users followed

I have two tables, the activities table and followings table. I want to get "following" activity the date it was created instead of by the date the user was created.
Schema::create('activities', function (Blueprint $table) {
$table->bigIncrements('id');
$table->integer('user_id')->index();
$table->integer('activity_id')->index();
$table->string('activity_type', 50);
$table->timestamps();
$table->index(['activity_id', 'activity_type']);
});
Schema::create('followings', function (Blueprint $table) {
$table->bigIncrements('id');
$table->integer('user_id');
$table->unsignedBigInteger('follower_id');
$table->dateTime('followed_date')->nullable();
$table->dateTime('unfollowed_date')->nullable();
$table->timestamps();
$table->unique(['user_id', 'follower_id']);
});
Right now I have this code:
Controller:
$followings = $user->following()->get();
View:
#foreach($followings->sortByDesc('id') as $following)
#foreach($following->user->activity->sortByDesc('id') as $activity)
#if($activity->activity_type == 'App\Thread')
<li class="activity">
<div class="activity-user">
<p class="avatar" style="background-color: {{ $activity->user->color }}">
{{ substr($activity->user->name, 0, 1) }}</p>
</div>
<div class="activity-body">
<h4>{{ $activity->user->name }}
published <a
href="/forums/{{ $activity->activity->channel->slug }}/{{ $activity->activity->slug }}/">
{{ $activity->activity->title }}</a></h4>
<p>
{!! BBCode::parse($activity->activity->body) !!}
</p>
<p class="created-at">
{{-- {{ $activity->activity->created_at->format('l') }}
at
{{ $activity->activity->created_at->format('h:i A') }} --}}
{{ $activity->activity->created_at->format('M j, Y') }}
</p>
</div>
</li>
#endif
#endforeach
#endforeach
Model:
public function following()
{
return $this->hasMany(Following::class);
}
Is there anyway for me to reach the polymorphic table and order by the Activity date instead of by the following() date?
Right now, it returns records based on the user's name in the date they posted it, but I would like it strictly by the activity.
For your scenario, you will be better of with a custom DB query like this, I haven't tested it so you will need to adjust for your use case but basic concept for Laravel is pretty much the same.
$following = Following::join('users', 'followings.user_id', '=','users.id')
->join('activites', 'users.id', '=', 'activities.user_id')
->orderBy('activities.created_at')
->select('followings.*')
->get();
Here's how I did it:
Controller
$followings = $user->following()->get();
foreach($followings as $following) {
foreach($following->user->activity as $activity) {
$followingArray[] = $activity->id;
}
}
//dd($followingArray);
if(!empty($followingArray)){
$following = Activity::whereIn('id', $followingArray)->orderBy('created_at', 'desc')->get();
} else {
$following = '';
}
View:
#if(!empty($following))
#foreach($following as $activity)

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

Reorder menu items without affecting database id

I have a menu in my Laravel application which lists tags. I want to be able to add more tags and change their order, but I don't want to affect the underlying database ids for each tag. Because that would break any data I have already associated with them. So I thought I add an extra column to my database called 'display_order' like so:
Database:
Schema::create('tags', function (Blueprint $table) {
$table->increments('id');
$table->integer('display_order');
$table->string('name');
$table->timestamps();
});
Now I want to create a view to let the user change the value of the display_order field. Initially I show a page which prints each tag and its display_order and allows the user to update the value in a text field:
Controller:
public function showTagsOrder()
{
$tags = Tag::all()->sortBy('display_order');
return view('menu.tags', compact('tags'));
}
Route:
Route::get('/menu/tags', ['as' => 'menu_tags', 'uses' => 'MenuOrderController#showTagsOrder']);
View:
{!! Form::open(['action' => 'MenuOrderController#updateTagsOrder']) !!}
#foreach($tags->chunk(18) as $chunk)
<div class="col">
#foreach($chunk as $tag)
<p><label>{!! $tag->name !!}</label><input type="text" name="tag_array[{!! $tag->id !!}]" value="{!! $tag->display_order !!}" class="numeric" />
#endforeach
</div>
#endforeach
<p>{!! Form::submit('Submit') !!}
{!! Form::close() !!}
When the form is submitted I then have the following function in my controller:
public function updateTagsOrder(TagsMenuRequest $request)
{
$i = 1;
foreach($request->tag_array as $tag_value)
{
$tag = new Tag;
$tag = Tag::find($i);
$tag->display_order = $tag_value;
$tag->save();
$i++;
}
flash()->overlay('The order of tags in the menu has been udpated!', 'Congratulations');
return redirect('/');
}
But this doesn't work. How would I rewrite this so I assigned the correct value for display_order to each tag. I'd be very grateful for your help.
foreach($request->tag_array as $tag_id => $tag_value)
{
$tag = Tag::find($tag_id);
$tag->display_order = $tag_value;
$tag->save();
}

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

Laravel 4: Eloquent soft deletes and relationships

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();

Resources