Update a table with composite key using Laravel Eloquent - php

I am badly stuck in a table with a composite key. I am trying to use the eloquent to filter the record by two primary keys. But I came to know that eloquent doesn't support composite keys. I have gone through many solutions but no solution is clear enough for a beginner to understand.
a solution code says to edit the model class with the following code:
<?php
class CustomerAddress extends Model {
protected function setKeysForSaveQuery(Builder $query)
{
$query
->where('Customer_No', '=', $this->getAttribute('Customer_No'))
->where('Address_Name', '=', $this->getAttribute('Address_Name'));
return $query;
}
}
I want to perform the update function using the resource controller provided by laravel:
my code looks like this:
/**
* Update the specified resource in storage.
*
* #param \Illuminate\Http\Request $request
* #param int $id
* #return \Illuminate\Http\Response
*/
public function update(Request $request, $sem_course_id, $clo_id)
{
$id = $this->getKeyForSaveQuery();
$request->validate([
'assignments_weightage'=>'required',
'quizes_weightage'=>'required',
'project_weightage'=>'required',
'class_participation_weightage'=>'required',
'mid_weightage'=>'required',
'final_weightage'=>'required',
]);
$course = CoursesMarksScheme::find($clo_id,$sem_course_id);
$course->assignments_weightage = $request->get('assignments_weightage');
$course->quizes_weightage = $request->get('quizes_weightage');
$course->project_weightage = $request->get('project_weightage');
$course->class_participation_weightage = $request->get('class_participation_weightage');
$course->mid_weightage = $request->get('mid_weightage');
$course->final_weightage = $request->get('final_weightage');
$course->save();
return redirect('/coursesmarks');
}
Also, please guide me on how to use the resource controller with primary keys as it is throwing an error for a few arguments?
Please help me. I am really exhausted reading a lot of online articles but unable to resolve the problem.

As I see solution code which you found is only for update, but don't for find. Laravel support composite key in migrations, but don't in eloquent.
Maybe it is better, use simple solution like this and don't waste time:
CoursesMarksScheme::where(['clo_id' => $clo_id, 'sem_course_id' => $sem_course_id])
->update($request);
I suppose necessary fields are in $fillable variable in CoursesMarksScheme model.

Related

Return relationships in Laravel show route

When I create a CRUD controller, this is the show route created by default:
/**
* Display the specified resource.
*
* #param \App\Team $team
* #return \Illuminate\Http\Response
*/
public function show(Team $team)
{
//
}
$team is an object here, an instance of Team. If I do this I have the correct object passed to blade:
public function show(Team $team)
{
return view('admin.teams.show', ['team' => $team]);
}
But, Team has a many-to-many relationship with another model called Player, and this relationship is defined as such from the Team side:
public function players() {
return $this->belongsToMany(Player::class);
}
In my show method, I'd like to return the $team with its related players. But since $team is already an object and not a query builder, it's too late to do something like
$team->with('players')
So how do I get the related players here? I know I can do something like:
public function show(Team $team)
{
$team_extended = Team::where('id', $team['id'])->with('players')->first();
return view('admin.teams.show', ['team' => $team_extended]);
}
But it feels like hacking a functionality that should be there by default. Is there a built-in Laravel way to do this or am I just inventing hot water and should take the approach I used in my solution above?
If you've already got your Team model loaded, you can load a relationship without having to completely re-create it using the ->load() method:
public function show(Team $team){
$team->load("players");
return view("admin.teams.show", ["team" => $team]);
}
Note however, this isn't required unless you need to modify the default content of $team->players. When you trying to access $team->players say in your admin.teams.show view, if that property doesn't already exist (as it would using ->with(["players"]) or ->load("players"), Laravel will load it automatically.

Relationship use difference connection

I need to get data from a different database in a relationship, like so:
Table1::development(1)->with([ 'column' => function($q) {
$q->connection('live');
}])->first()
development is a local scope on my Table1 model, it just performs a where clause.
I'm getting an error with the above code which I can't figure out:
Error: BadMethodCallException: Call to undefined method Illuminate\Database\Query\Builder::connection() in /var/www/vendor/illuminate/database/Query/Builder.php:2445
Can someone help me out?
Managed to figure it out, not sure if it's the best way. I just added my condition to the construct in my model and then swapped the connection there.
/**
* Create a new Eloquent model instance.
*
* #param array $attributes
* #return void
*/
public function __construct(array $attributes = [])
{
parent::__construct();
if (env('MODE') === 'mode2') {
$this->setConnection('live');
}
}

Am I doing eager loading correctly? (Eloquent)

I have a method that needs to pull in information from three related models. I have a solution that works but I'm afraid that I'm still running into the N+1 query problem (also looking for solutions on how I can check if I'm eager loading correctly).
The three models are Challenge, Entrant, User.
Challenge Model contains:
/**
* Retrieves the Entrants object associated to the Challenge
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function entrants()
{
return $this->hasMany('App\Entrant');
}
Entrant Model contains:
/**
* Retrieves the Challenge object associated to the Entrant
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function challenge()
{
return $this->belongsTo('App\Challenge', 'challenge_id');
}
/**
* Retrieves the User object associated to the Entrant
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo('App\User', 'user_id');
}
and User model contains:
/**
* Retrieves the Entrants object associated to the User
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function entrants()
{
return $this->hasMany('App\Entrant');
}
The method I am trying to use eager loading looks like this:
/**
* Returns an array of currently running challenges
* with associated entrants and associated users
* #return array
*/
public function liveChallenges()
{
$currentDate = Carbon::now();
$challenges = Challenge::where('end_date', '>', $currentDate)
->with('entrants.user')
->where('start_date', '<', $currentDate)
->where('active', '1')
->get();
$challengesObject = [];
foreach ($challenges as $challenge) {
$entrants = $challenge->entrants->load('user')->sortByDesc('current_total_amount')->all();
$entrantsObject = [];
foreach ($entrants as $entrant) {
$user = $entrant->user;
$entrantsObject[] = [
'entrant' => $entrant,
'user' => $user
];
}
$challengesObject[] = [
'challenge' => $challenge,
'entrants' => $entrantsObject
];
}
return $challengesObject;
}
I feel like I followed what the documentation recommended: https://laravel.com/docs/5.5/eloquent-relationships#eager-loading
but not to sure how to check to make sure I'm not making N+1 queries opposed to just 2. Any tips or suggestions to the code are welcome, along with methods to check that eager loading is working correctly.
Use Laravel Debugbar to check queries your Laravel application is creating for each request.
Your Eloquent query should generate just 3 raw SQL queries and you need to make sure this line doesn't generate N additional queries:
$entrants = $challenge->entrants->load('user')->sortByDesc('current_total_amount')->all()
when you do ->with('entrants.user') it loads both the entrants and the user once you get to ->get(). When you do ->load('user') it runs another query to get the user. but you don't need to do this since you already pulled it when you ran ->with('entrants.user').
If you use ->loadMissing('user') instead of ->load('user') it should prevent the redundant call.
But, if you leverage Collection methods you can get away with just running the 1 query at the beginning where you declared $challenges:
foreach ($challenges as $challenge) {
// at this point, $challenge->entrants is a Collection because you already eager-loaded it
$entrants = $challenge->entrants->sortByDesc('current_total_amount');
// etc...
You don't need to use ->load('user') because $challenge->entrants is already populated with entrants and the related users. so you can just leverage the Collection method ->sortByDesc() to sort the list in php.
also, You don't need to run ->all() because that would convert it into an array of models (you can keep it as a collection of models and still foreach it).

Laravel getDirty and getOriginal give same values on updating

Using Laravel 5.4
I have a Job model which has a enum field on it with different statuses. These statuses change in many different places. I made a JobHistory model and migration which tracks those changes .
On my Job model i define the new laravel 5.4 way of tracking eloquent events:
/**
* The events that should be fired for eloquent actions
*
* #var array
*/
protected $events = [
'updating' => JobChangedStatus::class
];
Status changes are done like this:
/**
* Change the job status
*
* #param $status
*/
public function changeStatus($status)
{
$this->update([
'status' => $status,
]);
}
EventServiceProvider:
'App\Events\Jobs\JobChangedStatus' => [
'App\Listeners\Jobs\CreateJobHistory',
],
CreateJobHistory Listener:
$job = $event->job;
$jobHistory = new JobHistory();
$jobHistory->old_status = $job->getOriginal('status');
$jobHistory->new_status = $job->status;
$jobHistory->job()->associate($job);
$jobHistory->executor()->associate(Auth::user());
$jobHistory->save();
When i change my job status from e.g New to In_progress
My JobHistory table will look like this:
So the new_status and the old_status both give the old value. I tried using $job->getDirty() but when i print it it just gives back a empty array.
What am i doing wrong?
Usually I would achieve this inside an Observer. It feels a little awkward to see the events/listener setup like that.
In your AppServiceProvider.php:
public function boot()
{
App\Job::observe(App\Observers\JobObserver::class);
}
And then in App\Observers\JobObserver.php:
use App\Job;
use App\JobHistory;
class JobObserver{
public function updating(Job $job)
{
$jobHistory = new JobHistory();
$jobHistory->old_status = $job->getOriginal('status');
$jobHistory->new_status = $job->status;
$jobHistory->job()->associate($job);
$jobHistory->executor()->associate(Auth::user());
$jobHistory->save();
}
}
For eloquent models it makes the most sense (just my opinion) to use observers. The events/listeners model I use for listening to mail events, job events, possibly notifications, etc.
The other reason this may help you in your situation is if the event is being queued instead of ran synchronously, you will end up with a race condition and most cases the model will have been saved and that is why getDirty() has no keys. With observers, the operations are always ran synchronously and you will not run into a timing issue.

Autogenerate model classes with Laravel 4 (aka using an existing database with L4)

I've designed my database in MySQL Workbench, and have all my foreign keys setup, etc.
I'm wanting to use this DB schema with Laravel 4, however from the docs there is no word of any sort of ability to work with an existing set of database tables. From my understanding, other frameworks such as Cake with its 'Baking' allow you to automatically generate your model classes based on the tables already in your database.
I've looked around everywhere and cant see anything about this at all for Laravel 4. The closest thing I've found is Jeffrey Way's Generator package for artisan, however this only creates the base model, and doesn't detect established foreign key relationships.
Is this even possible with Laravel 4 or am I going to have to just do it all manually?
The good news is that Antonio just finished his MySQL WorkBench to Eloquent ORM converter
This is a beautiful solution but comes a way to late for me but may help you a lot.
Update: The link isn't working in the moment. The wabpage says "We are redesigning things, will be back soon!". I allready sent antonio an email asking him, when this service will be available again.
Antonio said that it'll be back but there is no estimated time of arrival. We have to wait..
cakePHP does a great job at fleshing out your whole project from the DB schema already in place. Laravel currently does not support anything like this. One of the minor features still holding me back from adopting laravel.
Hmm I had the same issue and I wrote a little script myself which generates base classes and solves the foreign key issues. It's a basic solution and only determines "hasOne" relations, which you might have to change to hasMany later on. I used a Controller and build my code Template in a view:
Controller:
namespace Admin;
/**
* just a quick helper to generate model classes
* from mysql to Eloquent style ones..
* #author Mario
*/
class ModelController extends \BaseController {
/**
* save Classes in folder of choice
*
* #return void
*/
public function create($folder)
{
$sql = "SELECT * FROM information_schema.tables WHERE table_schema = 'UR_SCHEMA'";
$tables = \DB::select($sql);
$sql2 = "select * from information_schema.`KEY_COLUMN_USAGE` where constraint_schema = 'UR_SCHEMA' order by table_name";
$keys = \DB::select($sql2);
$meta = $this->sortOutMetadata($keys);
foreach ($tables as $table) {
$metaData = null;
if(!empty($meta[$table->TABLE_NAME])){
$metaData = $meta[$table->TABLE_NAME];
}
$code = \View::make('model.start', array('table' => $table, 'meta' => $metaData))->render();
file_put_contents($folder.DIRECTORY_SEPARATOR.ucfirst(camel_case($table->TABLE_NAME).'.php'), $code);
}
}
/**
* provide structure indexed by table
*
* #param type $keys
* #return type
*/
private function sortOutMetadata($keys)
{
$return = array();
foreach ($keys as $key) {
if ($key->CONSTRAINT_NAME == 'PRIMARY') {
$return[$key->TABLE_NAME]['pk'] = $key->COLUMN_NAME;
} elseif (!empty($key->REFERENCED_TABLE_NAME)) {
//one way
$return[$key->TABLE_NAME]['fk'][] = array('column' => $key->COLUMN_NAME,
'refColumn' => $key->REFERENCED_COLUMN_NAME,
'refTable' => $key->REFERENCED_TABLE_NAME,);
//and the other
$return[$key->REFERENCED_TABLE_NAME]['fk'][] = array('column' => $key->REFERENCED_COLUMN_NAME,
'refColumn' => $key->COLUMN_NAME,
'refTable' => $key->TABLE_NAME,);
}
}
return $return;
}
}
My view Template (pretty much my Class Template)
<?php echo '<?php'; ?>
namespace Model\Base;
use Model\Model;
class <?php echo ucfirst(camel_case($table->TABLE_NAME));?> extends Model {
/**
* #var String
*/
protected $table = '<?php echo $table->TABLE_NAME;?>';
<?php if (isset($meta['pk'])):?>
/**
* #var String
*/
protected $primaryKey = '<?php echo $meta['pk'];?>';
/**
* attributes not writable from outside
* #var mixed
*/
protected $guarded = array('<?php echo $meta['pk'];?>');
<?php endif;?>
/**
* Timestamps we dont want here
* #var Boolean
*/
public $timestamps = false;
<?php if (isset($meta['fk'])):?>
<?php foreach($meta['fk'] as $keys):?>
/**
* #return HasOne
*/
public function <?php echo camel_case($keys['refTable']);?>()
{
return $this->hasOne('Model\<?php echo ucfirst(camel_case($keys['refTable']));?>', '<?php echo $keys['refColumn'];?>', '<?php echo $keys['column'];?>');
}
<?php endforeach;?>
<?php endif;?>
}
Then Simply generate your base classes by giving it the folder: (from wherever you prefer)
$controller = new \Admin\ModelController();
$controller->create(__DIR__ . DIRECTORY_SEPARATOR . 'tmpModel');
This gave me some decent way to get my base classes Auto generated the way I needed. Remember you got to be able to see the information_schema schema with your db user.
Hope this helps

Categories