Implementing a Laravel 4 Pivot Table using Eloquent - mysql

I've got the following structure in my database:
-shared_resources table
-tags table
-shared_resource_tag table
There is a many to many relationship between shared_resources and tags. When I am creating a shared_resource I do the following:
Add the shared_resource to the shared_resources table
Add the tags to the tags table
Add the shared_resource_id and the tag_id to the shared_resource_tag table
I can manage to get through steps 1 and 2 but for some reason I am unable to make any entries to the pivot table. I don't know why. I set up the relationships in my models accordingly:
SharedResource:
class SharedResource extends Eloquent{
public function tags(){
return $this->belongsToMany('Tag');
}
}
Tag:
class Tag extends Eloquent{
public function sharedResources(){
return $this->belongsToMany('SharedResource');
}
}
Then when I am creating the entries I do this:
$tags = Array();
$tags = explode(',', Input::get('tags'));
foreach($tags as $tag){
$newTag = new Tag;
$newTag->name = $tag;
$newTag->save();
}
//Pivot table entry
$resource->tags()->sync($tags);
$resource->save();
The code above runs into an error:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'shared_resource_id' in 'where clause' (SQL: select `tag_id` from `shared_resource_tag` where `shared_resource_id` is null)
I'm very confused as to whats going on, I understood that Eloquent made implementing these n:n relationships easy.

The sync() method will need the id's of the tags, and not the string names which you are giving it. You can try something like:
$tags = Array();
$tagIds = Array();
$tags = explode(',', Input::get('tags'));
foreach($tags as $tag){
$newTag = new Tag;
$newTag->name = $tag;
$newTag->save();
$tagIds[] = $newTag->id;
}
//Pivot table entry
$resource->tags()->sync($tagIds);
$resource->save();
You can find more info here under Using Sync To Attach Many To Many Models

$tags = explode(',', Input::get('tags'));
// Create or add tags
$tagIds = array();
if ( $tags )
{
$found = $this->tag->findOrCreate( $tags );
foreach ( $found as $tag )
{
$tagIds[ ] = $tag->id;
}
}
// Assign set tags to model
$model->tags()->sync( $tagIds );

Related

Can I use AUTO_INCREMENT in my laravel project?

I have an Eloquent model that has a column that is a number composed of various codes and at the end a number that should increment automatically. This last number is the same number I use to define the primary key of my model, so my question is:
Is there a way to use the AUTO_INCREMENT variable from my MySQL database?
If not, what is the best way to create a variable in Laravel that automatically increments?
example:
$code1 = $this->getCode1();
$code2 = $this->getCode2();
$autoIncr = ...;
$final_code = $code1 . $code2 . $autoIncr;
you can create a function that returns the next id (autoincrement) of your table and the insert the entry in your table;
$code1 = $this->getCode1();
$code2 = $this->getCode2();
$autoIncr = $this->getNextAutoincrement();
$final_code = $code1 . $code2 . $autoIncr;
//Insert in your table
$row = YourModel::create([
'finalCode' => $final_code
]);
private function getNextAutoincrement() {
//don't forget import DB facade at the beginning of your class
$id = DB::select("SHOW TABLE STATUS LIKE 'YOUR_TABLE_NAME'");
$next_id=$id[0]->Auto_increment;
return $next_id;
}
Hope I've helped you

Yii2 insert partial data into table

I'm inserting into a table data coming from another table which is basically a subset of the first table.
$partialProduct = partialProducts::find()
->where(['company_id' => $company_id])
->andWhere(['productName' => $productName])->one();
$partialProductData = ($partialProductData['attributes']);
$product = Yii::$app->db->createCommand()
->insert('products', [
'field1' => $field1value,
...
...
'fieldN' => $fieldN,
])->execute();
Something like that could work, but considering that partialProductData has a lot of fields, I was searching for a cleaner way of doing it.
I've tried with a foreach(partialProductData as $key => $value) approach, considering that the keys are named as the products table column names, but I struggled in obtain something viable.
As suggested, creating an array of key/values pairs should work if the keys match the table fields in both models. Something like:
$data = [];
foreach($partialProduct->attributes as $key => $value):
$data[$key] = $value;
endforeach;
$product = Yii::$app->db->createCommand()
->insert('products', $data)
->execute();
Alternatively, you could use massive assignment. Again, for this to work the fields on both models would need to match:
$product = new Product;
$product->attributes = $partialProduct->attributes;
$product->save();
If you only want to save certain fields from your partialProducts to your Product, you can use scenarios to select the specific fields you want to be to set with massive assignment:
In Product.php
const SCENARIO_TEST = 'test';
...
public function scenarios()
{
return [
self::SCENARIO_TEST => ['field_1', 'field_2'], // Any field not listed here will not be set
];
}
And wherever you're doing the save:
$product = new Product;
$product->scenario = Product::SCENARIO_TEST;
$product->attributes = $partialProduct->attributes;
$product->save();

How to update a pivot table using Eloquent in laravel 5

I am new to laravel. I am working on a laravel 5 app and I am stuck here. I have 2 models as such:
class Message extends Eloquent{
public function user()
{
return $this->belongsTo('App\User', 'from');
}
public function users()
{
return $this->belongsToMany('App\User')->withPivot('status');
}
}
class User extends Eloquent {
public function messages()
{
return $this->hasMany('App\Message', 'from');
}
public function receive_messages() {
return $this->belongsToMany('App\Message')->withPivot('status');
}
}
There exist a many-to-many relationship between Message and User giving me a pivot table as such:
Table Name: message_user
Colums:
message_id
user_id
status
I have an SQL query as such:
update message_user
set status = 1
where user_id = 4 and message_id in (select id from messages where message_id = 123)
How can I translate this query to the laravel equivalent?
The code below solved my problem:
$messages = Message::where('message_id', $id)->get();
foreach($messages as $message)
$message->users()->updateExistingPivot($user, array('status' => 1), false);
You may use one of these two functions, sync() attach() and the difference in a nutshell is that Sync will get array as its first argument and sync it with pivot table (remove and add the passed keys in your array) which means if you got 3,2,1 as valued within your junction table, and passed sync with values of, 3,4,2, sync automatically will remove value 1 and add the value 4 for you. where Attach will take single ID value
The GIST: if you want to add extra values to your junction table, pass it as the second argument to sync() like so:
$message = Messages::find(123);
$user = User::find(4);
// using attach() for single message
$user->message()->attach($message->id, [
'status' => 1
]);
$message2 = Messages::find(456); // for testing
// using sync() for multiple messages
$user->message()->sync([
$message->id => [
'status' => 1
],
$message2->id => [
'status' => 1
],
]);
Here is a small example of how to update the pivot table column
$query = Classes::query();
$query = $query->with('trainees')
->where('user_id', Auth::id())
->find($input['classId']);
foreach ($query->trainees as $trainee) {
$trainee->pivot->status = 1 //your column;
$trainee->pivot->save();
}
Note: make sure your relation data must in an array
Hope its help you :)
happy coding
Laravel 5.8
First, allow your pivot columns to be searchable by chaining the withPivot method to your belongsToMany
Copied from my own code to save time
// I have 3 columns in my Pivot table which I use in a many-to-many and one-to-many-through scenarios
$task = $user->goalobjectives()->where(['goal_objective_id'=>$goal_objective_id,'goal_obj_add_id'=>$goal_obj_add_id])->first(); //get the first record
$task->pivot->goal_objective_id = $new; //change your col to a new value
$task->pivot->save(); //save
The caveat is that your pivot table needs to have a primary 'id' key.
If you don't want that then you can try the following:
$tasks=$user->posts()->where(['posts_id'=>$posts_id,'expires'=>true])->get()->pluck('id'); // get a collection of your pivot table data tied to this user
$key=join(",",array_keys($tasks->toArray(),$valueYouWantToRemove));
$tasks->splice($key,1,$newValueYouWantToInsert);
$c = array_fill(0,$tasks->count(),['expires'=>true]); //make an array containing your pivot data
$newArray=$tasks->combine($c) //combine the 2 arrays as keys and values
$user->posts()->sync($newArray); //your pivot table now contains only the values you want
4th July Update Update to above snippet.
//Ideally, you should do a check see if this user is new
//and if he already has data saved in the junction table
//or are we working with a brand new user
$count = $user->goalobjectives->where('pivot.goal_obj_add_id',$request->record)->count();
//if true, we retrieve all the ids in the junction table
//where the additional pivot column matches that which we want to update
if($count) {
$ids = $user->goalobjectives->where('pivot.goal_obj_add_id',$request->record)->pluck('id');
//convert to array
$exists = $ids->toArray();
//if user exists and both saved and input data are exactly the same
//there is no need
//to update and we redirect user back
if(array_sum($inputArray) == array_sum($exists)) {
//redirect user back
}
//else we update junction table with a private function
//called 'attachToUser'
$res = $this->attachToUser($user, $inputArray, $ids, $request->record);
}//end if
elseif(!$count) {
//we are working with a new user
//we build an array. The third pivot column must have equal rows as
//user input array
$fill = array_fill(0,count($inputArray),['goal_obj_add_id'=>$request->record]);
//combine third pivot column with user input
$new = array_combine($inputArray,$fill);
//junction table updated with 'user_id','goal_objective_id','goal_obj_add_id'
$res = $user->goalobjectives()->attach($new);
//redirect user if success
}
//our private function which takes care of updating the pivot table
private function attachToUser(User $user, $userData, $storedData, $record) {
//find the saved data which must not be deleted using intersect method
$intersect = $storedData->intersect($userData);
if($intersect->count()) {
//we reject any data from the user input that already exists in the database
$extra = collect($userData)->reject(function($value,$key)use($intersect){
return in_array($value,$intersect->toArray());
});
//merge the old and new data
$merge = $intersect->merge($extra);
//same as above we build a new input array
$recArray = array_fill(0,$merge->count(),['goal_obj_add_id'=>$record]);
//same as above, combine them and form a new array
$new = $merge->combine($recArray);
//our new array now contains old data that was originally saved
//so we must remove old data linked to this user
// and the pivot record to prevent duplicates
$storedArray = $storedData->toArray();
$user->goalobjectives()->wherePivot('goal_obj_add_id',$record)->detach($storedArray);
//this will save the new array without detaching
//other data previously saved by this user
$res = $user->goalobjectives()->wherePivot('goal_obj_add_id',$record)->syncWithoutDetaching($new);
}//end if
//we are not working with a new user
//but input array is totally different from saved data
//meaning its new data
elseif(!$intersect->count()) {
$recArray = array_fill(0,count($userData),['goal_obj_add_id'=>$record]);
$new = $storedData->combine($recArray);
$res = $user->goalobjectives()->wherePivot('goal_obj_add_id',$record)->syncWithoutDetaching($new);
}
//none of the above we return false
return !!$res;
}//end attachToUser function
This will work for pivot table which doesn't have a primary auto increment id. without a auto increment id, user cannot update,insert,delete any row in the pivot table by accessing it directly.
For Updating your pivot table you can use updateExistingPivot method.

Retrieve every model and return three values

I am looking for a way to retrieve all models in a database. Then loop through all of the models and read out the values for name, firstname and phonenumber.
So far I've gotten this and failed to go past it:
$searchModel = new EmployeeSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
I am then looking to implement those three values in a simple HTML table:
<tr><td>$firstname</td><td>$name</td><td>$phone</td></tr>
The table should be part of a PDF output, so ideally I would save it to a variable:
$html_table = '<tr><td>$firstname</td><td>$name</td><td>$phone</td></tr>';
I would need to get this for every model that fulfills the criteria of status = 'active' in the database.
So far I've only been able to get tables via gridView and not in a HTML template either.
You don't really need a data provider to achieve this, you could simply try :
$models = Employee::find()->where(['status'=>'active'])->orderBy('name ASC')->all();
foreach ($models as $model) {
echo "<tr><td>{$model->firstname}</td><td>{$model->name}</td><td>{$model->phone}</td></tr>";
}
Read more : http://www.yiiframework.com/doc-2.0/guide-db-active-record.html#querying-data
You can get all models like this:
$employees = Employee::find()
->select('firstname, name, phone')
->asArray()
->where(['status'=>'active'])
->all();
This way you will get an array of arrays containing the 3 selected fields, so now you only need to use a foreach to loop through them and create the table:
$html = '<table>';
foreach($employees as $employee) {
$html .= '<tr><td>'.$employee['firstname'].'</td><td>'.$employee['name'].'</td><td>'.$employee['phone'].'</td></tr>';
}
$html .= '</table>'

Not able to render external DB data on admin tab

I am trying to to show external database data onto custom admin tab but getting error.Can some one help me to resolve this . I am able to fetch data from external DB but when i pass it to the GridField it gives me this error.
Error###
"get_class() expects parameter 1 to be object, array given"
and here is my code
public function getList() {
$externalDB = $object = Injector::inst ()->create ( 'ExternalDatabase' );
$results = $externalDB->query ( 'SELECT "Course" FROM "Courses"' );
$list = ArrayList::create ();
foreach ( $results as $row ) {
$list->push ( $row ) ;
}
return $list;
}
I have ExternalDatabase Class which resolve my query and return result.
Instead of pushing the row directly, try doing
$list->push(ArrayData::create($row));