Seeding and pivot tables? - laravel-4

I'm trying to popular a pivot table with ids in a seed.
$id = DB::table('products')->insertGetId(array(
array(
'title' => 'Product A',
'published' => 1
)
));
DB::table('product_user')->insert(array(
array(
'product_id' => $id,
'user_id' => '9999999999'
)
));
Is the above the best way to do it? By getting an id via insertGetId and then putting it in the pivot table Is there a better way?
Also the above way gives an error:
[ErrorException]
preg_replace(): Parameter mismatch, pattern is a string while replacement i
s an array
I suspect $id is an array, how can i get InsertGetId to return an int?

Well, you can print_r it and seek for the id attribute, and then call $id->attribute on the second insert.
But...
"Me myself", I like to use Eloquent. The mainly reason is: 'Cause it too god damm FUN. Really, Eloquent it's one of the most beautful things I've ever seen in the programming world. I use to thought that .NET was a master piece of software (I was young and naive, though) but once a came across Laravel/Eloquent, I became so AMAZED!
That being said, in my humble opinion, use Eloquent is the best way of doing it!
I'm assuming that you have a table called products and another called users, and you have a product_user table to make the connection. Using Eloquent, you can simply do this:
$user = User::find($user_id);
$product = Product::find($product_id);
$product->user->attach($user);
...I reacomend this approach for several reasons, but the first one is: is way more readable.
Well, I hope I ain't been too prolixous on my answer, and that it hope you and others.

Related

Laravel Eloquent updateOrCreate doesn't work properly

I once wrote probably same question last time and I'm back..
Laravel Eloquent firstOrCreate doesn't work properly
On the last question, I found that fillable property filters update field manifest. So, if you want to update a table based on fieldA and fieldB, then your code might be..
$modelOrRelation->updateOrCreate(
['fieldA' => 'a', 'fieldB' => 'b'], ['otherfields' => 'update value']
);
and you MUST specify those fields on fillable property. $fillable = ['fieldA', 'fieldB', ...]
This is what I know about firstOrCreate and updateOrCreate.
At this time, following code generate many same rows. It looks like, the first parameter ['candle_date_time_kst'] do nothing..
// candleRelation is hasMany relation..
$candleRelation = $market->candles($period);
$created = $created->add($candleRelation->updateOrCreate(
[
'candle_date_time_kst' => $time,
],
$item
));
This creates many same candle_date_time_kst value rows. At this time, fillable property already filled target fields.
What else do I miss?
Is updateOrCreate should not trust? I didn't think so.. There are something I miss... any insight?
#220114 update
So, I do my homework..
Using DB::getQueryLog(), I get this query..
It looks like, updateOrCreate() remembers the last update value. Then if I reuse same eloquent relation object for another updateOrCreate(), method use last update parameter again. It makes and clause, so return record is none..
So, I use newQuery() method for initialize query bindings.
$created->add($candleRelation->newQuery()->updateOrCreate(
[
'candle_date_time_kst' => $time
],
$item
));
#220114
Unfortunately, retest reveals newQuery() actually not helping..
I tried $relation->newModelInstance() and getting same bindings.
What I trying to do is getting same parent binding without anything else. .. anyone knows?
Based on binding, when I get relation model I can get clean binding also. So I just do below..
$created->add($market->candles($period)->updateOrCreate(
[
'candle_date_time_kst' => $item['candle_date_time_kst']
],
$item
));
Only change is $candleRelation to $market->candles($period).
On each attempt, new relation instance produce so binding problem won't even exists.
.... I'm mad.
you need to supply an array in the format
[ column => value, ... ] not [ value ]
I had a similar problem a time ago. And the UpdateOrInsert method solved it.
Unfortunately, this method is Query Builder, not eloquent. But to achieve this result that was the only really working solution to me.
The issue for only happened when I tried to use more than 1 column on where clause, like in your example.

How to create a child by one SQL-request in Laravel?

If an author has many books ("one to many" relationship) and I want to create a child by $author_id I should do this:
$author = Author::find($author_id);
$author->books()->create([...]);
But this code produces two SQL-requests as well as this:
Author::find($author_id)->books()->create([...]);
To reduce the number of SQL-requests I should add author_id field into the $fillable array in my Book model and do this:
Book::create([
'author_id' => $author_id,
...
]);
Which approach is better? As for me, the first one looks more correct, more Eloquent way, but 2 SQL-requests are too much for such simple case. Are there any other ways to make only one SQL-request without touching the $fillable array?
The old school:
$book = new Book;
$book->author_id = $author_id;
//...
$book->save();
Or you could forceCreate, which bypasses fillable:
Book::forceCreate(['author_id' => $author_id, ...]);
As for which approach is better: if you know the author_id, then 2nd (without using the relationship). But from my experience, that's rarely the case, since you usually want to check whether the related model actually exists. But if you're confident in the correctness of your input, no need for 2 queries.

Saving a model with multiple foreign keys in Laravel 4

I understand that in order to save a foreign key, one should use the related model and the associate() function, but is it really worth the trouble of going through this
$user = new User([
'name' => Input::get('name'),
'email' => Input::get('email')
]);
$language = Language::find(Input::get('language_id');
$gender = Gender::find(Input::get('gender_id');
$city = City::find(Input::get('city_id');
$user->language()->associate($language);
$user->gender()->associate($gender);
$user->city()->associate($city);
$user->save();
when one can simply do this?
User::create(Input::all());
I feel like I'm missing something here, maybe there's an even simpler and cleaner way to handle foreign keys in controllers (and views)?
You can use push() method instead which would allow you to push to related models.
This link should answer your query.
Eloquent push() and save() difference
I really don't see anything wrong at all with doing User::create(Input::all());.
Obviously you'd want some validation, but it's doing the same thing.
I think the associate() method is more useful for the inverse of your situation.
For example, say you had a form which a user could fill out to add their city to your app, and upon doing so, they should automatically be assigned to that city.
$city = City::create(Input::all()); would only achieve the first half of your requirements because the user has not yet been attached as city does not have a user_id column.
You'd then need to do something like $city->user()->associate(User::find(Auth::user()->id));

Cake php 2: Sort on 2nd-level Association

I wrote a post last year about this. The first part, how to sort on a 1st-level Association, was answered (thanks again!) but the second part of the question, about how to sort on a 2nd-level Association, was never answered. It wasn't a big issue, and we ran out of time so I never actually implemented it. But now we're updating the web, and the client wants this functionality, namely, with the following Models:
Company belongsTo City
City belongsTo Country, hasMany Companies
Country hasMany Cities
In the Companies page I want to sort on City.Country.name. Even putting recursive=2, it doesn't work. Cake ignores my 'order' condition; in the generated SQL there simply is no 'order by' at all.
It works fine if I sort on sort on City.name, however.
Is there any way to do this? I've been scouring the docs and Stackoverflow. I've looked at virtual fields, custom queries.
One way that seemed to look promising was to use Model->query() in the CompaniesController:
$companies = $this->Company->query("SELECT * FROM companies com left join cities c on c.id = com.city_id left join countries c2 on c2.id = c.country_id order by c2.name");
But, is this the best/only way to go? And do I not have to now worry about overriding pagination? I don't mind that but I would still like to use "normal" built-in pagination elsewhere for Companies. Will that be possible?
Also, in the examples in the docs, it says to do something like Model->query('SELECT * FROM pictures AS Picture LIMIT 2') so the resulting Array will use the model name as the array key. But how can I do this with my complex query? Where would the "AS" go?
I was rather hoping I'd be able to avoid having to do it like this though. Is there a simpler way to do it?
EDIT
Hi, thanks for your help. By "pagination technique" you mean Cake's built-in pagination? Yes, that's what I want. My default paging conditions in the controller are:
$this->paginate = array('conditions' => 'order' => array('Company.name' => 'ASC');
And it sorts on company name. The SQL is
SELECT Company.id, etc. FROM companies AS Company LEFT JOIN cities AS City ON Company.city_id = City.id order by Company.name
And when I create paging links in the View like this
$paginator->sort('City.name')
it adds these parameters to the url
.../companies/sort:City.name/direction:desc
it sorts on City name. The SQL is
SELECT Company.id, etc. FROM companies AS Company LEFT JOIN cities AS City ON Company.city_id = City.id order by City.name
But when I try this:
$paginator->sort('City.Country.name');
it adds these parameters to the url
.../companies/sort:City.Country.name/direction:asc
and the generated SQL is
SELECT Company.id, etc. FROM companies LEFT JOIN cities AS City ON (Company.city_id = City.id)
It completely ignores the 'sort' condition, and there is no 'order by' at all. I'm not sure why. Maybe Cake just can't do this? I have 'recursive' set to 2.
The other option is Model->query, which I tried and got working, but I'd rather not use because I would have to override paginate and paginateCount methods, which isn't that hard, but the problem is that on the same page, and on other pages, I am already using 'normal' Cake paging for Companies. So if I override paginate and paginateCount, won't I have to change all of those pages to use the new, over-ridden paging? I'd like to avoid that, because it seems like overkill, and is working fine everywhere else, except for this one case.
Any assistance much appreciated.
Bob
Well, in the end I managed it, after perusing the docs and many examples online, like this:
First, in the Model, declare and implement custom findMethods.
class Company extends AppModel {
public $findMethods = array('companiesOrderByCountry' => true);
protected function _findCompaniesOrderByCountry($state, $query, $results = array()) {
if ($state === 'before') {
$query['joins'] = array(
array(
'table' => 'cities',
'alias' => 'City',
'type' => 'LEFT',
'conditions' => 'City.id = Company.city_id'
),
array(
'table' => 'countries',
'alias' => 'Country',
'type' => 'LEFT',
'conditions' => 'Country.id = City.country_id'
)
);
$query['order'] = array('Country.name' => 'asc');
return $query;
}
return $results;
}
Then in the Controller, conditionally call it, based on named params in request
if (!empty($this->request->params['named']['sort']) && $this->request->params['named']['sort'] == 'Country.name') {
// here we need to sort on Country.name. Call custom findMethod in Model
$this->paginate = array('companiesOrderByCountry','limit' => 10);
$companies = $this->paginate();
$this->set(compact('companies'));
} else {
// do a normal search
$this->paginate = array('limit' => 10,'conditions' => array('order' => array('Company.nombre' => 'ASC' ));
$companies = $this->paginate('Company');
$this->set('companies', $companies);
}
Lastly, create a link, passing the named parameter, in the .ctp
<?php $paginator = $this->Paginator; ?>
<?php echo $paginator->sort('Country.name', __('Country')); ?>
There's probably a more elegant way to do this, but I was sick of dealing with it. The generated sql is just what I need and now I can sort on Country, which is a 2nd-level (Company->City->Country) association.
Hope this helps someone someday!
Bob
If you want to try using the query method, then I would not use the asterisks (*). Instead explicitly list the fields you want.
Your query should look something like this: {My personal best practice, use COALESCE on fields that may be NULL - especially when using a LEFT JOIN}
SELECT companies.name AS CompanyName,
COALESCE(cities.name, 'Unknown') AS CityName,
COALESCE(countries.name, 'Unknown') AS CountryName
FROM companies
LEFT JOIN cities
ON companies.city_id = cities.id
LEFT JOIN countries
ON cities.country_id = countries.id
ORDER BY CountryName, CityName
Use AS to separate the field from its alias. It also separates a table from its alias ( e.g., the examples you listed.) AS is optional, but I feel it adds to readability.
--
(If you want to try the pagination technique, please show the MySQL SELECT statement that is failing when RECURSIVE = 2, along with what the controller's code looks like - specifically the 'order'=> array)

Laravel 4: making a combination of values/columns unique

I'm importing a bunch of csv entries in my database with Laravel 4.
I can't really point at one column that has to be unique, it's a combination of 5 columns that makes it unique. However: how does one define this in Laravel?
Option 1: schema builder
You can use the $table->unique('email') method, but that only seems to allow one column, not a combination of columns.
Option 2: Validation
Less preferable, but I could validate the model before inserting it. However, again, using 'unique:[table]' validation rules, it will return an error when just one of the column values isn't unique, not a combination of them.
Can anyone tell me how I should go about this?
I'm sure I'm missing something, but I could use a push in the right direction :-)
Thanks,
Dieter
You can combine:
$table->unique( array('email','name') );
And pretty much everything in Laravel will accept arrays to do whatever you need to with 'more than one'.
Use Schema Builder's unique() method to define your data model, as Antonio mentioned.
Additionally, if you want to use validation on your model, consider my custom Validator rule for multiple UNIQUE indexes: https://github.com/felixkiss/uniquewith-validator
You can also do this;
$table->unique(["column1", "column2"], 'uq_columns');
Which means that you will have a unique column combination of all the columns i.e. column1 and column2
I know this question is for Laravel 4, but I just came across this on searches and found a solution for Laravel >= 5.3
Here it is:
Of course, the migration may look something like
$table->unique( array('email','name') );
Then to validate this, you do not need to use custom rules, just advanced rules:
'email' => Rule::unique('users')->where(function ($query) use ($request) {
return $query->where('name', $request->name);
}),
Of course, you may want to validate name before of this. The name should be required so that you may finish with something like this:
'name' => 'required|max:255',
'email' => Rule::unique('users')->where(function ($query) use ($request) {
return $query->where('name', $request->name);
}),
I hope it helps.
You can try this
$table->string("name");
$table->string("email")->unique("name")

Resources