I would like to mock the model TemplateLibraryCategory so that I don't have to hit the database on my unit test. Whenever I run the test I get the following:
There was 1 error:
1) Tests\Unit\Http\Filters\V2\Template\TemplateLibraryFilterTest::filter_is_applied_when_category_query_is_passed_with_value
Illuminate\Database\Eloquent\ModelNotFoundException: No query results for model [App\Models\Checklist\TemplateLibraryCategory].
Which gives me the impression that the model is not being mocked as it should be.
This is how I'm mocking the model TemplateLibraryCategory
/**
* #test
* #group filters
* #group template-library
* #group filter-template-library
*/
public function filter_is_applied_when_category_query_is_passed_with_value()
{
$mock = Mockery::mock(TemplateLibraryCategory::class);
$mockBuilder = Mockery::mock(Builder::class);
$templateCategory = $this->createPartialMock(TemplateLibraryCategory::class, ['withSubcategories']);
$mockBuilder->shouldReceive('firstOrFail')->once()->andReturn($templateCategory);
$mock->shouldReceive('where')->once()->andReturn($mock);
$this->request->expects($this->any())->method('query')->willReturn(['category' => 'test-slug']);
$this->SUT = new TemplateFilter($this->request);
$checklistQuery = TemplateLibraryTemplate::filter($this->SUT);
$this->assertStringContainsString('`template_id` and `id` in', $checklistQuery->toSql());
}
Then on my TemplateFilter I have the following:
public function category(string $slug): void
{
$category = TemplateLibraryCategory::where('slug', $slug)->firstOrFail();
$this
->builder
->whereHas('categories', function ($query) use ($category) {
$categories = $category->withSubcategories()->pluck('id');
$query->whereIn('id', $categories);
});
}
By now I would guess that TemplateLibraryCategory is being mocked and both the method where and firstOrFail is mocked as well. Why is the test hitting the database?
Related
I have a Laravel job class and I'm initializing a Collection attribute with an Eloquent join query. Inside the constructor everything works fine.
The problem comes in the handle method of the class, that specific attribute losses the value initalized previously in the constructor. With a simple Eloquent query everything works well, the value persists.
Here's how the constructor looks like and the model class where the Eloquent join query is made:
class CreateResponseFile extends Job
{
/**
* #var Collection
*/
protected Collection $records;
public function __construct(ResponseWriter $parser, $body)
{
$responseID = $body["response_id"];
$this->parser = $parser;
$this->response = Response::where('id', $responseID)->firstOrFail();
if($parser instanceof WriterCsv){
$this->records = ResponseRecord::getConciliatedRecords($this->response);
}else{
$this->records = ResponseRecord::where('response_id', $responseID)->get();
}
$this->file = $body["path"];
}
}
class ResponseRecord extends Model
{
public static function getConciliatedRecords(Response $response)
{
$records = self::where("response_id", $response->id)
->join("conciliated_trxs", function($join){
$join->on("response_records.id", "=", "conciliated_trxs.response_trx_id");
})
->join("presentation_records", function($join){
$join->on("presentation_records.id", "=", "conciliated_trxs.presentation_trx_id");
})
->get();
return $records;
}
}
Maybe I don't understand this idea but I'm starting create my first test app that use api platform.
I have custom action for my search:
/**
* #Route(
* path="/api/pharmacies/search",
* name="pharmacy_search",
* defaults={
* "_api_resource_class"=Pharmacy::class,
* "_api_collection_operation_name"="search"
* }
* )
* #param Request $request
*
* #return Paginator
*/
public function search(Request $request)
{
$query = SearchQuery::createFromRequest($request);
return $this->pharmacyRepository->search($query);
}
and method in the repository:
public function search(SearchQuery $searchQuery: Paginator
{
$firstResult = ($searchQuery->getPage() - 1) * self::ITEMS_PER_PAGE;
$qb = $this->createQueryBuilder('p')
/...
->setFirstResult($firstResult)
->setMaxResults(self::ITEMS_PER_PAGE);
$doctrinePaginator = new DoctrinePaginator($qb, false);
return new Paginator($doctrinePaginator);
}
And it works fine but this action doesn't need all field form the entity and relations to other tables. Currently this action creates 22 queries. I'd like create query in the DBAL/QueryBuilder and return pagination object with DTO.
public function search(SearchQuery $searchQuery)
{
.../
$qb = $this->createQueryBuilder('p')
->select('p.id')
->addSelect('p.name')/....
$rows = $qb->getQuery()->getArrayResult();
foreach ($rows as $row){
$data[] = new SearchPharmacy($row['id'], $row['name']);
}
return $data;
}
The above code will work but if the response isn't Pagination object and I don't have hydra:totalItems, hydra:next etc in the response.
In theory I can use DataTransformer and transform entity to DTO, but this way can't allow simplify database queries.
Can I achieve this?
I don't know how to use dbal query and mapped the result on a DTO, but I know how to add custom select, return it like a scalar:
public function search(SearchQuery $searchQuery): Paginator
{
$firstResult = ($searchQuery->getPage() - 1) * self::ITEMS_PER_PAGE;
$qb = $this->createQueryBuilder('p')
->select('p.id')
->addSelect('p.name')
->where('p.name LIKE :search')
->setParameter('search', "%" . $searchQuery->getSearch() . "%");
$query = $qb->getQuery()->setFirstResult($firstResult)
->setMaxResults(self::ITEMS_PER_PAGE);
$doctrinePaginator = new DoctrinePaginator($query, false);
$doctrinePaginator->setUseOutputWalkers(false);
return new Paginator($doctrinePaginator);
}
It finally I have 2 light queries, selected field from entity and Pagination.
Sources:
https://github.com/APY/APYDataGridBundle/issues/931
https://www.doctrine-project.org/projects/doctrine-orm/en/2.8/cookbook/dql-custom-walkers.html
I have this problem with Laravel Eloquent.
I have two models - for simplicity's sake named:
A (id, name)
B (id, a_id, created_at)
relationship: A hasMany B
I need to return all B records filtered by these conditions:
A.name = given_name
B.created_at >= given_date
I want to do this by passing in a closure.
I searched through the laravel documentation on models:
https://laravel.com/docs/7.x/eloquent
https://laravel.com/docs/7.x/eloquent-relationships
and found these examples, but how do I put this together?
public function user()
{
return $this->belongsTo('App\User')->withDefault(function ($user, $post) {
$user->name = 'Guest Author';
});
}
function (Builder $builder) {
$builder->where('age', '>', 200);
}
You can do it using whereHas but first you need to define the relationship in your models like so:
A Model
class A extends Model
{
/**
* Get all B for the A.
*/
public function b()
{
return $this->hasMany(B::class);
}
}
B Model
class B extends Model
{
/**
* Get the B's A.
*/
public function a()
{
return $this->belongsTo(A::class);
}
}
Now after we defined the relationships just use whereHas with a Closure to check for extra conditions:
$allB = B::whereHas('a', function (Builder $query) {
$query->where('A.name', $givenName);
})->where('created_at','>=',$givenDate)->get();
You need to write this on A model
class A extends Model
{
public function aToB()
{
return $this->hasMany('App\B', 'a_id', 'id');
}
}
Then you need to write the query
$userData = A::where('name', $givenName)->with('aToB');
$userData = $userData->whereHas('aToB', function($query) use($givenDate){
$query->whereDate('created_at', date('Y-m-d', strtotime($givenDate)));
});
$userData = $userData->get();
dd($userData->aToB);
i have this table structure, project has one to many relation with rewards , rewards and shipping has many to many relation with pivot table reward_ship.
projects rewards shipping reward_ship
--------- -------- -------- ------------
id id id id
title amount location reward_id
amount project_id name ship_id
i am trying to extract one particular project details with all other associate tables data(rewards and shipping data using reward_ship table) in one query.
These is how i am trying
Projects Model
class Rewards extends Model {
public function projs(){
return $this->hasMany('App\Rewards');
}
public function rewds(){
return $this->belongsToMany('App\Shipping')
->withPivot('reward_ship', 'ship_id', 'reward_id');
}
public function shiplc(){
return $this->belongsToMany('App\Rewards')
->withPivot('reward_ship', 'ship_id', 'reward_id');
}
}
class Rewards extends Model {
public function proj() {
return $this->belongsTo('App\Projects');
}
}
Controller api class
Route::get('projects/{id}', function($id) {
$p = Projects::find($id);
$getd = Rewards::with('proj')
->where('rewards.project_id', '=', $p->id)
->get();
});
it doesn't work.
i search and tried many related model base query in larvel.
i know my implementation are wrong. Please suggest me to work out.
You can use Laravel 5.5 new feature API Resources.
It helps you to format the output of objects such as models or collections, to display attributes and also relationships.
So, you could do something like this in your ItemResource:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
class Project extends Resource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request
* #return array
*/
public function toArray($request)
{
return [
'project_id' => $this->project_id,
'title' => $this->title,
'amount' => $this->amount,
// To access relationship attributes:
'rewards' => $this->rewards->load('shippings'),
];
}
}
Then in your controller, you just need to create a new Resource instance and pass the item object that you want to return:
use App\Http\Resources\Project as ProjectResource;
// some code
/**
* Show a single formatted resource.
*
* #param Project $project
* #return ProjectResource
*/
public function show($project)
{
return new ProjectResource($project);
}
// the rest of your code
The output should be the expected.
You have to fix the relationships that you have :
Projects Model :
public function rewards(){
return $this->hasMany('App\Rewards');
}
Rewards Model :
public function projects() {
return $this->belongsTo('App\Projects');
}
public function shippings(){
return $this->belongsToMany('App\Shipping','reward_ship', 'reward_id', 'ship_id');
}
Shipping model:
public function rewards(){
return $this->belongsToMany('App\Rewards','reward_ship', 'ship_id', 'reward_id');
}
After that you can call the relationships in the controller to eager load the wanted elements like this :
$project = Projects::with('rewards.shippings')
->where('id', $project_id)
->get();
And in the view you can loop over the rewards then get the shippings like this :
#foreach ($project->rewards as $reward)
<p>This is a reword {{ $reward->amount }}</p>
#foreach ($reward->shippings as $shipping)
<p>This is a shipping {{ $shipping->name }}</p>
#endforeach
#endforeach
class Project extends Model
{
public function rewds()
{
return $this->hasMany('App\Rewards');
}
public function shiplc()
{
return $this->hasManyThrough('App\Shipping', 'App\Rewards');
}
}
class Rewards extends Model
{
public function shiplc()
{
return $this->belongsToMany('App\Shipping');
}
public function projs()
{
return $this->belongsTo('App\Project');
}
}
class Shipping extends Model
{
public function shiplc()
{
return $this->belongsToMany('App\Shipping');
}
}
Route::get('projects/{id}', function($id) {
$p = Projects::with(['rewds', 'shiplc'])->find($id);
});
Project.php
class Project extends Model {
public function rewards() {
return this->hasMany(Reward::class, 'project_id', 'id');
}
}
Reward.php
class Reward extends Shipping {
public function shipping(){
return $this->belongsToMany(Shipping::class, 'reward_ship', 'reward_id', 'ship_id');
}
public function project(){
return $this->belongsTo(Project::class);
}
}
You can retrieve it like this:
$projectDetails = Project::where('id', $projectId)
->with(['rewards', 'rewards.shipping'])->get();
I'm developing a web API with Laravel 5.0 but I'm not sure about a specific query I'm trying to build.
My classes are as follows:
class Event extends Model {
protected $table = 'events';
public $timestamps = false;
public function participants()
{
return $this->hasMany('App\Participant', 'IDEvent', 'ID');
}
public function owner()
{
return $this->hasOne('App\User', 'ID', 'IDOwner');
}
}
and
class Participant extends Model {
protected $table = 'participants';
public $timestamps = false;
public function user()
{
return $this->belongTo('App\User', 'IDUser', 'ID');
}
public function event()
{
return $this->belongTo('App\Event', 'IDEvent', 'ID');
}
}
Now, I want to get all the events with a specific participant.
I tried with:
Event::with('participants')->where('IDUser', 1)->get();
but the where condition is applied on the Event and not on its Participants. The following gives me an exception:
Participant::where('IDUser', 1)->event()->get();
I know that I can write this:
$list = Participant::where('IDUser', 1)->get();
for($item in $list) {
$event = $item->event;
// ... other code ...
}
but it doesn't seem very efficient to send so many queries to the server.
What is the best way to perform a where through a model relationship using Laravel 5 and Eloquent?
The correct syntax to do this on your relations is:
Event::whereHas('participants', function ($query) {
return $query->where('IDUser', '=', 1);
})->get();
This will return Events where Participants have a user ID of 1. If the Participant doesn't have a user ID of 1, the Event will NOT be returned.
Read more at https://laravel.com/docs/5.8/eloquent-relationships#eager-loading
#Cermbo's answer is not related to this question. In that answer, Laravel will give you all Events if each Event has 'participants' with IdUser of 1.
But if you want to get all Events with all 'participants' provided that all 'participants' have a IdUser of 1, then you should do something like this :
Event::with(["participants" => function($q){
$q->where('participants.IdUser', '=', 1);
}])
N.B:
In where use your table name, not Model name.
for laravel 8.57+
Event::whereRelation('participants', 'IDUser', '=', 1)->get();
With multiple joins, use something like this code:
$userId = 44;
Event::with(["owner", "participants" => function($q) use($userId ){
$q->where('participants.IdUser', '=', 1);
//$q->where('some other field', $userId );
}])
Use this code:
return Deal::with(["redeem" => function($q){
$q->where('user_id', '=', 1);
}])->get();
for laravel 8 use this instead
Event::whereHas('participants', function ($query) {
$query->where('user_id', '=', 1);
})->get();
this will return events that only with partcipats with user id 1 with that event relastionship,
I created a custom query scope in BaseModel (my all models extends this class):
/**
* Add a relationship exists condition (BelongsTo).
*
* #param Builder $query
* #param string|Model $relation Relation string name or you can try pass directly model and method will try guess relationship
* #param mixed $modelOrKey
* #return Builder|static
*/
public function scopeWhereHasRelated(Builder $query, $relation, $modelOrKey = null)
{
if ($relation instanceof Model && $modelOrKey === null) {
$modelOrKey = $relation;
$relation = Str::camel(class_basename($relation));
}
return $query->whereHas($relation, static function (Builder $query) use ($modelOrKey) {
return $query->whereKey($modelOrKey instanceof Model ? $modelOrKey->getKey() : $modelOrKey);
});
}
You can use it in many contexts for example:
Event::whereHasRelated('participants', 1)->isNotEmpty(); // where has participant with id = 1
Furthermore, you can try to omit relationship name and pass just model:
$participant = Participant::find(1);
Event::whereHasRelated($participant)->first(); // guess relationship based on class name and get id from model instance
[OOT]
A bit OOT, but this question is the most closest topic with my question.
Here is an example if you want to show Event where ALL participant meet certain requirement. Let's say, event where ALL the participant has fully paid. So, it WILL NOT return events which having one or more participants that haven't fully paid .
Simply use the whereDoesntHave of the others 2 statuses.
Let's say the statuses are haven't paid at all [eq:1], paid some of it [eq:2], and fully paid [eq:3]
Event::whereDoesntHave('participants', function ($query) {
return $query->whereRaw('payment = 1 or payment = 2');
})->get();
Tested on Laravel 5.8 - 7.x