Laravel belongsTo with condition and eager load - laravel

I have a Post model associated to a Section model, which depend on an extra condition to work:
<?php
class Post extends Base
{
public function section()
{
return $this->belongsTo('App\Models\Section', 'id_cat')->where('website', $this->website);
}
}
When I want to retrieve a Post and get it's associated section, I can do it as:
$post = Post::first();
echo $post->section->name; // Output the section's name
However, when trying to get the section using an eager load:
Post::with(['section'])->chunk(1000, function ($posts) {
echo $post->section->name;
});
Laravel throw the following exception :
PHP error: Trying to get property of non-object
When I do a debug of a Post object returned by the above eager load query, I notice that the section relationship is null.
Note that it is working fine if I remove the condition from the belongsTo association.
Do you guys have any ideas why it's happening?

As mentioned in my comment, where shouldn't be used in the relationship definition. Hence, your relation definition is good with just
public function section()
{
return $this->belongsTo('App\Models\Section', 'id_cat');
}
and you can eager load in this way (not giving out the exact query with chunk etc)
Post::with(['section' => function ($query) use ($request) {
$query->where('website', $request['website'])
}])->get()->first();
i.e. when you pass the variable website in request or else use any other variable in a similar way.
I hope that explains. Please add comments if anything is unclear.

You can achieve it by defining custom relationship.
BelongsToWith.php
<?php
declare(strict_types=1);
namespace App\Database\Eloquent\Relations;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class BelongsToWith extends BelongsTo
{
/**
* #var array [$foreignColumn => $ownerColumn, ...] assoc or [$column, ...] array
*/
protected $conditions = [];
public function __construct(array $conditions, Builder $query, Model $child, string $foreignKey, string $ownerKey, string $relation)
{
$this->conditions = $conditions;
parent::__construct($query, $child, $foreignKey, $ownerKey, $relation);
}
public function addConstraints()
{
if (static::$constraints) {
// Add base constraints
parent::addConstraints();
// Add extra constraints
foreach ($this->conditions as $key => $value) {
if (is_int($key)) {
$key = $value;
}
$this->getQuery()->where($this->related->getTable() . '.' . $value, '=', $this->child->{$key});
}
}
}
public function addEagerConstraints(array $models)
{
// Ignore empty models
if ([null] === $this->getEagerModelKeys($models)) {
parent::addEagerConstraints($models);
return;
}
$this->getQuery()->where(function (Builder $query) use ($models) {
foreach ($models as $model) {
$query->orWhere(function (Builder $query) use ($model) {
// Add base constraints
$query->where($this->related->getTable() . '.' . $this->ownerKey, $model->getAttribute($this->foreignKey));
// Add extra constraints
foreach ($this->conditions as $key => $value) {
if (is_int($key)) {
$key = $value;
}
$query->where($this->related->getTable() . '.' . $value, $model->getAttribute($key));
}
});
}
});
}
public function match(array $models, Collection $results, $relation)
{
$dictionary = [];
foreach ($results as $result) {
// Base constraints
$keys = [$result->getAttribute($this->ownerKey)];
// Extra constraints
foreach ($this->conditions as $key => $value) {
$keys[] = $result->getAttribute($value);
}
// Build nested dictionary
$current = &$dictionary;
foreach ($keys as $key) {
$current = &$current[$key];
}
$current = $result;
unset($current);
}
foreach ($models as $model) {
$current = $dictionary;
// Base constraints
if (!isset($current[$model->{$this->foreignKey}])) {
continue;
}
$current = $current[$model->{$this->foreignKey}];
// Extra constraints
foreach ($this->conditions as $key => $value) {
if (is_int($key)) {
$key = $value;
}
if (!isset($current[$model->{$key}])) {
continue 2;
}
$current = $current[$model->{$key}];
}
// Set passed result
$model->setRelation($relation, $current);
}
return $models;
}
}
HasExtendedRelationships.php
<?php
declare(strict_types=1);
namespace App\Database\Eloquent\Concerns;
use App\Database\Eloquent\Relations\BelongsToWith;
use Illuminate\Support\Str;
trait HasExtendedRelationships
{
public function belongsToWith(array $conditions, $related, $foreignKey = null, $ownerKey = null, $relation = null): BelongsToWith
{
if ($relation === null) {
$relation = $this->guessBelongsToRelation();
}
$instance = $this->newRelatedInstance($related);
if ($foreignKey === null) {
$foreignKey = Str::snake($relation) . '_' . $instance->getKeyName();
}
$ownerKey = $ownerKey ?: $instance->getKeyName();
return new BelongsToWith($conditions, $instance->newQuery(), $this, $foreignKey, $ownerKey, $relation);
}
}
Then:
class Post extends Base
{
use HasExtendedRelationships;
public function section(): BelongsToWith
{
return $this->belongsToWith(['website'], App\Models\Section::class, 'id_cat');
}
}
$posts = Post::with('section')->find([1, 2]);
Your Eager Loading query will be like:
select * from `sections`
where (
(
`sections`.`id` = {$posts[0]->id_cat}
and `sections`.`website` = {$posts[0]->website}
)
or
(
`sections`.`id` = {$posts[1]->id_cat}
and `sections`.`website` = {$posts[1]->website}
)
)

Related

Laravel 5.6 Many To Many Polymorphic Relations Insert Not Work

I'm use laravel 5.6 on this project. Categories value not recorded 'categorizables' pivot table. I check with f12 or bug but I do not get any errors. all of them ok but not recorded pivot table. Where I have
been mistake.
My Blog project sql structure is below
--blogs
id
title
description
...
-- categorizables
category_id
categorizable_id
categorizable_type
Below code belong to Category.php Model
class Category extends Model
{
protected $primaryKey='category_id';
public function blogs(){
return $this->morphedByMany('App\Blog', 'categorizable', 'categorizables', 'category_id');
}
}
Above code belong to Blog.php
public function category($categories)
{
$categories = Blog::buildCatArray($categories);
foreach ($categories as $catName) {
$this->addOneCat($catName);
$this->load('categories');
}
return $this;
}
public function buildCatArray($categories): array
{
if (is_array($categories)) {
$array = $categories;
} elseif ($categories instanceof BaseCollection) {
$array = $this->buildCatArray($categories->all());
} elseif (is_string($categories)) {
$array = preg_split(
'#[' . preg_quote(',;', '#') . ']#',
$categories,
null,
PREG_SPLIT_NO_EMPTY
);
} else {
throw new \ErrorException(
__CLASS__ . '::' . __METHOD__ . ' expects parameter 1 to be string, array or Collection; ' .
gettype($categories) . ' given'
);
}
return array_filter(
array_map('trim', $array)
);
}
protected function addOneCat(string $catName)
{
$cat = Self::findOrCreate($catName);
$catKey = $cat->getKey();
if (!$this->cats->contains($catKey)) {
$this->categories()->attach($catKey);
}
}
public function find(string $catName)
{
return $this->Category::$catName->first();
}
public function findOrCreate(string $catName): Category
{
$cat = $this->find($catName);
if (!$cat) {
$cat = $this->Category::create(['name' => $catName]);
}
return $cat;
}
This my Blog Controller file store class
BlogController.php
public function store(Request $request)
{
$data = new Blog;
$data->title = $request->title;
$data->content = $request->content;
$tags = explode(',',$request->tag);
$categories = explode(',',$request->category);
$data->save();
$data->tag($tags);
$data->category($categories);
}
Best wishes

Save dynamic data to two tables using relationship in Laravel

I have two tables Students and Hobbies. I've all details in one form but I want to save details to separate tables on submit. How can I achieve this?
My Student model
<?php
namespace App;
class Students extends Model {
protected $fillable = ['name', 'address'];
public function hobbies() {
return $this->hasMany(Hobby::class);
}
}
Hobby model:
<?php
namespace App;
use App\Product;
class Hobby extends Model
{
protected $fillable = [
'entertainment', 'sports'
];
public function hobby()
{
return $this->belongsTo(Student::class);
}
}
Controller:
public function save(Request $request, Student $student)
{
$students= new Student;
$students->name= request('name');
$students->address= request('address');
$students->save();
if($students->save())
{
$hobbies= [];
$images = $request->file('hob_img');
$hob_desc = $request->hob_desc;
foreach ($request->hob_name as $key => $hobby) {
$hob_img = '';
{
$hob_img = uniqid() . '.' . $files[$key]->getClientOriginalExtension();
$files[$key]->move(public_path('/assets/images/'), $hob_img);
}
$hobbies[] = [
'hob_name' => $hobby,
'hob_desc' => $hob_desc[$key],
'hob_img' => $hob_img
];
}
$hobbies[] = new Hobby;
$hobbies->hob_name = request('hob_name')[$key];
$hobbies->hob_desc= request('hob_desc')[$key];
$hobbies->hob_img = request($hob_img)[$key];
$hobbies->save();
}
}
But I cannot save it. It says SQLSTATE[HY000]: General error: 1364 Field 'student_id' doesn't have a default value
Try the insert to save many records at onece :
public function save(Request $request, Student $student)
{
$student= new Student;
$student->name= request('name');
$student->address= request('address');
if($student->save())
{
$hobbies= [];
$images = $request->file('hob_img');
$hob_desc = $request->hob_desc;
foreach ($request->hob_name as $key => $hobby) {
$hob_img = '';
$hob_img = uniqid() . '.' . $files[$key]->getClientOriginalExtension();
// $files i think it should be $images
$files[$key]->move(public_path('/assets/images/'), $hob_img);
$hobbies[] = [
'hob_name' => $hobby,
'hob_desc' => $hob_desc[$key],
'hob_img' => $hob_img,
'student_id'=> $student->id
];
}
Hobby::insert($hobbies); // useing Eloquent
// Or you can use Query Builder
// DB::table('hobbies')->insert($hobbies);
}
}

Cannot use object of type stdClass as an array in codeigniter

My controller code
function edtpost($id)
{
$this->load->model('post');
$data = $this->post->edt_post($id);
$this->load->model('category');
$data['catname'] = $this->category->retrivecat();
$this->load->view('dashboard/edit_post',$data);
}
my model code
model post
public function edt_post($id)
{
$query = $this->db->get_where('post', array('id' => $id));
if ($query->num_rows() > 0) {
foreach ($query->result() as $row) {
return $row;
}
}
return false;
}
category model
public function retrivecat()
{
$query = $this->db->get('category');
foreach ($query->result() as $row)
{
$data[] = $row->catname;
}
return $data;
}
in controller & this code $data['catname'] = $this->category->retrivecat();
I have one error:
Cannot use object of type stdClass as array
Just change your that line with following :-
$data->catname = $this->category->retrivecat();

Laravel: dynamic where clause with Elouquent

I am calling URL with search params which are dynamic. How could I form proper Eloquent query?
In theory:
query
query where(someParam1)
query where(someParam2)
query orderby(someParam3)
query get
I need this kind of structure so I can use where clause if param exists.
If there is some other way in Laravel, please let me know.
It's easy with Laravel. Just do something like this:
$query = User::query();
if ($this == $that) {
$query = $query->where('this', 'that');
}
if ($this == $another_thing) {
$query = $query->where('this', 'another_thing');
}
if ($this == $yet_another_thing) {
$query = $query->orderBy('this');
}
$results = $query->get();
You can just use the where statement.
For ex: on users table or User model, you want dynamic search on name, id. You can do this
$where = [];
$firstName = $request->get('first_name');
if ($firstName) $where[] = ['first_name', 'like'. '%' . $firstName . '%'];
$id = $request->get('id');
if ($id) $where[] = ['id', $id];
$users = User::where($where)->get();
By default, it will return all the users, if anything exists in $where array, it will apply the where condition on that.
You can use like this
$validateUserDetail = User::query();
if (!empty($userDetails['email'])) {
$validateUserDetail->whereemail($userDetails['email']);
}if (!empty($userDetails['cellphone'])) {
$validateUserDetail->wherecellphone($userDetails['cellphone']);
}
$validateUserDetail->select('username');
$validateUserDetail->get()
You can pass dynamic value by below example
$user_auctions = $this->with('userAuctions')
->where('users.id', '=', $id)
->get();
I came here from Google. If you are going to be iterating over more then 5 if statements, its more effective to use a switch statement
if(empty($request->except('_token')))
return 'false';
$models = Vehicle::query();
$request_query = $request->all();
$year_switch = false;
foreach ($request_query as $key => $field_value){
if($field_value != 'any'){
switch($field_value){
case 'X':
case 'Y':
$year_switch = true;
break;
case'Z':
//Dynamic
$models->where($key,'LIKE', $field_value);
break;
}
}
}
You can pass a callback to the where function.
So, you can do something like this:
class TestService {
TestRepository $testeRepository;
public function __construct(TesteRepository $teste) {
$this->testeRepository = $teste;
}
public function getAll(array $filters)
{
$where = function (Builder $query) use ($filters) {
collect($filters)
->each(function ($value, $param) use ($query) {
if ($param === 'test') {
$query->where($param, '=', $value);
} else if ($param === 'test2') {
$query->orWhere($param, '>', $value);
}
});
};
return $this->testRepository->gelAll($where);
}
class TestRepository
{
public function getAll(\Closure $where)
{
$query = TestModel::query();
$query->where($where);
//and put more stuff here, like:
//$query->limit(15)->offset(30)
...
return $query->get();
}
}
And in your controller you pass the filters:
class TestControler ...
{
public function $index()
{
$filters = request()->query();
return $this->testService->getAll($filters);
}
}

select fails in custom model codeigniter 2

I have a problem with database select function, in my custom model. This is the code
class MY_Model extends CI_Model
{
public function __construct()
{
parent::__construct();
$this->load->database();
$this->load->helper('inflector');
}
public function fetch($parameters = array(), $raw = FALSE)
{
$tablename = $this->getTableName();
$this->select_fields(FALSE == empty($parameters['fields']) ? $parameters['fields'] : FALSE);
unset($parameters['fields']);
if (FALSE == empty($parameters['limit'])) $limit = $parameters['limit'];
if (FALSE == empty($parameters['offset'])) $offset = $parameters['offset']; else $offset = 0;
unset($parameters['limit']);
unset($parameters['offset']);
if (FALSE == empty($limit))
{
$this->db->limit($limit, $offset);
}
$this->parseFilters($parameters);
$query = $this->db->get($tablename);
if ($query->num_rows() > 0)
{
if ($raw)
return $query;
$rows = $query->result();
$objects = array();
foreach ($rows as $row)
$objects[] = $this->hidrate($row);
return $objects;
}
else
{
return array();
}
}
protected function select_fields($fields)
{
if (TRUE == empty($fields))
{
$fields = "`" . $this->getTableName() . "`.*";
}
$this->db->select($fields);
}
public function fetchOne($parameters = array())
{
$parameters['limit'] = 1;
$list = $this->fetch($parameters);
if (FALSE == empty($list))
{
return reset($list);
}
else
{
return null;
}
}
Expecifict in $this->db->select($fields);
Fatal error: Call to a member function select() on a non-object
The model is a custom model and the applicacions model extends of this model. The question is why throws that error the database is correct.
I have a MY_loader create in codeginiter 1.7 and I try update to codeigniter 2
class MY_Loader extends CI_Loader
{
function model($model, $name = '', $db_conn = FALSE)
{
if (is_array($model))
{
foreach($model as $babe)
{
$this->model($babe);
}
return;
}
if ($model == '')
{
return;
}
if ( substr($model, -4) == '_dao' )
{
return parent::model('dao/' . $model, $name, $db_conn);
}
parent::model( 'dao/' . $model . '_dao', $model, $db_conn);
include_once APPPATH . '/models/' . $model . EXT;
}
}
I don't know how update this model to codeigniter 2 and I believe this Loader generates error with my MY_Model
I'll try troubleshooting why does db return as a non-object.
I'd remove all code and start with a simple select(), if that works, I'll start adding code gradually and see where it breaks.
everything seems to be in order but first you'll need to see if the basic functionality exists.
so
1)remove all code, see if a basic select() works, if it doesn't, troubleshoot further.
2)if it does, keep adding code and see what breaks the select() statement.
3)keep adding code until you spot the issue.

Resources