Laravel error using with() method in Eloquent - laravel

I'm trying to call using with() method in Laravel Eloquent ORM, but getting the following error.
Argument 1 passed to
App\Http\Controllers\DashboardController::App\Http\Controllers\{closure}()
must be an instance of Illuminate\Database\Eloquent\Builder,
instance of Illuminate\Database\Eloquent\Relations\HasMany given
I'm using the latest version of Laravel 6. Any ideas what might have caused this?
Controller
class DashboardController extends Controller
{
public function __construct()
{
$this->middleware('auth:api');
}
public function formIndex(Request $request)
{
$id = auth()->user()->id;
$item = Groupe::find($id)->with(
[
'etudiants' => function (Builder $query) {
$query->select('id');
}
]
)->first();
return $item;
}
}
Model
class Groupe extends Authenticatable implements JWTSubject
{
public function etudiants()
{
return $this->hasMany('App\Etudiant');
}
}

The error comes from the type hint you put on the $query variable, as the error message said the object that gets passed in there is a relationship not a raw Query Builder. Just remove the type hint. Also ::find() executes a query, so you're be executing 2 queries, use the query below instead
Groupe::where('id', $id)->with(['etudiants' => function ($query) {
$query->select('id');
}])->first();
Additionally, you don't need to use the callback syntax to only eager load certain columns, the callback syntax is for putting contraints on which records get returned. Try this instead.
Groupe::where('id', $id)->with('etudiants:id,name,email')->first();

But what do you want return? groupe->students[]?? you can use
$item = Groupe::where('id',$id)->with('etudiants')->first();

Related

Laravel 5.8 query scope

I am learning laravel and I encountered this problem where when I use query scope my code returns zero data. The database has got data.
It is kinda confusing because I think I have done everything right as per the tutorial
Scope:
public static function scopeLatest($query)
{
return $query->orderBy('id', 'asc')->get();
}
Controller:
public function index()
{
$posts = Post::Latest();
return view('posts.index', compact('posts'));
}
AFAIK, you won't be able to use scopeLatest as Laravel already has a latest() method on its query builder.
As for the scope you tried to make, here are a few pointers:
a scope shouldn't be defined as static method
you shouldn't actually call get() inside your scope
You don't need to return from the scope.
So even though this scope won't actually work (because of the name), as an example, this is what is would look like based on your question:
public function scopeLatest($query)
{
$query->orderBy('id', 'desc'); //desc should put the latest first
}
Your controller method (in either case) should be:
public function index()
{
$posts = Post::latest()->get();
return view('posts.index', compact('posts'));
}

Method addEagerConstraints does not exist

I have two models, User and Event. I made a pivot table, invitations between User and Event with a status column. In my Event model definition, I wrote this :
public function invited()
{
return $this->belongsToMany(User::class, 'invitations', 'event_id', 'user_id')
->withTimestamps()
->withPivot('status')
->orderByDesc('invitations.updated_at');
}
public function participants()
{
$event = $this->with([
'invited' => function ($query) {
$query->where('invitations.status', InvitationStatus::ACCEPTED)->get();
}
])->first();
return $event->invited;
}
But when in my controller I do :
$event = Event::where('id', $id)->with(['owner', 'participants'])->first();
I have the following error :
(1/1) BadMethodCallException
Method addEagerConstraints does not exist.
Does someone know why?
When you're trying to do this:
->with('participants')
Laravel expects to get a relationship instance. In other words, you can't use this method as a relation.
where should come before with. This is what eloquent expects.
It should be like this:
$event = Event::with(['owner', 'participants'])->where('id', $id)->first();

PhpUnit - mocking laravel model with relations

I'm trying to mock (it's example only) $user->posts()->get().
example service:
use App\Models\User;
class SomeClass{
public function getActivePost(User $user): Collection
{
return $user->posts()->get();
}
}
and my Model:
and Model:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use \App\Models\Post;
class User extends Model
{
public function posts() : HasMany
{
return $this->hasMany(Post::class);
}
}
this doesn't work:
$this->user = Mockery::mock(User::class);
$this->user
->shouldReceive('wallets->get')
->andReturn('test output');
error:
TypeError: Return value of Mockery_2_App_Models_User::posts() must be an instance of Illuminate\Database\Eloquent\Relations\HasMany, instance of Mockery_4__demeter_posts returned
without return type hint (on post() method) everything is ok. Must I modify andReturn()? idk how
This error can be solved by using the alias prefix with a valid class name. Like the following:
$m = m::mock('alias:App\Models\User');
More information can be found at the official documentation http://docs.mockery.io/en/latest/reference/creating_test_doubles.html#aliasing
Alternatively you can use like this.
use App\Models\User;
class SomeClass{
public function getActivePost(User $user): Collection
{
$user->load('posts');
return $user->posts;
}
}
First you need to mock post, then add it to Collection (don't forget to use it in the top). Then when you call posts attribute its takes mocked $posts. In this case it will not throw error about return type.
use Illuminate\Database\Eloquent\Collection;
$post = $this->mock(Post::class)->makePartial();
$posts = new Collection([$post]);
$this->user = Mockery::mock(User::class);
$this->user
->shouldReceive('getAttribute')
->with('posts');
->andReturn($posts);
Also i wouldn't use mocks here. There is absolutely no need for it. So the unit test i write would be:
Create a user.
Create some posts authored by the user.
Perform assertions on user & posts.
So the code will then be something like this in my test:
$user = factory(User::class)->create();
$posts = factory(Post::class, 5)->create(['user_id' => $user->id]);
$this->assertNotEmpty($user->id);
$this->assertNotEmpty($posts);
$this->assertEquals(5, $posts->fresh()->count());
$this->assertEquals($user->id, $post->fresh()->first()->user_id);
if you want to test the relationship you can:
/** #test */
function user_has_many_posts()
{
$user = factory(User::class)->create();
$post= factory(Post::class)->create(['user_id' => $user->id]);
//Check if database has the post..
$this->assertDatabaseHas('posts', [
'id' => $post->id,
'user_id' => $user->id,
]);
//Check if relationship returns collection..
$this->assertInstanceOf('\Illuminate\Database\Eloquent\Collection', $user->posts);
}

Laravel pass variable from controller query to model function

I have a function on my model that check active status in equipment_station table,
originally it only checked one status, now i need to pass a parameter to the model with active or inactive values, I've tried this with no success.
controller:
model :
How can i change the parameter in the model, I send status from controller as inactive or active.
thanks in advance
You could you query scope
public function scopeActive($query, $active)
{
return $query->where('active', $active);
}
then you can call it in your model
Estacion::active(true)->get();
You could Constraint Eager Loading by specifying additional query constraints for the eager loading
$historial_estaction =
Estacion::where('estacion.id', $id)
->whereHas('equipos', function($query) use ($estado1) {
$query->where('equipo_estacion.estado', $estado1);
})->with(['equipos' => function ($query) use ($estado1) {
$query->where('estado', $estado1);
}])->get();
Model:
public function equipos()
{
return $this->belongsToMany('App\Equipo')
->withPivot('estado')
->withTimestamps();
}

Global filtering - how to use global scope in Laravel Eloquent

I have a published filter that I use for my articles. Guests can only view published articles, logged in users can view and apply filter (?published=0/1):
public function scopePublishedFilter($query)
{
if(!Auth::check()) $query->where('published', '=', 1);
else
{
$published = Input::get('published');
if (isset($published)) $query->where('published', '=', $published);
}
return $query;
}
I apply this in my ArticlesController:
public function index()
{
return View::make('articles.index', [
'articles' => Article::with('owner')
->with('category')
->with('tags')
->publishedFilter()
->get()
]);
}
And on the article relationships:
public function articles()
{
return $this->hasMany('Article')->publishedFilter();
}
But ideally I would like to only define it in the Article model itself, since it's easy to forget to include this filter when implementing new features or views.
How can I make sure that all returned articles from the Article model are run through this filter before returned?
UPDATE: Just use this: https://github.com/jarektkaczyk/laravel-global-scope for global scopes in L5+
Better way is a bit too long to paste it and works like SoftDeleting thing in the core.
Read this if you want it http://softonsofa.com/laravel-how-to-define-and-use-eloquent-global-scopes/
Short way: you need global scope for this. And here's how you do it in 2 steps (squashed a bit):
1 Create a class PublishedScope that implements ScopeInterface
class PublishedScope implements ScopeInterface {
public function apply(Builder $builder)
{
$table = $builder->getModel()->getTable();
$builder->where($table.'.published', '=', 1);
$this->addWithDrafts($builder);
}
public function remove(Builder $builder)
{
$query = $builder->getQuery();
$column = $builder->getModel()->getTable().'.published';
$bindingKey = 0;
foreach ((array) $query->wheres as $key => $where)
{
if ($this->isPublishedConstraint($where, $column))
{
unset($query->wheres[$key]);
$query->wheres = array_values($query->wheres);
$this->removeBinding($query, $bindingKey);
}
// Check if where is either NULL or NOT NULL type,
// if that's the case, don't increment the key
// since there is no binding for these types
if ( ! in_array($where['type'], ['Null', 'NotNull'])) $bindingKey++;
}
}
protected function removeBinding(Builder $query, $key)
{
$bindings = $query->getRawBindings()['where'];
unset($bindings[$key]);
$query->setBindings($bindings);
}
protected function addWithDrafts(Builder $builder)
{
$builder->macro('withDrafts', function(Builder $builder)
{
$this->remove($builder);
return $builder;
});
}
2 Boot that class in your Eloquent model by calling static::addGlobalScope(new AbcScope)
// the model
public static function boot()
{
parent::boot();
static::addGlobalScope(new PublishedScope);
}
If I were you I would use published_at column and check it for null instead of = 1, but that's up to you.
edit remove method updated - thanks to #Leon for pointing out unexpected behaviour, when using this scope together with SoftDeletingTrait. The problem is a bit deeper:
when you use this one with SoftDeletingScope or another one, that utilizes NULL or NOT NULL constraint and this scope is not the first one used (yes, order of use statements matters here), remove method will not work as expected. It will not remove any binding or not the one, that it should.
you can use trait and add your method or filter thing in booting method check the following
http://laravel.com/docs/4.2/eloquent#global-scopes

Resources