Active Record Query with Calculated Field - activerecord

I am trying to use a query with a calculated field in a Yii Relationship definition but all I get is errors.
Here is my query: $me = new CDbExpression('CONCAT_WS(\', \', last_name, first_name) AS the_name');
Here is my relation: 'author' => array(self::BELONGS_TO, 'Author', 'auth_id', 'select'=>$me),
My problem seems to be that CDbExpression is expecting a parameter but the query requires no parameters!?!?!?
I'm getting an Error 500 "trim() expects parameter 1 to be string, array given" (because I have no parameter!?!).
If I add a fake parameter: $me = new CDbExpression('CONCAT_WS(\', \', last_name, first_name) AS the_name',array('test'=>'test'));
I get the same error message.
What am I doing wrong?

It sounds like trim() is getting passed an array instead of a string. The "param" referred to isn't the "params" array passed into CDbExpression as the second parameter, but rather the single param passed in to trim().
Could CDbExpression be returning an array instead of a string to the "select" clause in your author relation? I would verify that $me is a string, and not an array.
Also, where is trim() being called? This might shed some light on the subject as well. Turn on all your error reporting to get the file and line number. There are a few places in the DB code where trim() is called.

Try this way:
'author' => array(self::BELONGS_TO, 'Author', 'auth_id', 'select'=>array($me)),

Related

Splitting laravel query returns massive odd result

I have this query in Laravel (5.3):
$menu_categories = DB::table('categories')->get();
It returns the categories from the database as expected. However, if I split it (so that I can add other parameters):
$menu_categories = DB::table('categories');
$menu_categories->get();
It returns a massive collection that starts like this:
Illuminate\Database\Query\Builder Object
(
[connection:protected] => Illuminate\Database\MySqlConnection Object
(
[pdo:protected] => PDO Object
(
)
[readPdo:protected] =>
[reconnector:protected] => Closure Object
(
[this] => Illuminate\Database\DatabaseManager Object...
What is happening here? I've searched for a while and can't find anything on this - although I admit I'm not sure what to search for. Note that I am NOT adding any additional parameters when it has the weird return collection - I saw the weird return initially with extra params (orderBy and whereIn) but even after removing those it still returns this weirdness.
The "non split" line is creating a query object for the categories table, calling get() on the query object and assigning the results of get() to the $menu_categories variable.
In the split version, the first line creates a query object for the categories table and sets it in the $menu_categories variable.
In the second line, calling get() on $menu_categories doesn't actually mutate the variable. Instead it returns the collection that you're looking for but it's not being assigned to anything. This is why you just see this massive object when you dump it.
The correct 2 line version would look something like this:
$query_object = DB:table('categories');
$menu_categories = $query_object->get();

Returning and accessing JSON fields in Eloquent

I have a model called EmploymentApplication that uses a JSON field to hold all of the submitted form data when someone submits their application. This field is simply called "data". I'm working on the resource controller's 'index' method. I only want to return the id, and the first name and last name (that's inside the 'data' JSON field).
I'm trying to figure it out in Tinker (there is currently only one application in the DB):
$result = \App\Models\EmploymentApplications\EmploymentApplication::all(['id', 'data->fname', 'data->lname'])
returns:
Illuminate\Database\Eloquent\Collection {#888
all: [
App\Models\EmploymentApplications\EmploymentApplication {#881
id: 1,
`data`->'$."fname"': ""Betty"",
`data`->'$."lname"': ""Sue"",
},
],
}
So, It's returning the correct data. The part that has me stumped is how to access that data->fname and data->lname from here.
$result[0]->id works just fine.
$result[0]->(backtick)data(backtick)->'$."lname"'
throws:
PHP Parse error: Syntax error, unexpected '`', expecting T_STRING or T_VARIABLE or '{' or '$' on line 1
What is the correct way to access these properties?
I know I could just use array_map to clean up the result before returning it from the controller, but what is the best practice here?
Thank you.
Use column aliases:
$result = EmploymentApplication::all(['id', 'data->fname as fname', 'data->lname as lname']);
$result[0]->fname
$result[0]->lname
I am not sure if this is what you mean, but if I am using tinker and want to get information from my database, this is what I use.
DB::select("SELECT id, fname, lname FROM EmploymentApplication")
or if you are looking for a specific id.
DB::select("SELECT id, fname, lname FROM EmploymentApplication WHERE id=1")

Difference between [attributes:protected] and [original:protected]

Please could anyone explain to me a difference between [attributes:protected] array and [original:protected] array in laravel when using print_r to an array?
When Model reads data from table, arrays 'original' and 'attribute' contains same data. When you change the attribute value (ex $user->name='John'), the change is reflected only on the 'attributes' array but 'original' remains same. (hence the name).
When update() on a model is called, method checks what has changed comparing two arrays and construct query only for changed fields. Thus, in the case of $users->name change Laravel will not create this code:
UPDATE users set name = 'John', password = 'pass', email = 'email' where id = 1
but this:
UPDATE users set name = 'John' where id = 1
This may not be the only way Eloquent uses 'original' array. I found clockwork helpful when you need to see what's going on under the hood of Eloquent.

Get Attribute from Elequent where clause

So I have the following where clause where I am searching the name "Gold" inside of my tier table.
$tier = Tier::where(['name' => 'Gold'])->get(['id']);
dd($tier);
When I do the above the "id" is listed inside of the attributes but when I do as below it gives me
Undefined property: Illuminate\Database\Eloquent\Collection::$id
dd($tier->id);
All I want is to get the Id of the record where the name is "Gold".
To get actual model you should use first() instead of get(). Because there are potentially multiple rows that match get() will return a collection.
$tier = Tier::where(['name' => 'Gold'])->first(['id']);
dd($tier->id);
If you really need only the id there's even a simpler function to that, pluck().
It will get one attribute from the first result:
$id = Tier::where(['name' => 'Gold'])->pluck('id');

First Or Create

I know using:
User::firstOrCreate(array('name' => $input['name'], 'email' => $input['email'], 'password' => $input['password']));
Checks whether the user exists first, if not it creates it, but how does it check? Does it check on all the params provided or is there a way to specifiy a specific param, e.g. can I just check that the email address exists, and not the name - as two users may have the same name but their email address needs to be unique.
firstOrCreate() checks for all the arguments to be present before it finds a match. If not all arguments match, then a new instance of the model will be created.
If you only want to check on a specific field, then use firstOrCreate(['field_name' => 'value']) with only one item in the array. This will return the first item that matches, or create a new one if not matches are found.
The difference between firstOrCreate() and firstOrNew():
firstOrCreate() will automatically create a new entry in the database if there is not match found. Otherwise it will give you the matched item.
firstOrNew() will give you a new model instance to work with if not match was found, but will only be saved to the database when you explicitly do so (calling save() on the model). Otherwise it will give you the matched item.
Choosing between one or the other depends on what you want to do. If you want to modify the model instance before it is saved for the first time (e.g. setting a name or some mandatory field), you should use firstOrNew(). If you can just use the arguments to immediately create a new model instance in the database without modifying it, you can use firstOrCreate().
As of Laravel 5.3 it's possible to do this in one step with firstOrCreate using a second optional values parameter used only if a new record is created, and not for the initial search. It's explained in the documentation as follows:
The firstOrCreate method will attempt to locate a database record using the given column / value pairs. If the model cannot be found in the database, a record will be inserted with the attributes resulting from merging the first array argument with the optional second array argument.
Example
$user = User::firstOrCreate([
'email' => 'dummy#domain.example'
], [
'firstName' => 'Taylor',
'lastName' => 'Otwell'
]);
This returns the User for the specified email if found, otherwise creates and returns a new user with the combined array of email, firstName, and lastName.
This technique requires Mass Assignment to be set up, either using the fillable or guarded properties to dictate which fields may be passed into the create call.
For this example the following would work (as a property of the User class):
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = ['email', 'firstName', 'lastName'];
firstOrCreate() checks for all the arguments to be present before it finds a match.
If you only want to check on a specific field, then use firstOrCreate(['field_name' => 'value']) like:
$user = User::firstOrCreate([
'email' => 'abcd#gmail.com'
], [
'firstName' => 'abcd',
'lastName' => 'efgh',
'veristyName'=>'xyz',
]);
Then it checks only the email.
An update:
As of Laravel 5.3 doing this in a single step is possible; the firstOrCreate method now accepts an optional second array as an argument.
The first array argument is the array on which the fields/values are matched, and the second array is the additional fields to use in the creation of the model if no match is found via matching the fields/values in the first array:
See the Laravel API documentation
You can always check if in current instance the record is created with the help of
$user->wasRecentlyCreated
So basically you can
if($user->wasRecentlyCreated){
// do what you need to do here
}

Resources