Using "With" and BelongsTo on a model which has a defined table name causes a NULL to be returned - eloquent

What I am finding is that the "picker" data does not get returned when using "with" but does if I use the default lazy loading query. It seems it might be down to the fact that the "picker" table uses a table name of "users".
So I have the following tables.
USERS [id, name]
PRODUCTS [id, name, picker_id]
I have created the following models.
class User extends BaseModel {
protected $hidden = array('pivot');
public function roles() : \Illuminate\Database\Eloquent\Relations\BelongsToMany {
return $this->belongsToMany(Role::class);
}
}
class Product extends BaseModel {
public function picker() : \Illuminate\Database\Eloquent\Relations\BelongsTo {
return $this->belongsTo(Picker::class);
}
}
class Picker extends User {
protected $table = 'users';
public function products() : \Illuminate\Database\Eloquent\Relations\HasMany {
return $this->hasMany(Product::class, 'picker_id');
}
public function pickingSequence() : \Illuminate\Database\Eloquent\Relations\HasOne {
return $this->hasOne(PickingSequence::class);
}
protected static function boot() {
parent::boot();
static::addGlobalScope('status', function (\Illuminate\Database\Eloquent\Builder $builder) {
$builder->join('role_user', 'role_user.user_id', '=', 'users.id')
->where('role_user.role_id', '=', Role::PICKER);
});
}
}
The picker and user models are using the same table (users), however the picker model filters out users so only those that have the correct role of "picker" are found. Working great so far.
What I want to do is to get all products and their relevant pickers (a product has one picker, a picker has multiple products).
Product::with('picker')->get()
This returns NULL for "picker", yet if I do:
Product::first()->picker
This works so the relationship seems fine and returns:
array(6) { ["id"]=> int(5) ["name"]=> string(8) "Picker B"
["created_at"]=> NULL ["updated_at"]=> NULL ["user_id"]=>
int(3) ["role_id"]=> int(1) }
If I change the "product" model to add a debug message It shows the relationship is working:
class Product extends BaseModel {
public function picker() : \Illuminate\Database\Eloquent\Relations\BelongsTo {
debug($this->belongsTo(Picker::class)->first());
return $this->belongsTo(Picker::class);
}
}
I am using the same syntax using "with" on other queries without a problem just can't seem to see the issue.
By changing the "belongsTo" inside the "Product" class to the following:
return $this->belongsTo(User::class, 'picker_id');
This now works and returns the picker data but I am using the wrong model, the "picker" model extends the user model.

Related

Eloquent hasMany with hasMany and a join in the middle

I have this database structure
orders ====► order_items ====► order_item_meta
║ |
║ |
▼ ▼
order_meta products
The relations are orders hasMany order_items which hasManyThrough order_item_meta, orders also hasMany order_meta.
In addition, the order_items/product_id needs to be joined with the products table.
I have the order_id and I am trying to get the whole data in one call. But I have a weird issue. This is the current code:
$orders = Orders::
with([
'order_items' => function($q) { //#1
$q->leftJoin('products','order_items.product_id', '=', 'products.id');
}
])
->with(['order_items.orderitem_meta']) //#2
->with(['order_meta']); //#3
It seems that with#1 and with#2 are interfering with each other.
Case1: If I do with#1+with#3, I am able to see in the result the data from the product table + the data from order_items, but not the data from order_item_meta.
Case2: If I do with#2+with#3, I am able to see in the result the data from the from order_items + data from order_item_meta, but not from the product table.
In both cases data from with#3 is ok.
But if I do all three together (with#1+with#2+with3) I get the same results as case1. data from order_item_meta is missing.
Orders.php
class Orders extends Model
{
public function order_items()
{
return $this->hasMany('App\OrderItem','order_id','id'); //'foreign_key', 'local_key'
}
public function order_meta()
{
return $this->hasMany('App\OrderMeta','order_id','id'); //'foreign_key', 'local_key'
}
public function orderitem_meta()
{
return $this->hasManyThrough(
'App\OrderItem',
'App\OrderItemMeta',
'order_item_id', // Foreign key on order_itemmeta table...
'order_id', // Foreign key on order_item table...
'id', // Local key on order_item table...
'id' // Local key on order_itemmeta table...
);
}
}
OrderItem.php
class OrderItem extends Model
{
public function order()
{
return $this->belongsTo('App\Orders');
}
public function orderitem_meta()
{
return $this->hasMany('App\OrderItemMeta','order_item_id','id'); //'foreign_key', 'local_key'
}
}
OrderItemMeta.php
class OrderItemMeta extends Model
{
protected $table = 'order_itemmeta';
public function orderitem()
{
return $this->belongsTo('App\OrderItem');
}
}
What is the correct way to do this query?
I solved it by adding a relationship between the order_items and the products:
in OrderItem.php
public function product()
{
return $this->hasOne('App\Products','id','product_id'); //'foreign_key', 'local_key'
}
then the query becomes this:
$orders = Orders::
with(['order_items.orderitem_meta','order_items.product','order_meta']);
and it works

Yii2 Rest Controller and public variable

I have made a REST controller with Yii2 framework. When I try to retrieve a record from my database through an ActiveRecord model, JsonFormatter give me only real attributes. How can configure JsonFormatter to give me also public variable?
This is my code:
Controller
class MyController extends yii\rest\ActiveController
{
...
public function actionView($id)
{
$struct = \common\models\Struct::find()->where(['id' => '285'])->One();
if ($struct) {
return $struct;
}
return false;
}
}
Model
/**
* property string $id;
* property string $name;
*/
class Struct extends \yii\db\ActiveRecord
{
public $test;
...
public function afterFind()
{
parent::afterFind();
$this->test = 'ok';
}
}
result of request
{"id":1,"name": "ciccio"}
but if I print variable with print_r(), I have all object
\app\models\Struct object
(
[test] => ok
[_attributes:yii\db\BaseActiveRecord:private] => Array
(
[id] => 1
[name] => ciccio
)
)
How can I get the variable test property without add an empty field on my database table?
You can override the ActiveRecord::fields() method to add the custom field that is declared as the public property of the class. The fields() method returns the names of the columns whose values have been populated into this record.
Looking at your code you are trying to set the test property inside the afterFind() and want that value to be reflected against all rows when you call the Model::find() method. If that is correct then add the following inside your model:
public function fields() {
$fields = parent::fields();
$fields['test'] = 'test';
return $fields;
}
Now when you call the \common\models\Struct::find() it will return
{"id":1,"name": "ciccio","test":"ok"}
Try this:
keep all you showed and then Override getAttributes
public function getAttributes($names = null, $except = [])
{
return array_merge(['test'=>$this->test], parent::getAttributes($names, $except));
}
and in your controller isted of return like this: return $struct;
do it like this: return $struct->attributes;

Eloquent filter on method

I have 2 tables items and naws.
With
Item::where( 'items.item_type' , '=' , $pagetype)->get();
I retrieve the object perfect, but now i wan t to filter on area in the naws table.
How can i achieve that?
Model Item:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Item extends Model
{
protected $table = 'items';
public function naw()
{
return $this->hasOne('App\Naw');
}
}
Awnser:
return Item::with([
'naw' => function($query) use ($slug)
{
$query->whereArea($slug);
}
])->where( 'items.item_type' , '=' , $pagetype)->get();

Calling child relations from model in laravel

I want to fetch the categories with products and images. I have following relation:
Product Model
class Product extends Model
{
public function productCategory() {
return $this->belongsToMany('ProductCategory');
}
public function addtionalImages() {
return $this->hasMany('ProductImage');
}
}
Product Category Model
class ProductCategory extends Model
{
public function product() {
return $this->hasMany('Bazar\Models\Product', 'product_catid')
->orderBy('id', 'DESC')->limit(10);
}
}
This is how i am using eager loading:
$categories = ProductCategory::select('product_categories.*')
->with(['product'])->Paginate(20);
This returns the category and products not the additionalImage, images are related to products not with the categories, I tried ->with(['product', 'addtionalImages']) but no success, can anyone let me know what i missed? or how do i achieve?
SOLVED
I solved it, and posting the answer so it helps to others.
$categories = ProductCategory::select('product_categories.*')
->with(['product', 'product.addtionalImages'])->Paginate(20);
With use of product.addtionalImages i can access the methods of related relation.
Portfolio Model:
public function getCats(){
return $this->hasMany(RelPortfolioCategory::class,'portfolioID','id');
}
controller:
public function portfolioDetail($slug){
$db = Portfolio::where('slug' , $slug)->with('getCats')->firstOrFail();
$dbRelated = RelPortfolioCategory::whereIn('categoryID' , $db->getCats->pluck('categoryID'))->whereNot('portfolioID' , $db->id)
->with('getPortfolioDetail')->get();
return view('portfolioDetail' , compact('db' , 'dbRelated'));
}

EF: Partial class - acces to the adapter?

let's say that I have a table TabA
namespace MyProject.Models.Database //<-- the same namespace as the EF's dbmx file
{
public partial class TabA
{
public void Foo()
{
//
}
}
}
Inside the Foo method, I need to perform some operations on the other table which isn't asosiated with the TabA In the other words, I need to access to the Entity Framework adapter inside that method. Is it possible ?
Edit
the answer is here https://stackoverflow.com/a/11135157/106616
If I understand the problem correctly, I assume you have your reasons for wanting to work on another entity from the TabA entity. If this is true, I can see two ways of doing this.
A) If you want your changes to be applied at the same time as other potential changes to the TabA Entity, then you can always pass in the context as a parameter:
namespace MyProject.Models.Database //<-- the same namespace as the EF's dbmx file
{
public partial class TabA
{
public void Foo(YourDbContext context)
{
var otherTableQuery = from x in context.SecondTable
where ...
select x;
foreach (var item in otherTableQuery)
{
item.Column1 = "A certain value";
}
}
}
}
Your calling method might look like:
public void DoChangesToTabA()
{
using ( YourDbContext context = new YourDbContext())
{
var tabAquery = from x in context.TabA
where ...
select x;
foreach( var item in tabAQuery)
{
item.LastModified = DateTime.Now;
if(???)
{
}
}
}
}
Now your changes will be applied the next time you call context.SaveChanges() from the calling method.