Yii2 ActiveQuery use OR in Link array - php

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.

Related

Laravel : Get all columns except some of them [duplicate]

When I'm using eloquent, I can use the "where" method then the method 'get' to fill an object containing what I've selected in my database.
I mean:
$users = User::where('gender', 'M')->where('is_active', 1)->get(['pseudo', 'email', 'age', 'created_at'])->toArray();
Here I can choose the columns I want to get like 'pseudo', 'email', etc..
But what I miss in laravel doc is the way to do the contrary.
It could be something like that:
$users = User::where('gender', 'M')->where('is_active', 1)->notGet(['pseudo', 'email', 'age', 'created_at'])->toArray();
Thank you for you futur answer and have a nice day.
If you only need to hide attributes from your model's array or JSON representation, you may use one or both approaches:
Add the
$hidden property to your model
class User extends Model
{
/**
* The attributes that should be hidden for arrays.
*/
protected $hidden = ['password'];
}
Use the
makeHidden
function
$users = $users->makeHidden(['address', 'phone_number']);
See other answers for more details... But sometimes you don't want to load huge data (geospatial, html, logs...) into your application, it will be slow and take more memory. OP asked for an SQL query hence my answer, but most of the time it's more convenient to only hide the data from the JSON response.
AFAIK there is no build in option in SQL to exclude columns explicitly, so Laravel can't do it. But you can try this trick
Update
Another trick is to specify all columns in your model (or use an extra query to get all columns using $this->getTableColumns() from this answer, it can also be cached after each migration to avoid two queries) then add a local scope function
// The below code requires you to define all columns in $columns.
// A better approach is to query the schema of the table and cache it after each
// migration, for more details: https://stackoverflow.com/a/56425794/3192276
protected $columns = ['id','pseudo','email'];
public function scopeExclude($query, $value = [])
{
return $query->select(array_diff($this->columns, (array) $value));
}
Then you can do :
$users = User::where('gender', 'M')
->where('is_active', 1)
->exclude(['pseudo', 'email', 'age', 'created_at'])
->toArray();
using hidden array in model is good, but if you don't want to hide your column all the time and use makeVisible to access them in need, then instead, hide your column from serialization where you need with makeHidden function like this :
$res = Model::where('your query')->get();
$res->makeHidden(['column_one','column_two','column_n']);
return response()->json($res);
I don't know about previous Laravel version, but in 5.4 you can put this line in User model
protected $hidden = ['pseudo', 'email', 'age', 'created_at'];
and then User::find(1); will return all fields except pseudo, email, age, and created_at.
But you still can retrieve those hidden fields by using:
$user = User::find(1);
$email = $user['email']; // or $user->email;
I have looked into the answer by #Razor
But there is Very Conveinent way by skipping $columns property
/**
* Scope a query to only exclude specific Columns.
*
* #author Manojkiran.A <manojkiran10031998#gmail.com>
* #param \Illuminate\Database\Eloquent\Builder $query
* #return \Illuminate\Database\Eloquent\Builder
*/
public function scopeExclude($query, ...$columns)
{
if ($columns !== []) {
if (count($columns) !== count($columns, COUNT_RECURSIVE)) {
$columns = iterator_to_array(new \RecursiveIteratorIterator(new \RecursiveArrayIterator($columns)));
}
return $query->select(array_diff($this->getTableColumns(), $columns));
}
return $query;
}
/**
* Shows All the columns of the Corresponding Table of Model
*
* #author Manojkiran.A <manojkiran10031998#gmail.com>
* If You need to get all the Columns of the Model Table.
* Useful while including the columns in search
* #return array
**/
public function getTableColumns()
{
return \Illuminate\Support\Facades\Cache::rememberForever('MigrMod:'.filemtime(database_path('migrations')).':'.$this->getTable(), function () {
return $this->getConnection()->getSchemaBuilder()->getColumnListing($this->getTable());
});
}
getTableColumns function will get all the columns of the table so you dont need to define the $column property
NOTE: COLUMN NAMES OF TABLE WILL BE CACHED UNTIL CONTENTS OF MIGRATIONS DIRECTORY IS ADDED OR DELETED.
MODIFYING THE CONTENTS OF FILES INSIDE THE MIGRATIONS DIRECTORY WILL
NOT RE-CACHE THE COLUMNS
To clear cache manually you can run php artisan cache:clear
you can use hidden array like this:
class Promotion extends Model
{
protected $table = 'promotion';
protected $hidden = array('id');
}
I have a solution that worked for me, which is slightly different than those already stated.
Solution:
$all_columns = Schema::getColumnListing('TABLE_NAME');
$exclude_columns = ['COLUMN_TO_EXCLUDE_1', 'COLUMN_TO_EXCLUDE_2'];
$get_columns = array_diff($all_columns, $exclude_columns);
return User::select($get_columns)->get();
Reasoning:
For me:
Razor's answer didn't work as I got the following error:
BadMethodCallException with message 'Call to undefined method App/CaseStudy::exclude()'
Then, the remaining answers were attemping to hide the columns within the model. Unfortunately, that would hide them for each method in my class and this isn't something that I wanted.
So, in the end, I modified Razor's solution so that it would work without having to hide any of the columns for each method.
I hope this helps someone! 😊
We get the object eloquent from the model full with all fields, transform it to array and we put it inside of a collection. Than we get all fields except all fields specified in array $fields.
$fields = ['a', 'b', 'c', 'N'];
$object = Model::find($id);
return collect($object->toArray())->except($fields);
More clearly, let's give an example:
// Array of fields you want to remove
$fields_to_remove = ['age', 'birthday', 'address'];
// Get the result of database
$user = User::find($id);
// Transform user object to array
$user = $user->toArray();
// Create a collection with the user inside
$collection = collect($user);
// Get all fields of our collection except these fields we don't want
$result = $collection->except($fields_to_remove);
// Return
return $result;
This example above makes exactly the same thing of the first one, but it's more explained.
you can use makeHidden array like this: (After get() or all())
$users = User::where('gender', 'M')->where('is_active', 1)->get()->makeHidden(['pseudo', 'email', 'age', 'created_at'])->toArray();
You can leverage Illuminate\Support\Facades\Schema::getColumnListing('table_name');
use Illuminate\Support\Facades\Schema;
$users_table_columns = Schema::getColumnListing('users');
$exclude_columns = [
'password',
'token',
'address',
];
$select = array_diff($users_table_columns, (array) $exclude_columns);
$site = User::select($select)
->where('gender', 'M')
->where('is_active', 1)
->first();
I wrapped a slitly changed approach from #manojkiran-a up in a small package, cause I needed it in multiple projects:
https://github.com/laracraft-tech/laravel-useful-traits/#selectallbut
Install via composer:
composer require laracraft-tech/laravel-useful-traits
This is how it is working:
use LaracraftTech\LaravelUsefulTraits\UsefulScopes;
$class = new class extends Model
{
use UsefulScopes;
protected $timestamps = false;
protected $table = 'scope_tests';
};
$class->create([
'foo' => 'foo',
'bar' => 'bar',
'quz' => 'quz',
]);
$class::query()->selectAllBut(['foo'])->first()->toArray();
// return ['bar' => 'bar', 'quz' => 'quz']
Note: Since you can't do a native "select all but x,y,z" in mysql, we need to query (and cache) the existing columns of the table, and then exclude the given columns which should be ignored (not selected) from the existing columns.
Cache: Column names of each table will be cached until contents of migrations directory is added or deleted. Modifying the contents of files inside the migrations directory will not re-cache the columns. Consider to clear the cache whenever you make a new deployment/migration!
You can use unset unset($category->created_at,$category->updated_at);
$fcategory = array();
$kCategory = KCategory::where("enabled", true)->get();
foreach ($kCategory as $category) {
$subkCategory = PostCategory::select("id", "name", "desc")
->where("id_kcategory", $category->id)
->where("enabled", true)
->get();
unset($category->created_at, $category->updated_at);
$fcategory[] = $category;
}

How to refactor php foreach using Laravel (Eloquent's) map collection function

I've just watched Adam Wathan's video on Refactoring Loops and Conditionals, and feel like I can use the map collection method in the sumLeagueStats method on my Team model (rather than the foreach).
I have a relationship on teams -> leagues, and the getLeagueStats function gets all of the stats (played, won, drew, lost, for, against, points) from the leagues table for the relevant team.
In the sumLeagueStats method I was going to use a foreach loop and loop through each stat by year, and take the sum of all of the played, etc, and return it, but having watched the above video,
class Team extends Model {
public function league()
{
return $this->hasMany('league');
}
public function getLeagueStats($year = [2018])
{
return $this->league()->whereIn('year', [$year])->get();
}
public function sumLeagueStats($year = [2018])
{
foreach {
...
return
}
/*
* Want to return a colleciton with the following:
*
$this->getLeagueStats()->sum('played');
$this->getLeagueStats()->sum('won');
$this->getLeagueStats()->sum('drew');
$this->getLeagueStats()->sum('lost');
$this->getLeagueStats()->sum('for');
$this->getLeagueStats()->sum('against');
$this->getLeagueStats()->sum('points');
*/
}
}
I'm new to Laravel, so firstly want to check. my suspicions are correct, and secondly looking for any insight/resource for more information, as the docs are slightly lacking).
Calling getLeagueStats everytime is making a request to your database everytime you call the sum method, so you can create a variable to work with and create a collection that represents the data :
$stats = $this->getLeagueStats();
return collect([
'played' => $stats->sum('played'),
'won' => $stats->sum('won'),
'drew' => $stats->sum('drew'),
'won' => $stats->sum('won'),
'lost' => $stats->sum('lost'),
'for' => $stats->sum('for'),
'points' => $stats->sum('points')
]);
is that a what you want ?

Laravel: Seeding multiple unique columns with Faker

Introduction
What up folks, I got a question about model factories and multiple unique columns:
Background
I have a model named Image. This model has language support stored in a separate model, ImageText. ImageText has an image_id column, a language column and a text column.
ImageText has a constraint in MySQL that the combination image_id and language has to be unique.
class CreateImageTextsTable extends Migration
{
public function up()
{
Schema::create('image_texts', function ($table) {
...
$table->unique(['image_id', 'language']);
...
});
}
...
Now, I want each Image to have several ImageText models after seeding is done. This is easy with model factories and this seeder:
factory(App\Models\Image::class, 100)->create()->each(function ($image) {
$max = rand(0, 10);
for ($i = 0; $i < $max; $i++) {
$image->imageTexts()->save(factory(App\Models\ImageText::class)->create());
}
});
Problem
However, when seeding this using model factories and faker, you are often left with this message:
[PDOException]
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '76-gn' for key 'image_texts_image_id_language_unique'
This is because at some point, inside that for loop, the faker will random the same languageCode twice for an image, breaking the unique constraint for ['image_id', 'language'].
You can update your ImageTextFactory to say this:
$factory->define(App\Models\ImageText::class, function (Faker\Generator $faker) {
return [
'language' => $faker->unique()->languageCode,
'title' => $faker->word,
'text' => $faker->text,
];
});
But then, you instead get the problem that the faker will run out of languageCodes after enough imageTexts have been created.
Current solution
This is currently solved by having two different factories for the ImageText, where one resets the unique counter for languageCodes and the seeder calls the factory which resets te unique counter before entering the for loop to create further ImageTexts. But this is code duplication, and there should be a better way to solve this.
The question
Is there a way to send the model you are saving on into the factory? If so, I could have a check inside the factory to see if the current Image has any ImageTexts attached already and if it doesn't, reset the unique counter for languageCodes. My goal would be something like this:
$factory->define(App\Models\ImageText::class, function (Faker\Generator $faker) {
$firstImageText = empty($image->imageTexts());
return [
'language' => $faker->unique($firstImageText)->languageCode,
'title' => $faker->word,
'text' => $faker->text,
];
});
Which of course currently gives:
[ErrorException]
Undefined variable: image
Is it possible to achieve this somehow?
I solved it
I searched a lot for a solution to this problem and found that many others also experienced it. If you only need one element on the other end of your relation, it's very straight forward.
The addition of the "multi column unique restriction" is what made this complicated. The only solution I found was "Forget the MySQL restriction and just surround the factory creation with a try-catch for PDO-exceptions". This felt like a bad solution since other PDOExceptions would also get caught, and it just didn't feel "right".
Solution
To make this work I divided the seeders to ImageTableSeeder and ImageTextTableSeeder, and they are both very straight forward. Their run commands both look like this:
public function run()
{
factory(App\Models\ImageText::class, 100)->create();
}
The magic happens inside the ImageTextFactory:
$factory->define(App\Models\ImageText::class, function (Faker\Generator $faker) {
// Pick an image to attach to
$image = App\Models\Image::inRandomOrder()->first();
$image instanceof App\Models\Image ? $imageId = $image->id : $imageId = null;
// Generate unique imageId-languageCode combination
$imageIdAndLanguageCode = $faker->unique()->regexify("/^$imageId-[a-z]{2}");
$languageCode = explode('-', $imageIdAndLanguageCode)[1];
return [
'image_id' => $imageId,
'language' => $languageCode,
'title' => $faker->word,
'text' => $faker->text,
];
});
This is it:
$imageIdAndLanguageCode = $faker->unique()->regexify("/^$imageId-[a-z]{2}");
We use the imageId in a regexify-expression and add whatever is also included in our unique combination, separated in this case with a '-' character. This will generate results like "841-en", "58-bz", "96-xx" etc. where the imageId is always a real image in our database, or null.
Since we stick the unique tag to the language code together with the imageId, we know that the combination of the image_id and the languageCode will be unique. This is exactly what we need!
Now we can simply extract the created language code, or whatever other unique field we wanted to generate, with:
$languageCode = explode('-', $imageIdAndLanguageCode)[1];
This approach has the following advantages:
No need to catch exceptions
Factories and Seeders can be separated for readability
Code is compact
The disadvantage here is that you can only generate key combinations where one of the keys can be expressed as regex. As long as that's possible, this seems like a good approach to solving this problem.
I built on Rkey's answer to suit my needs:
problem
I have two integer fields that together should be unique, these are product_id and branch_id.
solution
Heres's my approach:
Get the total number of products and branches. Since the ids are generated from 1, the ids shall range from 1 to the-total-count-of-items-in-the-table(s).
Create all possible unique values that can be created from product_id and branch_id by creating a string separated by a character, in this case -
Generate unique random values from this set using the randomElements function.
Split the random element back to product_id and branch_id
$branch_count = Branch::all()->count();
$product_count = Product::all()->count();
$branch_products = [];
for ($i = 1; $i <= $branch_count; $i++) {
for ($j = 1; $j <= $product_count; $j++) {
array_push($branch_products, $i . "-" . $j);
}
}
$branch_and_product = $this->faker->unique->randomElement($branch_products);
$branch_and_product = explode('-', $branch_and_product);
$branch_id = $branch_and_product[0];
$product_id = $branch_and_product[1];
return [
// other fields
// ...
"branch_id" => $branch_id,
"product_id" => $product_id
];
Your solution only works for things that can be regexified as a combination. There are many use cases where a combination of multiple separate Faker generated numbers/strings/other objects need to be unique and cannot be regexified.
For such cases you can do something like so:
$factory->define(App\Models\YourModel::class, function (Faker\Generator $faker) {
static $combos;
$combos = $combos ?: [];
$faker1 = $faker->something();
while($faker2 = $faker->somethingElse() && in_array([$faker1, $faker2], $combos) {}
$combos[] = [$faker1, $faker2];
return ['field1' => $faker1, 'field2' => $faker2];
});
For your specific question / use case, here's a solution on the same lines:
$factory->define(App\Models\ImageText::class, function (Faker\Generator $faker) {
static $combos;
$combos = $combos ?: [];
// Pick an image to attach to
$image = App\Models\Image::inRandomOrder()->first();
$image instanceof App\Models\Image ? $imageId = $image->id : $imageId = null;
// Generate unique imageId-languageCode combination
while($languageCode = $faker->languageCode && in_array([$imageId, $languageCode], $combos) {}
$combos[] = [$imageId, $languageCode];
return [
'image_id' => $imageId,
'language' => $languageCode,
'title' => $faker->word,
'text' => $faker->text,
];
});
Here is another way you can handle the unique constraint problem in table seeder class.
I will take a model called JobCategory as an example.
For JobCategory, the column "title" has a unique constraint.
In the factory class:
$factory->define(JobCategory::class, function (Faker $faker) {
return [
'title' => $faker->words(3, true),
'description' => $faker->paragraphs(2, true),
];
});
Then, in the seeder class:
class JobCategoryTableSeeder extends Seeder
{
private $failures = 0;
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
try {
factory(JobCategory::class, 30)->create();
} catch(Exception $e) {
if($this->failures > 5) {
print_r("Seeder Error. Failure count for current entity: " . $this->failures);
return;
}
$this->failures++;
$this->run(); // retry again until the number of failure is greater than 5
}
}
}
Explanation:
The idea is to catch the exception which could result from unique constraint failure and then retry seeding by calling the method recursively until an exit condition is met.
I the example above, I want to create 30 records, but due to exceptions retries, I might get more or less than 30 records.
I chose 5 retries, you can use any appropriate number of retries.
I'm using Laravel 8.x and I don't know if the column function definition that I use works in previous versions.
I had the same problem and use a diferent aproach.
I create the ImageTextFactory this way:
<?php
namespace Database\Factories;
use App\Models\ImageText;
use Illuminate\Database\Eloquent\Factories\Factory;
class ImageTextFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* #var string
*/
protected $model = ImageText::class;
/**
* The number of models created till now.
*
* #var integer
*/
protected $created = 0;
/**
* Define the model's default state.
*
* #return array
*/
public function definition()
{
$this->created++;
return [
'language' => function (array $attributes) {
$count = ImageText::where(
'image_id',
$attributes['image_id']
)
->count();
$reset = $this->created == 1 && $count == 0;
return $this->faker->unique($reset)->languageCode();
},
'title' => $this->faker->word(),
'text' => $this->faker->sentence(),
];
}
}
Then I call the factory from the seeder as:
Image::factory()
->count(10)
->has(
ImageText::factory()->count(rand(0, 10))
)->create();
With the function in the definition I am able to check if there is previously defined ImageText for that image_id and how many Models are generated. As an ImageTextFactory instance is generated for each ImageFactory it automatically resets the $created counter to 0; and as the Seeders will always creates images in a sequential order, it must no generate problems.
It has a disadvantage, if the factory is called for Models that already exists, it will generate an OverflowException from Faker, as there is no new id to reset the unique constraint. It should only be generated with the has method.

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)
)
);

Creating and Update Laravel Eloquent

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

Categories