Laravel 5 - Override Get or Select from Eloquent - php

Is it possible to override the ->get() methods from Eloquent in order to always customize the output of the selected fields from the DB query?
For example, if you do a usual Eloquent query like:
User::where('email','like','%wherever.com')->get();
Instead of it making the query:
Select name, email, address, created_at from users where email like '%wherever.com'
Is it possible to override the method to always return something like:
Select CONCAT('CL::',name), lower(email), address, created_at from users where email like '%wherever.com'
I ask for ->get because I have seen that I can pass an array with the columns to be selected but I don't want to define them on all queries.

So, after digging around the functions regarding the query I found this solution:
I created a new class CustomQueryBuilder that extends the Illuminate\Database\Query\Builder
There you can override the get() / select() / where() methods from Eloquent.
Then, in the models that you want to change the way the query is made, define the fields to change like:
protected $encrypted = [
'name',
'email',
];
After this, I created a new class CustomModel that extends the Illuminate\Database\Eloquent\Model and there override the newBaseQueryBuilder like this:
protected function newBaseQueryBuilder()
{
$connection = $this->getConnection();
return new CustomQueryBuilder(
$connection, $connection->getQueryGrammar(), $connection->getPostProcessor(), $this
);
}
Inside the CustomQueryBuilder you can customise all the methods from builder for your needs.
With this setup, you can in any Illuminate\Database\Eloquent\Model change it to extend your CustomModel and inherit this special behaviour for the designated columns.
Be aware that all queries made from Models extending your CustomModel will get this new methods, so do all the needed checks to don't mess up with Eloquent normal behaviour, something like this:
public function where($column, $operator = null, $value = null, $boolean = 'and')
{
if ($this->model !== null && isset($this->model::$encrypted))
{
if (in_array($column, $this->model::$encrypted))
{
$column = DB::raw("CONCAT('CL::',$column)");
}
}
parent::where($column, $operator, $value, $boolean);
}
PS: I know this sound silly with the CONCAT example but with the property $encrypted you can figure out that it's not for concatenating string.

You can use model to update the result for the specific field like below
Use this code in your model file to contact CL::' with thename` value
public function getNameAttribute($value) {
return 'CL::'.$value;
}
You can just call User::where('email','like','%wherever.com')->get(); like this
This getNameAttribute function will always return name value with "CL::"
So you don't need to add CONCAT('CL::'.name) with your query.
Same way you can add for other fields also
Updated
Solution when querying the result
Add this geofields in your model
protected $geofields = array("concat('CL::',name) as name");
Add this newQuery function to override the columns
public function newQuery($excludeDeleted = true)
{
$raw='';
foreach($this->geofields as $column){
$raw .= $column;
}
return parent::newQuery($excludeDeleted)->addSelect('*',\DB::raw($raw));
}
Hope this is what you expect.

Related

Prevent Laravel Accessor involved when do query just for some column

I've made accessor for formatting date on my table model,
and this is my model looks like for the accessor
class Agent Authenticatable implements MustVerifyEmail
{
protected $appends = ['created_at_formatted'];
...
public function getCreatedAtformattedAttribute()
{
return $this->created_at->format('d-m-Y');
}
...
}
there is no problem with that if I retrieve all data from the table,
but the problem comes if I only take some data, for example, I take only phone and email for specific User, and the created_at column not involved,
and, my query on controller looks like this:
public function getAgentDetail($id)
{
$agDetail = Agent::where('id','=' ,50)->get(['phone','email']);
return response()->json($agDetail);
}
and the query above gives me an error :
Symfony\Component\Debug\Exception\FatalThrowableError
Call to a member function format() on null
and I think it's because no value was given to the accessor (in this case created_at),
so I change the accessor on my model to this:
public function getCreatedAtformattedAttribute()
{
$this->created_at
? $this->created_at->format('d-m-Y')
: null;
}
it only gives a condition if it is not set, it will return a null value,
The error disappears and returns this collection :
[
{
phone: "086741111111",
email: "example#jab.com",
created_at_formatted: null
}
]
What I want is, how to ignore accessor involved when only select specify column, which has nothing to do with the accessor,
for example, I just want to get the phone and email, and not for created_at_formatted
Remove the following line and try again:
protected $appends = ['created_at_formatted'];
Acessors can be called without appending them. Also you may want to change the function name to getCreatedAtFormattedAttribute to respect Laravel's convention.

use relationship in model accessor in laravel

Suppose I have a Course model like this :
class Course extends Model
{
public $primaryKey = 'course_id';
protected $appends = ['teacher_name'];
public function getTeacherNameAttribute ()
{
$this->attributes['teacher_name'] = $this->teacher()->first()->full_name;
}
public function teacher ()
{
return $this->belongsTo('App\User', 'teacher', 'user_id');
}
}
And in the other hand there is a User model like this :
class User extends Authenticatable
{
public $primaryKey = 'user_id';
protected $appends = ['full_name'];
public function getFullNameAttribute ()
{
return $this->name . ' ' . $this->family;
}
public function course ()
{
return $this->hasMany('App\Course', 'teacher', 'user_id');
}
}
As you can see there is a hasMany relationship between those.
There is an full_name accessor in User model.
Now I want to add a teacher_name accessor to Course model that uses it's teacher relations and gets full_name of teacher and appends to Course always.
In fact I want whenever call a Course model, it's related teacher name included like other properties.
But every time , when call a Course model , I got this error :
exception 'ErrorException' with message 'Trying to get property of non-object' in D:\wamp\www\lms-api\app\Course.php:166
That refers to this line of Course model :
$this->attributes['teacher_name'] = $this->teacher()->first()->full_name;
I do not know how can I solve that and what is problem exactly.
Yikes some interesting answers here.
FYI to those coming after me- getFooAttribute() should return the data, and not modify the internal attributes array.
If you set a new value in the attributes array (that doesnt exist in this model's db schema) and then attempt to save the model, you'll hit a query exception.
It's worth reading up the laravel docs on attribute accessors/mutators for more info.
Furthermore, if you need to access a related object from within the model (like in an accessor) you ought to call $related = $this->getRelation('foo'); - note that if the relation isnt loaded (e.g., if you didnt fetch this object/collection with eager loaded relations) then $this->getRelation() could return null, but crucially if it is loaded, it won't run the same query(ies) to fetch the data again. So couple that with if (!$this->relationLoaded('foo')) { $this->loadRelation('foo'); }. You can then interact with the related object/collection as normal.
$this->attributes['teacher_name'] = $this->teacher()->first()->full_name;
Should be
$this->attributes['teacher_name'] = $this->teacher->full_name;
First thing is that you want to reference the relationship, so loose the brackets (), and because the relationship is belongsTo, you will have one user / teacher returned. So you don't need the first().
We haven't seen your fields but probably you will have to change:
return $this->belongsTo('App\User', 'teacher', 'user_id');
to
return $this->belongsTo('App\User', 'foreign_key', 'other_key');
where foreign_key and other_key are the primary keys that you need to make the join on.
Check this link from the documentation for reference:
https://laravel.com/docs/5.4/eloquent-relationships#one-to-many-inverse
the right way to do this is:
COURSE
public function setTeacherNameAttribute ()
{
$this->attributes['teacher_name'] = $this->teacher->full_name;
}
100% working for me.
I have one to one relationship between Order and Shipment. I have to add the accessor of shipments table column from orders table.
function getOrderNoAttribute()
{
$appendText = "OR100";
if($this->orderShipment()->first()) {
$appendText = $this->orderShipment()->first()->is_shipping === 1 ? "ORE100" : "OR100";
}
return $appendText . $this->attributes['id'];
}
This error is only object data to array use or array data to object data use.
example::
$var->feild insted of $var[feild]
$var[feild] insted of $var->feild
You should use return for accessors . something like this :
public function getTeacherNameAttribute ()
{
return $this->teacher()->first()->full_name ?? '';
}
maybe a course hasn't teacher.

Scope And Relationship on Same Model

I have a table called payments which contains a field called Vendor ZIP.
I have a table called 201502_postcodes and my "join" in this case is the postcode field in this table.
How do I return field values in this 201502_postcodes table using Eloquent?
My Models are;
<?php namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Payment extends Model {
public function postcodeExtract()
{
return $this->belongsTo('App\Models\PostcodeExtract', 'postcode', 'Vendor ZIP');
}
_
<?php namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class PostcodeExtract extends Model {
protected $connection = 'postcodes';
public function scopeFromTable($query, $tableName)
{
return $query->from($tableName);
}
public function payment()
{
return $this->hasMany('App\Models\Payment', 'Vendor ZIP', 'postcode');
}
So, I have a scope on this model because the 201502 part of my table name is a variable (in that, a new one comes in every quarter).
In my controller... I have no idea what to put. I don't know how to get both scope and relationship to work. How can I write a query that will take a postcode/zip and output one of the fields from the (do I refer to them as "methods"?) postcode extract table?
It is not a duplicate of this question Laravel 4: Dynamic table names using setTable() because relationships are not involved or discussed on that question.
--- UPDATE ---
If I am to use getTable - would it go something like this...
class PostcodeExtract {
public function setTableByDate($selected_tablename)
{
$this->table = $selected_tablename;
// Return $this for method chaining
return $this;
}
public function getTable()
{
if (isset($this->table))
$this->setTableByDate($this->table);
return $this->table;
}
}
And then I would use it in my controller like;
$selected_tablename = 201502_postcode //created by some other controller
$postcode_extract = new PostcodeExtract;
$data = $postcode_extract->setTableByDate($selected_tablename)->get()->toArray();
The Carbon stuff isn't really relevant. I have a lookup to get those tablenames the fact the prefix with a date like value shouldn't mean it's treated like a date.
There are a couple of things going on here.
scopeFromTable() is redundant
Laravel employs magic methods to handle calls to undefined methods. Calling from() on the model will actually call from() on the models internal Query object (assuming you didn't define a method called 'from' on the model itself). It's worth reading the __call and __callStatic methods on the Model class.
relationships use getTable()
Another aspect of the Laravel is the concept of convention over configuration. This basically means that the framework assumes some things so that you don't have to define every detail. In regards to table naming convention, it will naturally use a table name derived from the class name.
// Uses table 'foos'
class Foo {}
There are a few ways to change this behavior. First, you can define a 'table' data member like this.
class Foo {
protected $table = 'bars';
}
If you need a more dynamic behavior, then you can redefine the getTable method.
class Foo {
public function getTable()
{
// return your special table name based on today's date
}
}
Ultimately the models and their relationships refer to getTable to figure out what the table names should be.
your use cases
If you only ever need to query the current table, then I would suggest redefining getTable.
If you need to query both current and past tables, then I suggest pairing a new method along side redefining getTable
class Foo {
public function setTableByDate(\DateTime $date)
{
$this->table = // generate table name from $date
// Return $this for method chaining
return $this;
}
public function getTable()
{
if (isset($this->table))
$this->setTableByDate(\Carbon\Carbon::now());
return $this->table;
}
}
With this in place, you don't have to worry about the table name in your controller or anywhere else unless you need to query past records.
setting the table by date per user
$foos = Foo::setTableByDate($user->some_date)->where(...)->get();

Laravel Removing Pivot data in many to many relationship

Not sure if I set this up correctly. In Laravel I'm creating two models with a many-to-may relationship
The models are Item and Tags. Each one contains a belongsTo to the other.
When I run a query like so:
Item::with('tags')->get();
It returns the collection of items, with each item containing a tags collection. However the each tag in the collection also contains pivot data which I don't need. Here it is in json format:
[{
"id":"49",
"slug":"test",
"order":"0","tags":[
{"id":"3","name":"Blah","pivot":{"item_id":"49","tag_id":"3"}},
{"id":"13","name":"Moo","pivot":{"item_id":"49","tag_id":"13"}}
]
}]
Is there anyway to prevent this data from getting at
you can just add the name of the field in the hidden part in your model like this:
protected $hidden = ['pivot'];
that's it , it works fine with me.
You have asked and you shall receive your answer. But first a few words to sum up the comment section. I personally don't know why you would want / need to do this. I understand if you want to hide it from the output but not selecting it from the DB really has no real benefit. Sure, less data will be transferred and the DB server has a tiny tiny bit less work to do, but you won't notice that in any way.
However it is possible. It's not very pretty though, since you have to override the belongsToMany class.
First, the new relation class:
class BelongsToManyPivotless extends BelongsToMany {
/**
* Hydrate the pivot table relationship on the models.
*
* #param array $models
* #return void
*/
protected function hydratePivotRelation(array $models)
{
// do nothing
}
/**
* Get the pivot columns for the relation.
*
* #return array
*/
protected function getAliasedPivotColumns()
{
return array();
}
}
As you can see this class is overriding two methods. hydratePivotRelation would normally create the pivot model and fill it with data. getAliasedPivotColumns would return an array of all columns to select from the pivot table.
Now we need to get this integrated into our model. I suggest you use a BaseModel class for this but it also works in the model directly.
class BaseModel extends Eloquent {
public function belongsToManyPivotless($related, $table = null, $foreignKey = null, $otherKey = null, $relation = null){
if (is_null($relation))
{
$relation = $this->getBelongsToManyCaller();
}
$foreignKey = $foreignKey ?: $this->getForeignKey();
$instance = new $related;
$otherKey = $otherKey ?: $instance->getForeignKey();
if (is_null($table))
{
$table = $this->joiningTable($related);
}
$query = $instance->newQuery();
return new BelongsToManyPivotless($query, $this, $table, $foreignKey, $otherKey, $relation);
}
}
I edited the comments out for brevity but otherwise the method is just like belongsToMany from Illuminate\Database\Eloquent\Model. Of course except the relation class that gets created. Here we use our own BelongsToManyPivotless.
And finally, this is how you use it:
class Item extends BaseModel {
public function tags(){
return $this->belongsToManyPivotless('Tag');
}
}
If you want to remove pivot data then you can use as protected $hidden = ['pivot']; #Amine_Dev suggested, so i have used it but it was not working for me,
but the problem really was that i was using it in wrong model so i want to give more detail in it that where to use it, so you guys don't struggle with the problem which i have struggled.
So if you are fetching the data as :
Item::with('tags')->get();
then you have to assign pivot to hidden array like below
But keep in mind that you have to define it in Tag model not in Item model
class Tag extends Model {
protected $hidden = ['pivot'];
}
Two possible ways to do this
1. using makeHidden method on resulting model
$items = Item::with('tags')->get();
return $items->makeHidden(['pivot_col1', 'pivot_col2']...)
2. using array_column function of PHP
$items = Item::with('tags')->get()->toArray();
return array_column($items, 'tags');

Where to put model code in ORM ? (Eloquent)

I dont quite understand where my code goes for this orm.
class Brand_model extends MY_Model {
public function add_brand($name, $size)
{
//goal:
// $sql = "insert into brand (name, size_id) values (?,?)";
// $query = $this->db->query($sql, array($name, $size));
$brand = new self();
$brand->name=$name;
$brand->size=$size;
$brand->save();
}
This produces a new row in the database, in the appropriate table, but with no data inside of it. However I am sure those variables are filled. Any ideas?
My design pattern pre orm is to put almost everything in the model. That way if multiple controllers need the same data structure, i call a function once and it handles all the validation/etc.
THanks!
Please find the link for eloquent and codeigniter integration
http://mannyisles.com/using-eloquent-orm-inside-codeigniter.html
To use the ORM, first of all, you would need the connection set up. You can look into this answer. It tells you how to set up eloquent DB connection.
Once it is done, you need to create a model, which would extends the ORM, and do all your DB calls using that model.
You can create a file - let's say, FooModel.php like this:
<?php
namespace Models\FooModel;
use Illuminate\Database\Eloquent\Model;
class FooModel extends Model
{
protected $table = 'sample_table';
protected $fillable = array('comma', 'seperated', 'column', 'names');
protected $hidden = array('id');
public $timestamps = true;
}
?>
$fillable tells eloquent which columns you want to write into. you can ignore the auto incremented columns and the columns with default values.
$timestamps handle your created_at and updated_at columns, if you have any.
When you have to create the row, you can call the eloquent create() function. This function requires an associative array with column names as key and values as the corresponding value you wanna insert to that column.
$row = array('name' => 'Foo',
'email' => 'foo#bar.com'
);
You can then just call the create function, with $row as a parameter.
$response = FooModel::create($row);
If you do a var_dump($response); you can see the status of the create(). true for success or the error message.
For further info you can check out the Docs. It's really not that hard!
Cheers! :)

Categories