Eloquent / Laravel: How to get last insert/update ID/instance of updateOrCreate()? - php

The title is mostly self-explanatory. Eloquent has a method called
updateOrCreate()
documented here: https://laravel.com/docs/5.5/eloquent#other-creation-methods
In some cases this is really useful. However after doing updateOrCreate() I need either the updated/created object or its primary key or its id.
Of course I could do MyModel::where(...)->first() and give all those data again but this is clumsy and may be some expensive request.
However updateOrCreate() only returns true or false.
Any ideas?

The method will return ID of created or updated object, so just do this:
$object = Model::updateOrCreate(['name' => 'John'], ['age' => 25]);
$id = $object->id;

Query for the last one
$lastRecord = MyModel::last();
Or
$lastRecord = MyModel::orderBy('id', 'DESC')->first();

Related

Not able to call SAVE method on Model object Laravel

I'm trying to call save method on an object of a Model Complaints Model to update a specific resource in Database but when I send a POST request on api url it gives this error
BadMethodCallException: Method Illuminate\Database\Eloquent\Collection::save does not exist.
I also tried calling update method but same error.
Code for edit:
$complaint = Complaint::where('complaint_id', $request->input('id'))->get();
$complaint->title = $request->input('Complaint_Subject');
$complaint->description = $request->input('Complaint_Details');
$complaint->address = $request->input('Complaint_Address');
$complaint->update();
return [
'save_status' => "success"
];
the very first line is returning the response and correct response.
AND
also I'm trying to call delete or destroy method to delete the resource but it also gives the same error
BadMethodCallException: Method Illuminate\Database\Eloquent\Collection::delete does not exist.
or
BadMethodCallException: Method Illuminate\Database\Eloquent\Collection::destroy does not exist.
Code for delete:
$complaint = Complaint::where('complaint_id', $request->input('id'))->get();
$complaint->destroy($request->input('id'));
return [
'request_status' => 'success',
'complaint' => $complaint
];
here also the response is being returned correctly.
Note that the complaint_id is not Primary Key in the table so I cannot simply call Complaint::find($request->input('id')) I must have to cross check with this complaint_id column value to find the resource. I have been searching since yesterday but could not find any solution. How can I solve this problem. Thanks!!!
If your $complaint is a collection of output. So save didn't work for this if you change your code like $complaint = Complaint::where('complaint_id', $request->input('id'))->first(); Then save() will work.
just use first() instead of get()
Complaint::where('complaint_id', $request->id)->first()
In your query
$complaint = Complaint::where('complaint_id', $request->input('id'))->get();
it returns Associative Arrays
But if you use
$complaint = Complaint::where('complaint_id', $request->input('id'))->first();
it returns Indexed or Numeric Arrays. Then you have to use $complaint[0]->destroy($request->input('id')); or $complaint[1]->destroy($request->input('id')); and so one

Can we add custom values to a CakePHP Table Object?

I have a Cake Object when querying a table:
$invoices = TableRegistry::get('invoices')->find('all', ['conditions' => ['order_number =' => $orderNumber]]);
This works fine. I then want to add other array key/values to this Object, like this one:
$invoicesTmp = array();
$invoicesTmp['customer'] = "name of customer";
But $invoicesTmp is incompatible with $invoices. (one is an array, other is an CakePHP Object)
I have tried this:
compact($invoices, $invoicesTmp);
but that didn't worked.
The find() method of a Table object returns a Cake\ORM\Query object. This object is used to build SQL queries and to execute them. It has some features to define how the results from the query should be returned.
When CakePHP fetches results from the database the records are stored as an array, and CakePHP then converts them to Entity objects. A process called "hydration" of entities. If you disable hydration the records are returned as just an array.
$query = TableRegistry::get('invoices')
->find()
->where(['order_number'=>$orderNumber])
->enableHydration(false);
foreach($query as $record) {
pr($record);
}
The above creates a query object, and you can iterate over the query records because the object itself supports iteration.
The query object implements the Cake\Collection\CollectionInterface interface, which means we can perform a bunch of collection methods on it. The most common method is the toArray().
$invoices = TableRegistry::get('invoices')
->find()
->where(['order_number'=>$orderNumber])
->enableHydration(false)
->toArray();
The $invoices variable is now a valid array object holding the all the records with each record as an array object.
You can now easily use array_merge to assign extra metadata to each record.
$invoices = array_map(function($invoice) {
return array_merge(['customer'=>'name of customer'], $invoice);
}, $invoices);
$this-set(compact('invoices'));
Updated:
Based upon the comments it appears you wish to use two different tables with different column names, but those columns represent the same data.
Field Aliases
You can rename fields in the SQL query to share a common alias.
$table = TableRegistry::get($whichTable ? 'table_a' : 'table_b');
$records = $table->find()
->select([
'id',
'invoice_id',
'name' => ? $whichTable ? 'customer_name' : 'invoice_name'
])->all();
The above selects a different column for name depending upon which table is being used. This allows you to always use $record->name in your view no matter which table.
I don't like this approach, because it makes the source code of the view file appear to reference a property of the entity that doesn't really exist. You might get confused when returning to the code later.
Field Mapping
From a MVC perspective. Only the controller knows what a view needs. So it's easier if you express this knowledge as a mapping.
$map = [
'id'=>'id',
'invoice_id'=>'invoice_id',
'name' => ? $whichTable ? 'customer_name' : 'invoice_name'
];
$table = TableRegistry::get($whichTable ? 'table_a' : 'table_b');
$records = $table->find()
->select(array_values($map))
->all();
$this->set(compact('records','map'));
Later in your view to output the columns you do it like this:
foreach($records as $record) {
echo $record->get($map['name']);
}
It becomes verbose as to what is happening, and why. You can see in the view that the controller provided a mapping between something called name and the actual field. You also know that the $map variable was injected by the controller. You now know where to go to change it.

Propel - get primary key after find

The question boils down to finding the proper way how to getPrimaryKey when iterating over a yielded result. When using select method, the result is an object of ArrayCollection which doesn't provide the getPrimaryKey method. A simple snippet
$q = UserQuery::create();
$q->select('a', 'b'); //yields an ArrayCollection object, doesn't have getPrimaryKey method when iterated
$q->find();
However,
$q = UserQuery::create();
$q->find(); //yields an ObjectCollection object, has getPrimaryKey method when iterated
Update
I have tried to use the setFormater to force using the ObjectCollection. Ultimately, it resulted in exception being thrown.
$q = UserQuery::create()
->setFormater(ModelCriteria::FORMAT_OBJECT)
->select('a', 'b')
->find(); //Error populating object
Update 2
Providing an exact use case, since it may be unclear at first what I am looking for. I have a table with >100 columns. I am providing the functionality using behaviour to not disable (not select) some of them. Thus, I am unseting some of the columns, and basing the $q->select on the remaining ones.
if (!empty($tableQuery->tableColumnsDisable)) {
$columns = $tableQuery->getTableMap()->getColumns();
foreach ($columns as $index => $column) {
if (!empty($tableQuery->tableColumnsDisable[$column->getName()])) {
unset($columns[$index]);
continue;
}
$columns[$index] = $column->getName();
}
//TODO - returns array collection, object collection needed
$tableQuery->select($columns);
}
When using select(), Propel will skip object hydration and will just return an ArrayCollection containing an array for each result row.
To retrieve the id of each result row, you need to add the column name to the select(). You can then just retrieve the value from the row arrays by using the column name:
$users = UserQuery::create()
->select(['id', 'a', 'b'])
->orderBy('c')
->find();
foreach ($users as $user) {
$id = $user['id'];
}
The select functionality is described in the documentation and in the docblock of Propel\Runtime\ActiveQuery\ModelCriteria#select() (source).
When you are using Propel 1, the functionality is the same. Read more about it in the Propel 1 documentation or the docblock of ModelCriteria#select() (source).

DBTable creates new row instead off updating the existing

I try to update an DB entry with now data, but i am just creating an new entry:
$client =$this->clientTable->find($id);
$client->CompanyName = $request->getPost('CompanyName');
$this->clientTable->update();
$this->_redirect('client/index');
Zend_Db_Table_Abstract::find() method returns Zend_Db_Table_Rowset object. You should use method which will return you Zend_Db_Table_Row object and use it.
For example:
$clientRow = $this->clientTable->fetchRow(array('id' => $id));
$clientRow->CompanyName = $request->getPost('CompanyName');
$clientRow->save();
If your's table primary key name is not 'id', change it to suitable value in the first line of code above.

Codeigniter how to map a class sent to a model

I have a SectorModel with this function:
public function update(Sector $sector) {
$this->db->where('sector_id', $sector->getScetor_id());
return $this->db->update(_SECTOR_, $sector);
}
There are times that I’ll change only the name of the Sector object:
$Sector = new Sector();
$Sector->setSector_name = 'test';
$this->SectorModel->update($Sector);
The generated select looks like:
UPDATE realestate_sector SET sector_name = 'teste', sector_description = NULL
It will update but will set all other properties to NULL because it was not set on my object.
Right now, I have to fill the whole object before sending it.
Is there a way to map the Sector class and update only what was sent on the object?
Thanks in advance for any help.
Sorry for any typos, my English is not good =)
Just loop through all your object's properites and then if any is NULL just drop it with unset.
Here is your model's method edited to achieve that:
public function update(Sector $sector)
{
foreach($sector as $k=>$v)
{
if($v === NULL)
unset($sector->$k)
}
$this->db->where('sector_id', $sector->getScetor_id());
return $this->db->update(_SECTOR_, $sector);
}
Here you can find some info about iterating objects in PHP
The easiest to do this would be to rather use a array - docs here http://codeigniter.com/user_guide/database/active_record.html#update - you just create a array of all the columns with their values that you want to update and perform a $this->db->update('mytable', array('name' => 'test'), array('id' => $id)); call. This will only update the columns you specified in the First array. With the second array acting as your WHERE expression.
The only reason I can think of as to why your other values are being set to NULL is because in your example you create a new instance of the class and the other values must either have been set to nothing or are set to NULL. It would (If this is the case) be better to get a record from the table and then change and values on the populated record and pass that to the function to update.
Hope that helps.

Categories