How to add data to relative table Laravel? - laravel

I have model Object and model Prototype.
Incoming data($_POST) is array prototype_id[]:
prototype_id[] = 1;
prototype_id[] = 2;
prototype_id[] = 3;
That should be saved in table ObjectPrototypes. No i dont have this model.
So, in controller I do:
$object = new Object();
$object->name = "Object";
How to add/attach prototype_id[] to saved object $object?
Important:
Object can has one or more prototypes

One (quick) solution could be to do raw insert on this table. Assuming pivot table name is object_prototype, here is what you could do:
$object = new Object();
$object->name = "Object";
$object->save();
foreach($request->input('prototype_id') as $p) {
\DB::table('object_prototype')->insert([
'object_id' => $object->id,
'prototype_id' => $prototype
]);
}
It should work, thought it's not really Laravelish.
You may also work with pivot, but it requires more boilerplate. You may have a look here: https://laravel.com/docs/5.4/eloquent-relationships#updating-many-to-many-relationships and here: https://laravel.com/docs/5.4/eloquent-relationships#one-to-many-inverse That's what I do usually but it's a bit too long for a short answer and I think Laravel Doc is more complete.

Related

Laravel Eloquent: inserting data

I am sending a bunch of data through post method. For now I am using a fully working solution:
$dataClient = new Client;
$dataClient->name = $post['client']['name'];
$dataClient->address = $post['client']['address'];
...
$dataClient->save();
I am wondering if there is a shorter solution? Like posting an array and Laravel could map keys to db fields?
What if you would add something more, like calculated value upon?
Example:
$dataClient = new Client;
Then map array keys to db keys
$dataClient->someField = someCalculatedValue
$dataClient->save();
Thank you in advance.
You have quite a few options when it comes to creating models from a data array. If you want to directly insert it into the database, the easiest way is create()
Client::create($post['client']);
If you want to change some things afterwards or just not save it right away, just pass your data to the constructor.
$client = new Client($post['client']);
$client->someField = 'some value';
$client->save();
And finally, the last option, calling fill() manually. This method is used internally for create() as well as in the constructor.
$client = new Client();
$client->fill($post['client']);
$client->save();
Note: for all methods above you'll need to set up the fillable attributes in your model. This is to protect against malicious user input (request data).
protected $fillable = ['name', 'address', 'etc'];
More information about Mass Assignment
Eloquent has create and update methods which will insert massive data. For example :
inputs = ['name' => 'value','address' => 'value'];
Client::create(inputs)
It will automatically map fields in Eloquent.
Have you tried something like this?
$dataClient = new Client;
$dataClient->fill($client);
$dataClient->save();
Depending on how you set up your Client model and what fields you set as guarded/fillable, the fill method will automatically map the data with their respective fields.

Can't modifiy eloquent query result

So I got the following code in my controller's show function which just returns a page with the tags:
$page = Post::with('tags')->findOrFail($id);
$page->tags->lists('name');
return response($page);
When I try to to execute this, it won't change the tags key, which is an array with the tags from the eloquent belongsToMany relationship.
Why isn't this working? To me it seems pretty handy to just change a value like this.
When I change it to $page->test = $page->tags->lists('name') it will add the test key as usual.
How would I modify a eloquent value in a easy way?
What works pretty well for such cases is overriding toArray in your Model:
public function toArray(){
$array = parent::toArray();
$array['tags'] = $this->tags->lists('name');
return $array;
}
After the $page = Post::with('tags')->findOrFail($id); line is executed, $page->tags is going to be an Illuminate\Database\Eloquent\Collection object containing all the related Tags for the Post. From your provided code and question, it sounds like you want to then change $page->tags to be an array containing just the related tag names.
The statement $page->tags->lists('name') is only going to return an array of all the names of the related tags; it does not modify the underlying collection. If you wanted to modify the $page->tags attribute, you would need to assign it the result of your statement:
$page->tags = $page->tags->lists('name');
However, $page->tags was an attribute that was dynamically created and assigned by the Model, and is expected to hold the contents of a relationship. Manually modifying the contents like this may have unintended consequences, but I do not know.
Edit
The Model::toArray() method merges in the relationship information over the attribute information. So, you can change the attribute, but if you echo the model, the relationship information will show up over your attribute change.
$page->tags = $page->tags->lists('name');
// this will echo the tags attribute, which is now the array of tags
echo print_r($page->tags, true);
// this will echo the model, with the tags attribute being
// overwritten with the related data
echo $page;
One option would be to unset the attribute (which also unsets the relationship) and then reassign the attribute to your desired data:
$page = Post::with('tags')->findOrFail($id);
$temp = $page->tags;
unset($page->tags); // you must unset the attribute before reassigning it
$page->tags = $temp->lists('name');
return response($page);
A little bit cleaner would be to use a different attribute name:
$page = Post::with('tags')->findOrFail($id);
$page->tagNames = $page->tags->lists('name');
unset($page->tags);
return response($page);
And another option is to do what #lukasgeiter suggested and override the Model::toArray method:
class Post extends Model {
public function toArray() {
// call the parent functionality first
$array = parent::toArray();
if (isset($this->tags)) {
$array['tags'] = $this->tags->lists('name');
}
return $array;
}
}
If you want to change the output of one of the relationships in the toArray/toJson methods, then use accessor:
// in order to not show the underlying collection:
protected $hidden = ['tags'];
// in order to append accessor to toArray output
protected $appends = ['allTags'];
// mutate the collection to be just an array of tag names
public function getAllTagsAttribute()
{
$collection = return $this->getRelation('tags');
return ($relation) ? $collection->lists('name') : [];
}
then you will get simple array instead of collection when you do $page->allTags or in the toArray/toJson output, while not showing the real collection.
It is allTags not `tags, since the latter should remain eloquent dynamic property, so you can work with it as usual before outputting anything.
not sure if this helps. To be honest, I do not get your point. But I guess there is something wrong with this line:
$page->tags->lists('name');
If $page->tags is a belongsToMany relationship and you want to add more query conditions after this relationship, you should query like this:
$page->tags()->lists('name');

One to Many relation - how to implement push

I am trying to do an insert which will create a parent record for one table and then insert records that link back to the parent record into another.
In other words this: User completes Course information form, then completes a series of questions on the same page. On submission, the course information is inserted into its own table then questions are inserted into a separate one.
My Course model is this:
class CourseVerification extends Eloquent
{
public function courseverificationqanda()
{
return $this->hasMany('CourseVerificationQandA', 'id', 'verification_id');
}
My Question model is this:
class CourseVerificationQandA extends InnovedBaseModel
{
public function courseverification()
{
return $this->belongsTo('CourseVerification');
}
On form submission, my controller is doing this:
// create course verification record first
$veri = new CourseVerification;
$veri->verification_date = $input['verification_date'];
// create collection to store questions
$collection = new Illuminate\Database\Eloquent\Collection();
// loop through submitted questions and push them to the collection
for($i = 0; $i < count(Input::get('question_id')); $i++) {
$answer = new CourseVerificationQandA;
$answer->question_id = $input['question_id'][$i];
$answer->answer = $input['answer'][$i];
$answer->additional_notes = $input['additional_notes'][$i];
$collection->push($answer);
}
// add collection to quesetion relation
$veri->courseverificationqanda = $collection;
// insert both course record and questions
$veri->push();
The push method then errors and debugs the SQL command
insert into `CourseVerification`
(`verification_date`, `topic_id`, `course_id`, `verifier_id`,`iv_status`,
`verification_type`, `courseverificationqanda`, `created_by`)
values
(29/10/2014, 1294, 47, 1, 1, I, [{"question_id":"2","answer":"0","additional_notes":""},
{"question_id":"3","answer":"0","additional_notes":""},
{"question_id":"4","answer":"0","additional_notes":""}], 1)
As you can see, the assignment of the collection to $veri->courseverificationqanda is then getting treated as a table column in the SQL query when it is actually a relationship to the question table.
Any ideas?
You have a few mistakes there.
You don't assign collection to the relation. You need to load that relation (as a collection in this case) and push on it, then just save everything:
$veri = new CourseVerification;
$veri->verification_date = $input['verification_date'];
// Save the parent model before you can add its children
if ($veri->save())
{
// loop through submitted questions and push them to the collection
for($i = 0; $i < count(Input::get('question_id')); $i++) {
$answer = new CourseVerificationQandA;
// associate with the parent model
// explicitly
$answer->verification_id = $veri->getKey();
// or the eloquent way
// $answer->courseverification()->associate($veri);
$answer->question_id = $input['question_id'][$i];
$answer->answer = $input['answer'][$i];
$answer->additional_notes = $input['additional_notes'][$i];
$veri->courseverificationqanda->push($answer);
}
}
// save both course record and questions
$veri->push();
Another thing are your relations, which are both wrong:
// this is how it should look like
// CourseVerification
public function courseverificationqanda()
{
return $this->hasMany('CourseVerificationQandA', 'verification_id', 'id');
}
// QandA
public function courseverification()
{
return $this->belongsTo('CourseVerification', 'verification_id');
}
In firsts case you swapped the keys, so it would work but not the way it should.
In second case you didn't specify the foreign key at all, so Eloquent would look for courseverification_id in the table (method_name_id).

Attaching a hasOne model to another Laravel/Eloquent model without specifying id

Background
Given we have the following two tables where type_id references a row in questionType:
question
id | type_id | description
---+---------+------------
1 | 1 | A nice question
.. | .. | ..
questionType
id | name
---+----------------
1 | Multiple-choice
.. | ..
with the following Eloquent models:
class Question extends Model {
public function type() {
return $this->hasOne( 'QuestionType', 'id', 'type_id' );
}
}
class QuestionType extends Model {
}
Question 1
How can I add a new question that references an existing question type without manually doing anything with ids? For example the following works but is ugly imo since I have to manually assign the corresponding question type id:
$q = new Question;
$q->type_id = 1; // Multiple-choice
$q->description = 'This is a multiple-choice question';
$q->save();
One would think there was a way to let the ORM handle the id-assignment (isn't the point to avoid stuff like this with ORMs?), something along the lines of (this does not work in Eloquent ORM):
$q = new Question;
$q->type = QuestionType.where('name', '=', 'Multiple-choice');
$q->description = 'This is a multiple-choice question';
$q->save();
Question 2
In relation to question 1, how would I go about adding a new question that references a new question type without manually doing anything with ids? Similarly I imagine something along the lines of:
$t = new QuestionType;
$t->name = 'Another type';
$q = new Question;
$q->type = $t;
$q->description = 'This is a multiple-choice question';
$q->save();
Here I'd like $q->save() to save both the new question type and question (or something similar).
The following works, but again I'm assigning the id myself which I believe the ORM should handle:
$t = new QuestionType;
$t->name = 'Another type';
$t->save();
$q = new Question;
$q->type = $t->id;
$q->description = 'This is a multiple-choice question';
$q->save();
I've tried playing with different combinations of save(), update() methods without luck. I also looked for attach() which exists on the hasMany relationships but seem to be missing in hasOne.
First off, you misunderstood the relation you refer to.
Here's what you need:
// Question model
public function questionType()
{
return $this->belongsTo('QuestionType', 'type_id');
}
// QuestionType model
public function questions()
{
return $this->hasMany('Question', 'type_id');
}
then you can link them together like this:
$questionType = QuestionType::where(..)->first();
$question = new Question;
... // do something with it
// associate
$question->questionType()->associate($questionType);
// or the other way around - save new question and link to the type:
$questionType->questions()->save($question);
You can explicitly pass an id to associate as well:
$question->type_id = $someTypeId;
$question->save();
You can't do this:
$question->questionType = $someQuestionType;
for this way Eloquent handles model attributes, not relations.
Question 2:
$questionType = new QuestionType(['name' => 'multiple']);
$questionType->save();
$question = new Question([ ... some values ... ]);
// then either this way:
$questionType->questions()->save($question);
// or, again, the other way around:
$question->questionType()->associate($questionType);
$question->save();
Answering question 1 for me both methods are good, you don't need to change anything.
Answering question 2 you should do it as you showed. ORM won't create automatically QuestionType until you use manually save method.
For example if you used code:
$t = new QuestionType;
$t->name = 'Another type';
$t2 = new QuestionType;
$t2->name = 'Another type 2';
$q = new Question;
$q->type = $t; // what here - $t2 or $t ?
$q->description = 'This is a multiple-choice question';
$q->save();
what ORM should decide? Question isn't connected to any of $t or $t2 so ORM won't decide it for you. You need to tell ORM what's the type.
I'm not an ORM/Eloquent expert but I think you expect too much from ORM. ORM shouldn't guess what you want to do. It help you manage relations or objects but it won't associate objects if you don't tell it to do so.
You could however try with mutator. You could add to your Question model:
public function setTypeAttribute($value)
{
$value->save();
$this->attributes['type_id'] = $value->id
}
and now it should be possible to use $q->type = $t; (However I haven't tested it)

Magento: Iterate through existing/newly saved objects or use addCommitCallback?

I have a query regarding magento 1.6.
I have a number of objects that I am creating, calling a method on and saving to the database with the Mage_Core_Model_Resource_Transaction transaction model.
The method now requires the primary key value of the object, which obviously isn't going to be available until after the object has been saved to the database.
Can I just loop through the created objects after they've been saved to the database? Instead, do I need to use the transaction's addCommitCallback method?
In other words, can I do:
$collection = [];
$transaction = Mage::getModel('core/resource_transaction');
for ($i = 0; $i <= 10; $i++) {
$obj = new ObjectFoo;
$transaction->addObject($obj);
$collection[] = $obj;
}
$transaction->save();
foreach($collection as $collected) {
$this->doSomething($collected);
}
(And assume that each $collected item will now have a valid id value)
Or do I need to use something like:
$transaction->addCommitCallback(array($obj, 'doSomething'));
One thing that I have noticed with addCommitCallback is that you can not specify any parameters for the callback, which is rather... restrictive.
Yes, each collected item should contain the id after saving the transaction.
http://www.php.net/manual/en/language.oop5.references.php

Resources