Dynamic column/attribute. Stuck. Need inspiration - php

i’m looking for a solution to a problem that I’m having for a while now. Maybe you can inspire me to do this better. I’m trying not to make a basic mistake in the planning process therefore I’m asking you for advice.
I’m having a Contact::model which has few fixed attributes like id etc. Additionally I would like to have different attributes created dynamically for the whole Contact::model. Some user will be given the functionality to add attributes like name, email, address to the whole model. I’ve dropped the idea of programmatically updating the table itself by creating/dropping columns (this would introduce different problems). As for now i've created two additional tables. One with the additional column names [Columns::model] and a pivot table to assign the value to a Contact::model and Column::model.
To list all contacts i’m preparing the ContactColumn table as array where the first key is the contact_id and the second is the column_id, therefore i get the value. This introduces the n+1 issue. This would not be that bad, but with this approach it will be extremely hard (or resource consuming) to order the contacts by dynamic column values, filtering, searching etc.
Can you somehow guide me to a better solution. How can i merge the contact collection with the values for given columns so it looks like it was a fixed table?
<table>
<thead>
<tr>
<th>Fixed columns [i.e. ID]</th>
#foreach ($columns as $column)
<th>{{ $column->name }}</th>
#endforeach
</tr>
</thead>
<tbody>
#foreach ($contacts as $contact)
<tr>
<td>{{ $contact->id }}</td>
#foreach ($columns as $column)
<td>
#if (array_key_exists($column->id, $values[$contact->id]))
{{ $values[$contact->id][$column->id] }}
#endif
</td>
#endforeach
</tr>
#endforeach
</tbody>
</table>
And the $value array.
foreach (ColumnContact::all() as $pivot) {
$values[$pivot->contact_id][$pivot->column_id] = $pivot->value;
}
return $values;
Edit: I've solved it like this
$this->contacts = Contact::when($this->dynamicColumnName, function($query) {
$query->join('column_contact', function ($join) {
$join->on('id', '=', 'column_contact.contact_id')
->where('column_contact.column_id', '=', $this->dynamicColumnName->id);
})
->orderBy('value', $this->orderingDirection);
})
(...)
->paginate(self::PER_PAGE);

Apart from the fixed fields, add an extra JSON field in your schema called 'custom_fields'. Have a look into => https://github.com/stancl/virtualcolumn
Separate table for custom fields is not a good idea because then you have to handle model events separately and so on.

Related

Laravel Limit route parameter dynamically (but only show valid urls)

I cannot wrap my mind around something.
I have a table which I render in my tables.blade.php file like this:
<table>
<thead>
<tr>
<th scope="col">Country</th>
<th scope="col">Member</th>
</tr>
</thead>
<tbody>
#foreach ($members as $member)
#if ($member->continent == 'Europe')
<tr>
<td>
{{ $member->country }}
</td>
<td class="align-middle player-code copy-code">
{{ $member->name }}
</td>
</tr>
#endif
#endforeach
</tbody>
</table>
As you can see you can click on a country which will show you members from one country in a new view (code not shown here).
The route for that single country site in the web.php looks like this:
Route::get('/{country}', 'PageController#show')->name('country');
Everything worked fine until I realized that I could put anything as 'country' and still would get shown the site for the country just with an empty table.
So '/abcde' would get you the view with just a naked table.
So I changed the route like this:
Route::get('/{country}', 'PageController#show')->name('country')->where('country', ('United Kingdom|France|Belgium|South Africa);
//the list is much longer
Ok. So now I have constrained the 'country'-parameter in a pretty static way. And I have the feeling that is not the way it should be done. Because in the end I would like to have URLS that look like this '/united-kingdom', '/france', but now they're looking like this '/United%20Kingdom'.
I saw the answers to this question Laravel clean links without spaces or uppercase , but for me they're not so useful since I'm not working with Eloquent models but the Query Builder (The db tables I get are ready made, I only have to display them).
So my questions are:
How to limit a route parameter more dynamically?
How to display data one way (written like it's also in the table like this 'United Kingdom'), but have a route like this ('united-kingdom')?
I am willing to provide more code or info if you need, I'm just pretty confused and have the feeling I'm overlooking something (big).
Thank you for your time and help!
If you would like a route parameter to always be constrained by a
given regular expression, you may use the pattern method. You should
define these patterns in the boot method of your RouteServiceProvider:
use Illuminate\Support\Facades\DB;
//...
/**
* Define your route model bindings, pattern filters, etc.
*
* #return void
*/
public function boot()
{
$list_of_countries = DB::table('members')->whereNotNull('country')->pluck("country")->unique()->map(function ($name) {
return str_slug($name, '-');
})->reject(function ($name) {
return empty($name);
})->toArray();
$regex = '(' . implode('|', $list_of_countries) . ')';
Route::pattern('{country-slug}', $regex);
parent::boot();
}
routes\web.php
Route::get('/{country-slug}', 'PageController#show')->name('country');
Docs

Creating function for laravel controller/view to load database values

I'm working on updating a laravel blade template to insert some database info into an html table. IN order to do this, I'm having to add new data to the controller for this blade and that's where I'm having some troubles.
I'm still trying to understand more with laravel, so I'm thinking my syntax or methods of creating this data are incorrect but I just can't put my finger on it right now.
In my function below, the $calls_allowed portion was already existing and it works on the page currently. I created the $contact_events portion of the function and that's where my problem is.
IN my view, I created a foreach loop and if statement around the html table in question. The table loads, but it's empty even though there are records in the database for the dealer.
I'm trying to say
if $dealer-> id matches contact_events.dealer_num, load all records for that dealer
contact_events is the table and dealer_num is the column I'm matching, then I'm trying to load the columns from that table (updated_at,method,notes) into the html table.
The affected code is below. The view/route/controller work, it's just this function I'm creating that isn't loading data. Any help is much appreciated.
Controller code:
public function show($id)
{
$d = Dealer::find($id);
if(!$d){
\Session::flash('warning_message', 'Sorry that resource can not be found.');
return redirect()->route('account.dealer.index');
}
$calls_allowed = DB::table('dealers.dealers')->
where('dealer_num', $id)->
pluck('calls_allowed');
$contact_events = DB::table('dealers.contact_events')->
where('dealer_num', $id)->
pluck('updated_at', 'method', 'notes');
if(!empty($calls_allowed)){
$d->calls_allowed = $calls_allowed[0];
} else {
$d->calls_allowed = null;
}
return view('Account.Dealer.show')->with('dealer', $d);
}
View code:
<thead>
<tr>
<th>Contacted Date</th>
<th>Type of Contact</th>
<th>Call Notes</th>
</tr>
</thead>
#foreach($dealer->contact_events as $events)
#if($events->dealer_num = $dealer->id)
<tbody>
<tr>
<td>{{$events->updated_at}}</td>
<td>{{$events->method}}</td>
<td>{{$events->notes}}</td>
</tr>
</tbody>
#endif
#endForeach
It looks like you are not assigning the data to the object after retrieving from database.
$contact_events = DB::table('dealers.contact_events')->
where('dealer_num', $id)->
pluck('updated_at', 'method', 'notes');
// add this
$d->contact_events = $contact_events;
This seems like a perfect time to use the power of Laravel's Eloquent ORM...
Check out the with and has in the Laravel docs
This will require some finessing based on your needs, but it will be something like this:
$d = Dealer::where('id', '=', $id)
->with('contact_events')->first();
This uses Eloquent to get all of the contact_events that belong to the dealer with the $id.
Then you can do something like this
note: this assumes that calls_allowed is a record on the dealer table. if I misunderstood that, you can still run than you can include that just as you have it.
#if(!is_null($dealer->calls_allowed)
#foreach($dealer->contact_events as $events)
<tbody>
<tr>
<td>{{$events->updated_at}}</td>
<td>{{$events->method}}</td>
<td>{{$events->notes}}</td>
</tr>
</tbody>
#endForeach
#endif

How to get all records in laravel Models

I'm not using eloquent, my models are like this.
class drivers extends Model
{
}
I want to display records of all drivers ( one record in each row )
My driver table has field (driver_id,name,tyre_id)
My tyre table has field (id, title)
My bank table has field (id, driver_id, bank)
I want my record to be like this...
Driver Id, Name, Bank Details, Tyre Title
100000111, Ayer, Available, GO
.. so on
For bank details if driver_id has a record in bank table, it should display available otherwise N/A.
$drivers= Drivers::get();
$myarray = ();
foreach ($drivers as $d){
$bank = Bank::where('driver_id',$d->driver_id)->first();
$tyre = Tyre::where('id',$d->tyre_id)->first();
$myarray[] = $d->driver_id;
$myarray[] = $d->name;
$myarray[] = isset($bank) ? "available" ; '';
$myarray[] = $tyre->title;
}
This is what i have tried, I'm to new to laravel, how can i achieve this in laravel or using query like DB Table?
Laravel offers two very useful tools for performing database operations eloquent and query builder. It is advisable to work as much as possible with eloquent and their relationships as it facilitates much of the common operations that we normally need to perform.
Now, if you want to make more complex queries you can use query builder, also, an example of your case using query builder would be something like this:
In your controller you make the query and pass the data to view:
$data = DB::table('driver')
->leftJoin('bank', 'bank.driver_id','=', 'driver.driver_id')
->join('tyre', 'tyre.id','=', 'bank.tyre_id')
->select('driver.driver_id as id',
'driver.name',
'bank.id as bank_id',
'tyre.title')
->groupBy('driver.driver_id')
->get()
And in your view you can use a foreach loop to display the data (and you can use a conditional to display the bank field):
<table>
<thead>
<tr>
<th>Driver ID</th>
<th>Name</th>
<th>Bank Detail</th>
<th>Tyre Title</th>
</tr>
</thead>
<tbody>
#foreach($data as $item)
<tr>
<td>{{ $item->id }}</td>
<td>{{ $item->name }}</td>
<td>{{ isset($item->bank_id) ? "availible":"N/A" }}</td>
<td>{{ $item->title }}</td>
</tr>
#endforeach
</tbody>
</table>
likewise I recommend you read the documentation of eloquent and try to use it.
https://laravel.com/docs/5.5/queries
https://laravel.com/docs/5.5/eloquent
The Good way to Solve this is laravel relations
here the link
laravel documentation
Select your driver table as base table and use relations to get the other table fields;
array_push() function to push values to array
Another way is using DB Facade with joins: Like this:
$users = DB::table('users')
->join('contacts', 'users.id', '=', 'contacts.user_id')
->join('orders', 'users.id', '=', 'orders.user_id')
->select('users.*', 'contacts.phone', 'orders.price')
->get();

Undefined property in Laravel 5

I've looked at other questions that have answered this same error and I understand I have a property that cannot be defined but not sure how to make it defined. First time using Many-to-Many relationship so I'm assuming this is the issue.
Project Table
id
company
stage
status
date_started
date_finished
timestamps
Employee Table
id
name
department
timestamps
Employee_Project Table
id
employee_id
project_id
In both models, I have belongsToMany and for the show function in the ProjectController, I have:
$projects = Project::find($id);
In the view, I want to show company name ($projects->company) and the employees on that project with their respective departments. These do not work:
$projects->employees->name
$projects->employees->department
How do I access these properties?
--Update--
Jeff's answer works but I set up a table like
<thead><th>PM</th><th>AM</th></thead>
<tbody><td>#if($employee->department == 'Project Manager'){{ $employee->name }}</td>
<td>#else($employee->department == 'Account Manager'){{ $employee->name }}</td></tbody>#endif
and this does not work to show the correct employees in their respective sections. Any ideas how to fix this?
Your problem is that $project->employees is a collection, not an individual instance of an employee. You will need to iterate over the collection in order to access each individual employee's name:
foreach($project->employees as $employee) {
echo $employee->name;
echo $employee->department;
}
Update
It looks like you may need to restructure as below, I think you probably want the entire if construct to be within the same table cell, though I could be wrong:
<tbody>
#foreach($project->employees as $employee)
<tr>
<td>
#if($employee->department == 'Project Manager')
{{ $employee->name }}
#elseif($employee->department == 'Account Manager')
{{ $employee->department }}
#else
<!-- What about non project/account managers? -->
#endif
</td>
</tr>
#endforeach
</tbody>
I could be totally wrong, and maybe you want account managers and project managers in different columns:
<tr>
<td>
#if($employee->department == 'Project Manager')
{{ $employee->name }}
#endif
</td>
<td>
#if($employee->department == 'Account Manager')
{{ $employee->name }}
#endif
</td>
</tr>
Be sure to take a look at the documentation on Blade if statements.
Update 2
If you want to order how the related entities are coming back, you can do something like this (assuming you want the employees ordered by their department:
$project->employees()->orderBy('department', 'DESC')->get();
Notice you're no longer accessing the property, you're calling the relation method employees() so that you can modify the query prior to it being executed.
Here's a link to the documentation on ordering queries, and there is also another StackOverflow question on it.
So I figured out how to show the correct person to show up under their respective departments in the table and I wanted to share.
Thanks to #jeff-lambert I added the foreach loop but I had added to the entire row like in his first update. This did not work because the employees were not showing up in their respective departments. For example, account managers were showing up under the project manager . To resolve this, I had to add a foreach loop to each .
<td>
#foreach($projects->employees as $employee)
#if($employee->department == 'Project Manager')
{{ $employee->name }}
#endif
#endforeach
</td>
Hope this helps explain it.

Trying to get object of non-object. (Laravel)

I'm confused here. I've passed
$grade=Grade::all()->whereLoose('id',$gid);
from my controller.
When I access it in my view like:
#foreach($grade as $grade)
{{$grade->grade_name}}
#endforeach
It works fine. But when i try to use it again in my table:
#foreach($grade as $grade)
<tr class="success">
<td></td>
<td><b>Balance</b></td>
<td>{{$grade->fee_status}}</td>
#endforeach
It just throws Trying to get object of non object. What's the problem here? Can anyone help me?
You are reassigning $grade in your loop. It is now a single model. When you try to now iterate that you are iterating a single model which is not what you want to do (that will iterate the public properties of the object).
This will avoid that issue:
$grades = Grade::all()->whereLoose('id',$gid);
// different name for the variable for the current iteration
#foreach ($grades as $grade)
...
#endforeach
#foreach ($grades as $grade)
...
#endforeach
Watch the naming of variables.
On a side note, I am not sure why you are getting all the grades to just find the one with 'id' equal to $gid. If the id field is unique there should only be one and you wouldn't need to retrieve a collection.
$grade = Grade::find($gid);

Categories