Creating and Update Laravel Eloquent - php

What's the shorthand for inserting a new record or updating if it exists?
<?php
$shopOwner = ShopMeta::where('shopId', '=', $theID)
->where('metadataKey', '=', 2001)->first();
if ($shopOwner == null) {
// Insert new record into database
} else {
// Update the existing record
}

Here's a full example of what "lu cip" was talking about:
$user = User::firstOrNew(array('name' => Input::get('name')));
$user->foo = Input::get('foo');
$user->save();
Below is the updated link of the docs which is on the latest version of Laravel
Docs here: Updated link

2020 Update
As in Laravel >= 5.3, if someone is still curious how to do so in easy way it's possible by using: updateOrCreate().
For example for the asked question you can use something like:
$matchThese = ['shopId'=>$theID,'metadataKey'=>2001];
ShopMeta::updateOrCreate($matchThese,['shopOwner'=>'New One']);
Above code will check the table represented by ShopMeta, which will be most likely shop_metas unless not defined otherwise in the model itself.
And it will try to find entry with
column shopId = $theID
and
column metadateKey = 2001
and if it finds then it will update column shopOwner of found row to New One.
If it finds more than one matching rows then it will update the very first row that means which has lowest primary id.
If not found at all then it will insert a new row with:
shopId = $theID,metadateKey = 2001 and shopOwner = New One
Notice
Check your model for $fillable and make sure that you have every column name defined there which you want to insert or update and rest columns have either default value or its id column auto incremented one.
Otherwise it will throw error when executing above example:
Illuminate\Database\QueryException with message 'SQLSTATE[HY000]: General error: 1364 Field '...' doesn't have a default value (SQL: insert into `...` (`...`,.., `updated_at`, `created_at`) values (...,.., xxxx-xx-xx xx:xx:xx, xxxx-xx-xx xx:xx:xx))'
As there would be some field which will need value while inserting new row and it will not be possible, as either it's not defined in $fillable or it doesn't have a default value.
For more reference please see Laravel Documentation at:
https://laravel.com/docs/5.3/eloquent
One example from there is:
// If there's a flight from Oakland to San Diego, set the price to $99.
// If no matching model exists, create one.
$flight = App\Flight::updateOrCreate(
['departure' => 'Oakland', 'destination' => 'San Diego'],
['price' => 99]
);
which pretty much clears everything.
Query Builder Update
Someone has asked if it is possible using Query Builder in Laravel. Here is reference for Query Builder from Laravel docs.
Query Builder works exactly the same as Eloquent so anything which is true for Eloquent is true for Query Builder as well. So for this specific case, just use the same function with your query builder like so:
$matchThese = array('shopId'=>$theID,'metadataKey'=>2001);
DB::table('shop_metas')::updateOrCreate($matchThese,['shopOwner'=>'New One']);
Of course, don't forget to add DB facade:
use Illuminate\Support\Facades\DB;
OR
use DB;

Updated: Aug 27 2014 - [updateOrCreate Built into core...]
Just in case people are still coming across this... I found out a few weeks after writing this, that this is in fact part of Laravel's Eloquent's core...
Digging into Eloquent’s equivalent method(s). You can see here:
https://github.com/laravel/framework/blob/4.2/src/Illuminate/Database/Eloquent/Model.php#L553
on :570 and :553
/**
* Create or update a record matching the attributes, and fill it with values.
*
* #param array $attributes
* #param array $values
* #return static
*/
public static function updateOrCreate(array $attributes, array $values = array())
{
$instance = static::firstOrNew($attributes);
$instance->fill($values)->save();
return $instance;
}
Old Answer Below
I am wondering if there is any built in L4 functionality for doing this in some way such as:
$row = DB::table('table')->where('id', '=', $id)->first();
// Fancy field => data assignments here
$row->save();
I did create this method a few weeks back...
// Within a Model extends Eloquent
public static function createOrUpdate($formatted_array) {
$row = Model::find($formatted_array['id']);
if ($row === null) {
Model::create($formatted_array);
Session::flash('footer_message', "CREATED");
} else {
$row->update($formatted_array);
Session::flash('footer_message', "EXISITING");
}
$affected_row = Model::find($formatted_array['id']);
return $affected_row;
}
I would love to see an alternative to this if anyone has one to share.

firstOrNew will create record if not exist and updating a row if already exist.
You can also use updateOrCreate here is the full example
$flight = App\Flight::updateOrCreate(
['departure' => 'Oakland', 'destination' => 'San Diego'],
['price' => 99]
);
If there's a flight from Oakland to San Diego, set the price to $99. if not exist create new row
Reference Doc here: (https://laravel.com/docs/5.5/eloquent)

Save function:
$shopOwner->save()
already do what you want...
Laravel code:
// If the model already exists in the database we can just update our record
// that is already in this database using the current IDs in this "where"
// clause to only update this model. Otherwise, we'll just insert them.
if ($this->exists)
{
$saved = $this->performUpdate($query);
}
// If the model is brand new, we'll insert it into our database and set the
// ID attribute on the model to the value of the newly inserted row's ID
// which is typically an auto-increment value managed by the database.
else
{
$saved = $this->performInsert($query);
}

If you need the same functionality using the DB, in Laravel >= 5.5 you can use:
DB::table('table_name')->updateOrInsert($attributes, $values);
or the shorthand version when $attributes and $values are the same:
DB::table('table_name')->updateOrInsert($values);

$shopOwner = ShopMeta::firstOrNew(array('shopId' => $theID,'metadataKey' => 2001));
Then make your changes and save. Note the firstOrNew doesn't do the insert if its not found, if you do need that then its firstOrCreate.

Like the firstOrCreate method, updateOrCreate persists the model, so there's no need to call save()
// If there's a flight from Oakland to San Diego, set the price to $99.
// If no matching model exists, create one.
$flight = App\Flight::updateOrCreate(
['departure' => 'Oakland', 'destination' => 'San Diego'],
['price' => 99]
);
And for your issue
$shopOwner = ShopMeta::updateOrCreate(
['shopId' => $theID, 'metadataKey' => '2001'],
['other field' => 'val' ,'other field' => 'val', ....]
);

One more option if your id isn't autoincrement and you know which one to insert/update:
$object = MyModel::findOrNew($id);
//assign attributes to update...
$object->save();

Actually firstOrCreate would not update in case that the register already exists in the DB.
I improved a bit Erik's solution as I actually needed to update a table that has unique values not only for the column "id"
/**
* If the register exists in the table, it updates it.
* Otherwise it creates it
* #param array $data Data to Insert/Update
* #param array $keys Keys to check for in the table
* #return Object
*/
static function createOrUpdate($data, $keys) {
$record = self::where($keys)->first();
if (is_null($record)) {
return self::create($data);
} else {
return self::where($keys)->update($data);
}
}
Then you'd use it like this:
Model::createOrUpdate(
array(
'id_a' => 1,
'foo' => 'bar'
), array(
'id_a' => 1
)
);

like #JuanchoRamone posted above (thank #Juancho) it's very useful for me, but if your data is array you should modify a little like this:
public static function createOrUpdate($data, $keys) {
$record = self::where($keys)->first();
if (is_null($record)) {
return self::create($data);
} else {
return $record->update($data);
}
}

Isn't this the same as updateOrCreate()?
It is similar but not the same. The updateOrCreate() will only work
for one row at a time which doesn't allow bulk insert.
InsertOnDuplicateKey will work on many rows.
https://github.com/yadakhov/insert-on-duplicate-key

Try more parameters one which will surely find and if available update and not then it will create new
$save_data= Model::firstOrNew(['key1' => $key1value,'key'=>$key2value]);
//your values here
$save_data->save();

UpdateOrCreate method means either update or creates by checking where condition.
It is simple as in the code you can see, in the users table, it will check if an email has the value $user->email then it will update the data (which is in the 2nd param as an array) or it will create a data according to it.
$newUser = User::updateOrCreate(['email' => $user->email],[
'name' => $user->getName(),
'username' => $user->getName().''.$user->getId(),
'email' => $user->getEmail(),
'phone_no' => '',
'country_id' => 0,
'email_verified_at' => Carbon::now()->toDateTimeString(),
'is_email_verified' => 1,
'password'=>Hash::make('Secure123$'),
'avatar' => $user->getAvatar(),
'provider' => 'google',
'provider_id' => $user->getId(),
'access_token' => $user->token,
]);

check if a user exists or not. If not insert
$exist = DB::table('User')->where(['username'=>$username,'password'=>$password])->get();
if(count($exist) >0) {
echo "User already exist";;
}
else {
$data=array('username'=>$username,'password'=>$password);
DB::table('User')->insert($data);
}
Laravel 5.4

Related

Laravel How to get AI id in controller before saving

I have tried to get the auto increment ID from the booking table before saving the new data's, but I have errors all the time.
This is the code in the controller:
public function addAppointment(Request $request) {
$user = auth()->user();
$booking = new Booking();
$booking->vac_center_id = $request->get('vaccination_center');
$booking->vac_id = $request->get('vaccination_id');
$booking->date_of_shot = $request->get('date_of_shot');
$booking->time = $request->get('time');
$booking->shot_number = $request->get('shot_number');
$booking->isDone = 0;
$booking->isCancelled = 0;
$booking->user_id = $user->id;
$booking->save();
$booking = new BookingHasVaccinationCenters();
//here below I want to get the auto increment id
$booking->booking_id->id;
$booking->vac_center_id = $request->get('vaccination_center');
$booking->save();
return redirect('/home');
}
This is the error that I had last time when I try to do this:
Attempt to read property "id" on null
instead of this
$booking = new BookingHasVaccinationCenters();
//here below I want to get the auto increment id
$booking->booking_id->id;
$booking->vac_center_id = $request->get('vaccination_center');
$booking->save();
use below code
$bookingHasVaccination = new BookingHasVaccinationCenters();
//change here
$bookingHasVaccination->booking_id = $booking->id;
$bookingHasVaccination->vac_center_id = $request->get('vaccination_center');
$bookingHasVaccination->save();
Note : always try to define variable with the name same as model class while crud operations
Your error is that you declare with the same name the variable $booking , when you
save the $booking you should declare a instance of object with other name for example
$bookingvaccine->booking_id = $booking->id;
I know your questions has already been answered, but let me share another way of doing what you are doing, so you prevent this errors and your code is better.
If you have relations between this tables/models (relations functions created) then you can use the relation to create new models between them, without the need of sharing or passing the parent model's ID.
Assuming your User's relation name with Booking is bookings and for Booking -> BookingHasVaccinationCenters relation (strange name) is bookingHasVaccinationCenters, you should be able to do this:
public function addAppointment(Request $request)
{
$booking = $request->user()
->booking()
->create([
'vac_center_id' => $request->input('vaccination_center'),
'vac_id' => $request->input('vaccination_id'),
'date_of_shot' => $request->input('date_of_shot'),
'time' => $request->input('time'),
'shot_number' => $request->input('shot_number'),
'isDone' => false,
'isCancelled' => false,
]);
$booking->bookingHasVaccinationCenters()->create([
'vac_center_id' => $request->input('vaccination_center'),
]);
return redirect('/home');
}
Another super small tip, remember to cast isDone and isCancelled to boolean, so you can use those fields as boolean so you can do true or false instead of 1 or 0.
And last tip, try to always stick to the Laravel's conventions: snake_case column names, isDone and isCancelled should be is_done and is_cancelled.

Laravel - Update database record if already exists - all fields

I am trying to get Laravel to update a database record, if it's already exists. This is my table:
id | booking_reference | description | date
------------------------------------------------------
PRI KEY | UNIQUE | MEDIUM TEXT | DATE
AUTO INC | |
My model looks like this:
Document.php:
class Document extends Model
{
protected $fillable = [
'booking_reference', 'description', 'date'
];
}
And my controller, looks like this - please note that it's webhook() that's being called.
DocumentController.php:
class DocparserController extends Controller
{
//This is the function to capture the webhook
public function webhook(Request $request)
{
$document = new Document();
$document->fill($request->all());
//Grab the date_formatted field from our request.
$document->date = $request->input('date_formatted');
$document->updateOrCreate(
['booking_reference' => $document->booking_reference],
//How can I do so it updates all fields?
);
return response()->json("OK");
}
}
So my problem is, that I cannot figure out how to update my entire row, where the booking_reference is already present.
I want to update all fields (description, date), without having to enter them all like:
['booking_reference' => $document->booking_reference],
['description' => $document->comments, 'date' => $document->date]
Document::updateOrCreate(
['booking_reference' => $request->input('booking_reference')],
$request->all() + ['date' => $request->input('date_formatted')]
);
If you wanted to adjust the request inputs before calling that you could do that mapping and slim this down.
$request->merge(['date' => $request->input('date_formatted')]);
// now $request->all() has `date`
...updateOrcreate(
[...],
$request->all(),
)
That particular field has to be mapped at some point ... if you really really wanted to you could actually have a middleware do this mapping, which would slim this down to just $request->all() as the second array.
Or even set up a mutator for date_formatted that sets date.
Basically this has to happen somewhere, it just depends where.
You can use any one of the following to check if the records exists and run the update query if the data already exists.
$user = Document::where('booking_reference', '=', $request->booking_reference)->first();
if ($user === null) {
// user doesn't exist
}
OR
if (Document::where('booking_reference', '=', $request->booking_reference)->count() > 0) {
// user found
}
Or even nicer
if (Document::where('booking_reference', '=', $request->booking_reference)->exists()) {
// user found
}
And i do not think you can update an entire row of data at once. You have to point which attribute to update to which one.
I would have a private function to normalize the input data:
private static function transformRequestInput($requestArray)
{
$map = ['date_formatted'=>'date'];
foreach($map as $key=>$newKey){
if(isset($requestArray[$key])) {
$requestArray[$newKey] = $requestArray[$key];
unset($requestArray[$key]);
}
}
return $requestArray;
}
And I would use it like so:
$document->updateOrCreate(
['booking_reference' => $document->booking_reference],
self::transformRequestInput($request->all())
);
If you want a class or object to associative array (properties must be public):
$updateArr = (array) $document;
$document->updateOrCreate($updateArr);
However, you use a protected property ($fillable) so you must:
$document = new Document();
$document->fill($request->all());
//Grab the date_formatted field from our request.
$document->date = $request->input('date_formatted');
$reflection = new ReflectionClass($document);
$property = $reflection->getProperty('fillable');
$property->setAccessible(true);
$updateArr = (array) $property->getValue($document);
$property->setAccessible(false);
$document->updateOrCreate($updateArr);
return response()->json("OK");

CakePHP 3.0 Custom Event Implementation

I was testing out the Events System on CakePHP v3.0.0-RC2 for my project purposes. I first have to apologise for the long text.
Basically I created a users table with fields id, name, and surname. I then created another table called user_statistics that tallies number of user creations per month. Below is the function that saves a user, create an event for the UserStatistics table object and then finally dispatch the event.
use Cake\Event\Event;
class UsersTable extends Table
{
//Other code
public function createUser($user)
{
if( $this->save( $user )){
$event = new Event('Model.User.afterPlace', $this, array(
'user' => $user
));
$this->eventManager()->dispatch( $event );
return true;
}
return false;
}
}
This functions does what its expected - partially so - as it does not seem to dispatch the event but only save the user data. Perhaps the issue lies with the UserStatistics table object. Below is a code snippet of how I have implemented the function that handles tallying of users.
use Cake\Event\EventListenerInterface;
class UserStatistics extends Table implements EventListenerInterface
{
//Code ommitted for in account of relevence
public function tallyUsers( $event )
{
$data = array();
if(!empty($event->subject()->user)){
$date = date('Y-m-d');
// Check existing record of today
$record = $this->find()->where(array('date' => $date));
if(empty($record)){
//Insert new record if none exist for the current date
$data = array(
'date' => $date,
'count' => 1
);
}else{
//Update record if date exist by incerementi count field by one
$count = (int) $record->count + 1;
$data = array(
'id' => $record->id,
'date' => $date,
'count' => $count
);
}
if($this->save($data))
return true;
else
return false;
}
}
}
After this I had a little misunderstanding as to where I am suppose to register the UserStatistics such that its able to observe the User object. Ofcourse I have implemented the implementedEvents() method on my UserStatistics table object (see below):
public function implementedEvents()
{
return array(
'Model.User.afterPlace' => 'tallyUsers'
);
}
I figured out that I should register my observer(UserStatistics) inside the UsersController. Below is how I went about doing it:
...
publiv function add()
{
if($this->request->is('post')){
$this->loadModel('UserStatistics');
$this->Users->eventManager()->on( $this->UserStatistics );
if($this->Users->createUser( $user )){
....
}
}
}
Question(s):
How can I access the array user passed on the Users table object i.e. array( 'user' => $user )?
What does the return true or false suppose to do inside the createUser method on Users table object?
Did I pass my observer object on my subject model inside the controller?
Should my UserStatistics tallUsers() method return anything?
Please help me understand as I couldnt find clear readings on this subject from the doc itself or any other place.
I have managed to get the two table objects talking to each other. The user statistics table is updated with a record each and everytime a new user is created by the users object which in our case is our subject.
I have suffered so many exception in order to get it to work. Basically I had to enforce a primary key because it couldnt create a new entity without an id. I want to be able to create new records when no records exist for that particular month else update the count field if a record does exist. So the problem is nowhere else but on the code below:
public function update( $event )
{
if(!empty( $event->data)){
// find current month's record
if( empty( $record )){
//Create a new row
$entity = new \App\Model\Entity\UserStatistic(['id' => 1, 'date' => $date, 'count' => 1]);
$update = $this->save($entity);
}else{
$entity = new \App\Model\Entity\UserStatistic(['id' => $record->id + 1, 'count' => (int) $record->count + 1]);
$updated = $this->save( $entity );
}
}
}
Something is wrong with this code and I cannot put my finger on but ultimately I was able to experiment with the Event System provided by the framework and the issue I am having now I want to believe is Event System related. Any help with regards to the explanation provided above would be appreciated.

Yii2 ActiveQuery use OR in Link array

I want to use OR operator in $link array in hasMany function in class extended by ActiceRecord.
For example, I want to get transactions which related whith user account. In sql it would be something like SELECT * FROM transactions WHERE fromAccountId = :id OR toAccountId = :id But how I can wrote this using Yii2
public function getTransactions() {
return $this->hasMany(Transaction::className(), [
'fromAccountId' => 'id',
'toAccountId' => 'id'
]);
}
Link ActiveQuery works with the keys of the array as name column, values - as value of column.
The array keys must be columns of the table for this relation, and the array values must be the corresponding columns from the primary table
Because the code doesn't work (where (fromAccountId, toAccountId) IN ('id','id')):
[
'fromAccountId' => 'id',
'toAccountId' => 'id'
]
You can rewrite hasMany behavior in getTransactions()
public function getTransactions() {
$query = Transaction::find();
$query->multiple = true;
return $query->where(['OR',
['fromAccountId' => $this->id],
['toAccountId' => $this->id]
]);
}
It supports native behavior, as expected:
$model->getTransactions() // return as \yii\db\ActiveQuery
$model->transactions // return as array of Transactions models
But doesn't work for $model->find()->with('transactions'), because with require setting $query->link. Instead with need to use join....
You can use the find(), it's not as nice, but do the work:
return $this->find()->join('LEFT JOIN', 'transaction', 'fromAccountId = id OR toAccountId = id')->all();
Maybe you have to use tablename.id!
I have not tried this, but you could try something like
public function getTransactions() {
return $this->hasMany(Transaction::className(), ['1' => '1'])->where('fromAccountId = id OR toAccountId = id');
}
The idea is to create a join without a condition (or with a dummy condition) then use where to get the actual results you want. This might mean a massive performance problem.

Phalcon\Mvc\Model::find() together with Phalcon\Mvc\Model\Query\Builder

I am trying to use Phalcon\Mvc\Model\Query\Builder object together with Phalcon\Mvc\Model::find() method to customize data load behaviour.
All model rows in the table have "record_status" field which is used to mark records active/inactive.
I intend to extend Model::find() and Model::findFirst() methods to always add "record_status=1" constraint to all my Model queries. But for now, I'm just trying to pass Query\Builder object on to ::find() method externally:
class User extends Phalcon\Mvc\Model
{
}
$user = new User();
/**
* #var Phalcon\Mvc\Model\Query\Builder
*/
$query = $user->getModelsManager()->createBuilder();
$query->where('email = :email:', [
'email' => 'root#yahoo.com'
])->andWhere('record_status = :status:', [
'status' => 1
])->from('users');
$found = $user->find($query);
foreach ($found as $foundUser) {
...
}
The problem is that ->find($query) returns ALL rows from the database, ignoring WHERE clauses set to $query.
When inspecting properties of $query & $user I see the following relevant protected properties:
$query::_conditions = '(email = :email:) AND (record_status = :status:)';
$query::_bindParams = array(
'email' => 'root#yahoo.com',
'status' => 1
);
$user::_count = 4; // This is wrong, this corresponds to TOTAL number of rows
$user::_result->_bindParams = NULL; // Bound parameters have disappeared
$user::_result->_sqlStatement = 'SELECT `users`.`id`, `users`.`email`, `users`.`record_status` FROM `users`'; // As you can see, there is no WHERE clause
I'm on Phalcon 1.3.0, PHP 5.5.1.
I expect find() and findFirst() methods to accept Query\Builder() object and fetch the correct records. Is this a bug or am I approaching it incorrectly?
Thanks,
Temuri
Yep you have things wrong.
The Model::find() and Model::findFirst() functions accept arrays, int or string and are wrappers for query builder.
You have at least two options:
a) use query builder directly:
// ... here is your query builder script as in question
$results = $query->getQuery()->execute();
$firstFound = $results->getFirst();
b) pass array to Model::findFirst():
$firstFound = User::findFirst(
array(
'(email = :email:) AND (record_status = :status:)',
'bind' => array('email' => 'root#yahoo.com', 'status' => 1)
)
);

Categories