In Laravel, mocking an eloquent query on a model - php

I'm stuck on Laravel 4.2 for now (with phpunit and mockery) but the same should apply to later versions.
I have a Repository for my FxRate model. It has a method to get an FX rate vs GBP which contains this eloquent call:
$query = \FxRate::where('currency', $currency )
->where('fx_date', $fxDate->format('Y-m-d') )
->first();
return $query->rate_to_gbp;
In my unit test I'd like to mock this call so I can define the query result that would be returned by this call rather than relying on the database to have a value within it.
My attempt goes something like this:
$mocked_query_result = (object) ['rate_to_gbp' => 1.5];
FxRate::shouldReceive('where')
->once()
->andReturn($mocked_query_result);
But I'm fairly sure this won't work as the initial static call to FxRate should return some query object that accepts a further where() call and a first().
Is there a clean way of mocking this?

You should pass an instance of your model into the repository in the constructor:
public function __construct(FXRate $model)
{
$this->model = $model;
}
Then your query becomes:
$query = $this->model->where('currency', $currency)...etc
Then you pass a mocked model to the repo when you instantiate it:
$mockModel = Mockery::mock('FXRate');
// This could be better, and you should use correct with() calls but hey, it's only an example
$mockModel->shouldReceive('where')
->twice()
->andReturn($mockModel);
$mockModel->shouldReceive('first')
->once()
->andReturn($mocked_query_result);
$repo = new Repo($mockModel)
$this->assertEquals($mocked_query_result, $repo->testableMethod());
Further edit following comments. You can return a mock of any model but I find mocking the real model helps with readability:
$mockFXRate = Mockery::mock('FXRate');
$mockFXRate->shouldReceive('where')
->once()
->andReturn($mockFXRate);
$mockFXRate->shouldReceive('first')
->once()
->andReturn($mocked_query_result);
FXRate::shouldReceive('where')
->andReturn($mockFXRate);

Related

Laravel Eloquent - Relationships and methods. When to use ->users() vs ->users [duplicate]

There is some basic understanding/theory here that I am missing.I don't understand the difference between these function calls:
$distributors = $store->distributors();
$distributors = $store->distributors;
$distributors = $store->distributors()->get();
$distributors = $store->distributors->get();
What I am trying to accomplis here is to get a list of the distributors for a store (a many to many relationship), and they get each distributors list of beers into one giant list.
foreach ($distributors as $distributor)
{
$available_beers = array_merge($distributor->beers(), $available_beers);
}
I don't know if that is the best way to do this and I can't get it to work. Similar to the first list of methods, I don't know if I need ->$beers or ->$beers()
Update
Thanks to everyone who answered! This will be a good reference for me going forward. My biggest lesson was the difference between getting a collection back, vs getting the query builder/relationship object back. For future reference to those who find this question, here is what I set up in my controller:
$store = $this->store->find($id)->first();
$distributors = $store->distributors;
$beers = [];
foreach ($distributors as $distributor){
$beers = array_merge($distributor->beers->lists('name', 'id'), $beers);
}
Short answer
$model->relation() returns the relationship object
$model->relation returns the result of the relationship
Long answer
$model->relation() can be explained pretty simple. You're calling the actual function you defined your relation with. Yours for distributor probably looks somewhat like this:
public function distributors(){
return $this->hasMany('Distributor');
}
So when calling $store->distributors() you just get the return value of $this->hasMany('Distributor') which is an instance of Illuminate\Database\Eloquent\Relations\HasMany
When do you use it?
You usually would call the relationship function if you want to further specify the query before you run it. For example add a where statement:
$distributors = $store->distributors()->where('priority', '>', 4)->get();
Of course you can also just do this: $store->distributors()->get() but that has the same result as $store->distributors.
Which brings me to the explanation of the dynamic relationship property.
Laravel does some things under the hood to allow you to directly access the results of a relationship as property. Like: $model->relation.
Here's what happens in Illuminate\Database\Eloquent\Model
1) The properties don't actually exist. So if you access $store->distributors the call will be proxied to __get()
2) This method then calls getAttribute with the property name getAttribute('distributors')
public function __get($key)
{
return $this->getAttribute($key);
}
3) In getAttribute it checks if the relationship is already loaded (exists in relations). If not and if a relationship method exists it will load the relation (getRelationshipFromMethod)
public function getAttribute($key)
{
// code omitted for brevity
if (array_key_exists($key, $this->relations))
{
return $this->relations[$key];
}
$camelKey = camel_case($key);
if (method_exists($this, $camelKey))
{
return $this->getRelationshipFromMethod($key, $camelKey);
}
}
4) In the end Laravel calls getResults() on the relation which then results in a get() on the query builder instance. (And that gives the same result as $model->relation()->get().
The direct answer to your question:
$store->distributors() will return the actual relationship object (\Illuminate\Database\Eloquent\Relations\BelongsToMany).
$store->distributors will be a collection containing the results of the relationship query (\Illuminate\Database\Eloquent\Collection).
$store->distributors()->get() will be a collection containing the results of the relationship query (\Illuminate\Database\Eloquent\Collection).
$store->distributors->get() should return an error since you're calling get() on a Collection object and the first parameter is not optional. If not an error, it should at least return null.
More information:
Given the following model:
class Store extends Eloquent {
public function distributors() {
return $this->belongsToMany('Distributor');
}
}
Calling the relationship method ($store->distributors()) will return to you the relationship (\Illuminate\Database\Eloquent\Relations\BelongsToMany) object. This is basically a query object which you can continue to modify, but you still need to call some type of method to get the results (e.g. get(), first(), etc).
However, accessing the relationship attribute ($store->distributors) will return to you a collection (\Illuminate\Database\Eloquent\Collection) object containing the results from executing the relationship query.
By default, the relationship attribute is created and assigned a value the first time it is accessed (known as "lazy loading"). So, the first time you access $store->distributors, behind the scenes it is executing the relationship query, storing the results in the $store->distributors attribute, and then returning those results. However, it only does this once. The next time you access $store->distributors, the attribute already contains the data, so that is what you are accessing.
To illustrate this:
// the following two statements will run the query twice
$r1 = $store->distributors()->get();
$r2 = $store->distributors()->get();
// the following two statements will run the query once.
// the first statement runs the query, populates $store->distributors, and assigns the variable
// the second statement just accesses the data now stored in $store->distributors
$r3 = $store->distributors;
$r4 = $store->distributors;
// at the end, $r1 == $r2 == $r3 == $r4
Relationships can also be "eager" loaded, using the with() method on the query. This is done to alleviate all of the extra queries that may be needed for lazy loading (known as the n+1 problem). You can read more about that here.
When you work with relationships with Eloquent the property is a collection (Illuminate\Database\Eloquent\Collection) of your relation white the method is a start of a new query.
Say your model looks like this:
class User extends Eloquent {
public function roles()
{
return $this->belongsToMany('Role');
}
}
If you try to access $user->roles, Eloquent will run the query and fetch all roles related to that user thanks to magic methods and returns an instance of Illuminate\Database\Eloquent\Collection. That class has a method called get, that's why $user->roles->get() works for you.
If you try to access the method, $user->roles(), you will instead get a query builder object so you can fine tune your query.
$user->roles()->whereIn('role_id', [1, 3, 4])->get();
That would only return roles where role_id is 1, 3 or 4.
So, the property returns a complete query and it results (Illuminate\Database\Eloquent\Collection) while the method lets you customize your query.
$distributors = $store->distributors();
Result of a method (function)
$distributors = $store->distributors;
Value of property (variable)
$distributors = $store->distributors()->get();
Take the first one, where it's the result of a method, if the method returns an object, this is a method in that object that was returned.
$distributors = $store->distributors->get();
If the property is an object, then it's calling a method in that property that's an object.
Re ->$beers vs ->$beers() that's a dynamic name of a property/method depending on what you're for. Just make a really rough guess at what you're doing, in your class you're going to have
$this->beers = array('bud','miller','sam');
and in your code using the $store object, you're actually going to go something like
$drink_type = 'beers';
$drink_list = $store->$drink_type;
And that will return $this->beers from $store, the same as writing $store->beers;
Imagine that the store class looks like this:
<?php
class Store {
public $distributors;
function __construct($distributors = array()) {
$this->distributors = $distributors;
}
public function distributors() {
return $this->distributors;
}
}
So the difference is:
$store = new Store(array('some guy', 'some other guy'));
$guys = $store->distributors; # accesing the $distributors property
$more = $store->distributors(); # calling the distributors() method.
The main difference is:
$distributors = $store->distributors() return instance of the relationship object like Illuminate\Database\Eloquent\Relations\BelongsToMany. You can use other conditions such as where after call this.
$store->distributors return instance of the collection Illuminate/Database/Eloquent/Collection. Laravel call the magic method __get under the hood. It will return a result of query relationship.
Maybe this will be usefull.
Access to method:
$object->method();
Access to property:
$object->property;

Laravel 7 partial mock eloquent model fails with __construct(), but no expectations were specified

I'm using Laravel 7 and trying to partial mock an eloquent model..
My controller has eloquent model dependency injected:
use App\Models\Ticket;
class TicketsController extends Controller
{
/** #var Ticket */
private $tickets;
public function __construct(Ticket $ticket) {
$this->tickets = $ticket;
}
public function get() {
$tickets = $this->tickets::whereNull('staff_id')
->where('status', '!=', $this->tickets::STATUS_CLOSED)
->orderBy('created_at', 'desc')
->get();
}
}
Unit test:
use App\Models\Ticket;
$this->partialMock(Ticket::class, function($mock) {
$mock->shouldReceive('get')
->once()
->andReturn([]);
});
$response = $this->json('get', route('tickets'));
$response->assertOk();
Fails with:
testing.ERROR: Received Mockery_0_App_Models_Ticket::__construct(), but no expectations were specified {"exception":"[object] (Mockery\Exception\BadMethodCallException(code: 0): Received Mockery_0_App_Models_Ticket::__construct(), but no expectations were specified at /opt/project/vendor/mockery/mockery/library/Mockery/Loader/EvalLoader.php(34) : eval()'d code:911)
Also tried to change test for this:
$ticketsMock = Mockery::mock(Ticket::class);
$ticketsMock
->makePartial()
->shouldReceive('get')
->once()
->andReturn([]);
$this->instance(Ticket::class, $ticketsMock);
But getting exactly same error..
If I replace mock for non partial one and fake all method calls on the model it works fine.. but some eloquent queries will be pretty long and I'm trying to have it done via partial mock so I don't have to fake every single chained call like ->shouldReceive('something')->andReturnSelf()
The problem with partial, is that whereNull returns a QueryBuilder and not a Ticket model.
What i think is a good solution is mocking demeter chains. You can mock fluent calls, by doing expression it in a string whereNull->where->orderBy->get. I got the following example to work locally.
$this->mock(Ticket::class, function($mock) {
$mock->shouldReceive('whereNull->where->orderBy->get')
->once()
->andReturn([]);
});
You still have to express the path of the calls to mock, but i think it is very reasonable and straight forward since you do not have to think of parameters and returns.
I changed the controller method a little since you called method on objects statically. To ensure it works, my controller logic was this.
$tickets = $this->tickets->whereNull('staff_id')
->where('status', '!=', Ticket::STATUS_CLOSED)
->orderBy('created_at', 'desc')
->get();

Laravel methods

It's more a "global understanding" question.
To save a model instance in the Database, we can use both:
SAVE()
$model = new Model;
$model->attribute = value;
$model->save();
https://laravel.com/docs/5.4/eloquent#inserts
and
::CREATE()
App\Model::create(['attribute'=>'value']);
https://laravel.com/docs/5.4/eloquent#mass-assignment
I supposed both of these methods belong to Illuminate\Database\Eloquent\Model, but I have found only function save there:
public function save(array $options = [])
{
$query = $this->newQueryWithoutScopes();
//......
return $saved;
}
But I haven't found any function Create in that file.
My QUESTIONS are:
1) what is the fundamental difference between
->method()
and
::method()
(is the last one a query builder?)
2) where can I find "::create()" method declared?
Thank you very much!
::method() is static calling without the need of creating an object of the class beforehand. ->method() you have to create an object before.
$car = new Car();
$car->color = 'red';
$car->save();
vs.
$car = Car::create(['color' => 'red']);
The create method can be found:
\Illuminate\Database\Eloquent\Builder::create
1)
->mehtod() is calling a Non-Static or Instantiated object method. Where as ::method() is calling on a static public method of a class.
To help describe this in your context. Take a look at how ::create() Operates. It returns an object that you can now use the save() method on after making changes. In the inverse, you cannot 'create' a model object from the save() method. You must have a model object first before executing ->save(). Which where ::create() comes in.
Eloquent ORM - Laravel : Insert, Update, Delete
2)
the create method is declared, I believe, in a higher level.

How to access Doctrine defined methods after custom Repository is made?

I am developing a site using ZF2 and Doctrine. The problem I am facing is I am using Doctrine predefined object methods like findAll(), findOneBy(), findBy() etc in my code. For some custom actions I have prepared a custom Repository for one of my entities. Now I can't access the predefined methods. I have already written code by using findAll() method. But after building a repository I can't simply access findAll() method. How can I both access my custom defined methods along with Doctrine defined methods?
For example:
I am using findOneBy() like this:
$udata = $this->em()->getRepository('Application\Entity\Usermain')->findOneBy(array('userEmail' => 'subh.laha#gmail.com'));
Now I have prepared UsermainRepository like below:
namespace Application\Entity\Repositories;
use Doctrine\ORM\EntityRepository;
use Doctrine\Common\Persistence\ObjectRepository;
class UsermainRepository extends EntityRepository
{
protected $sl;
public function __construct($sl){
$this->sl = $sl;
}
public function customFind($arr)
{
$qb = $this->sl->createQueryBuilder();
$whereStr = '';
if(count($arr)){
foreach($arr as $kvarr=>$varr){
$whereStr .= "u.$kvarr = '".$varr."'";
}
}
$qry = $qb->select('u')
->from('Application\Entity\Usermain','u')
->where($whereStr)
->getQuery()
->getResult();
return $qry;
}
}
Now I can access
$udata = $this->em()->getRepository('Application\Entity\Usermain')->customFind(array('userEmail' => 'subh.laha#gmail.com'));
But Not
$udata = $this->em()->getRepository('Application\Entity\Usermain')->findOneBy(array('userEmail' => 'subh.laha#gmail.com'));
Why? I have already written code by using doctrine defined methods. What can I do now?
I believe you are getting this error because you have overridden the repository's constructor method but aren't calling the parent constructor so required parameters aren't being properly set.
I think your code is not correct, You can use the below query instead of writing complex custom object.
$query = $this->getEntityManager()->createQueryBuilder()
->select('U.id,U.name')
->from('Application\Entity\StudentClass', 'U')
->where('U.pkStudentClass = :pkStudentClass')
->setParameter('pkStudentClass', 1)
->setMaxResults(20);
->orderBy('id', 'DESC')
->getQuery();
$result = $query->getScalarResult();
There are several problems exist in your approach;
I think trying to access the service locator's itself in an entity repository is bad idea. You shouldn't need service container in repository level.
The second detail is, when extending any class, you need to check out, read and respect the signature of the parent. In your case, you're overriding the parent's __construct. Calling parent::__construct() may seems like a solution but it's not. You'll soon realize that you also need a custom repository factory to pass additional arguments to constructor while keeping the current functionality. No way.
This is more important than others: you believe that $this->sl->createQueryBuilder() returns query builder instance. Theoretically seems like working but $this->sl is not service locator, service locator doesn't knows anything about query builders, it's just EntityManager instance which passed to your constructor.
Try this:
<?php
namespace Application\Entity\Repositories;
use Doctrine\ORM\EntityRepository;
class UsermainRepository extends EntityRepository
{
public function customFind($arr)
{
// Just pass an alias for your entity
$qb = $this->createQueryBuilder('u');
$whereStr = '';
if (count($arr)) {
foreach ($arr as $kvarr => $varr) {
$whereStr .= "u.$kvarr = '".$varr."'";
}
}
return $qb->where($whereStr)
->getQuery()
->getResult();
}
}
Finally, in your Application\Entity\Usermain entity, you'll also need telling about your custom repository to doctrine since you don't want to use default EntityRepository :
namespace Application\Entity;
/**
* #ORM\Entity(repositoryClass="Application\Entity\Repositories\UsermainRepository")
*/
class Usermain
{
}
Now in your controller (or service) level, you can test:
$em = $this->getServiceLocator()->get('doctrine.entitymanager.orm_default');
$repo = $em->getRepository('Application\Entity\Usermain');
// repo is a UsermainRepository instance
// this should work:
$udata = $repo->customFind(array('userEmail' => 'subh.laha#gmail.com'));
// this should also work
$udata = $repo->findOneBy(array('userEmail' => 'subh.laha#gmail.com'));
I strongly recommend carefully reading of Working With Objects section of the documentation before diving into deeps.

Laravel Custom Model Methods

Whenever I add additional logic to Eloquent models, I end up having to make it a static method (i.e. less than ideal) in order to call it from the model's facade. I've tried searching a lot on how to do this the proper way and pretty much all results talk about creating methods that return portions of a Query Builder interface. I'm trying to figure out how to add methods that can return anything and be called using the model's facade.
For example, lets say I have a model called Car and want to get them all:
$cars = Car::all();
Great, except for now, let's say I want to sort the result into a multidimensional array by make so my result may look like this:
$cars = array(
'Ford' => array(
'F-150' => '...',
'Escape' => '...',
),
'Honda' => array(
'Accord' => '...',
'Civic' => '...',
),
);
Taking that theoretical example, I am tempted to create a method that can be called like:
$cars = Car::getAllSortedByMake();
For a moment, lets forget the terrible method name and the fact that it is tightly coupled to the data structure. If I make a method like this in the model:
public function getAllSortedByMake()
{
// Process and return resulting array
return array('...');
}
And finally call it in my controller, I will get this Exception thrown:
Non-static method Car::getAllSortedByMake() should not be called statically, assuming $this from incompatible context
TL;DR: How can I add custom functionality that makes sense to be in the model without making it a static method and call it using the model's facade?
Edit:
This is a theoretical example. Perhaps a rephrase of the question would make more sense. Why are certain non-static methods such as all() or which() available on the facade of an Eloquent model, but not additional methods added into the model? This means that the __call magic method is being used, but how can I make it recognize my own functions in the model?
Probably a better example over the "sorting" is if I needed to run an calculation or algorithm on a piece of data:
$validSPG = Chemical::isValidSpecificGravity(-1.43);
To me, it makes sense for something like that to be in the model as it is domain specific.
My question is at more of a fundamental level such as why is all()
accessible via the facade?
If you look at the Laravel Core - all() is actually a static function
public static function all($columns = array('*'))
You have two options:
public static function getAllSortedByMake()
{
return Car::where('....')->get();
}
or
public function scopeGetAllSortedByMake($query)
{
return $query->where('...')->get();
}
Both will allow you to do
Car::getAllSortedByMake();
Actually you can extend Eloquent Builder and put custom methods there.
Steps to extend builder :
1.Create custom builder
<?php
namespace App;
class CustomBuilder extends \Illuminate\Database\Eloquent\Builder
{
public function test()
{
$this->where(['id' => 1]);
return $this;
}
}
2.Add this method to your base model :
public function newEloquentBuilder($query)
{
return new CustomBuilder($query);
}
3.Run query with methods inside your custom builder :
User::where('first_name', 'like', 'a')
->test()
->get();
for above code generated mysql query will be :
select * from `users` where `first_name` like ? and (`id` = ?) and `users`.`deleted_at` is null
PS:
First Laurence example is code more suitable for you repository not for model, but also you can't pipe more methods with this approach :
public static function getAllSortedByMake()
{
return Car::where('....')->get();
}
Second Laurence example is event worst.
public function scopeGetAllSortedByMake($query)
{
return $query->where('...')->get();
}
Many people suggest using scopes for extend laravel builder but that is actually bad solution because scopes are isolated by eloquent builder and you won't get the same query with same commands inside vs outside scope. I proposed PR for change whether scopes should be isolated but Taylor ignored me.
More explanation :
For example if you have scopes like this one :
public function scopeWhereTest($builder, $column, $operator = null, $value = null, $boolean = 'and')
{
$builder->where($column, $operator, $value, $boolean);
}
and two eloquent queries :
User::where(function($query){
$query->where('first_name', 'like', 'a');
$query->where('first_name', 'like', 'b');
})->get();
vs
User::where(function($query){
$query->where('first_name', 'like', 'a');
$query->whereTest('first_name', 'like', 'b');
})->get();
Generated queries would be :
select * from `users` where (`first_name` like ? and `first_name` like ?) and `users`.`deleted_at` is null
vs
select * from `users` where (`first_name` like ? and (`id` = ?)) and `users`.`deleted_at` is null
on first sight queries look the same but there are not. For this simple query maybe it does not matter but for complicated queries it does, so please don't use scopes for extending builder :)
for better dynamic code, rather than using Model class name "Car",
just use "static" or "self"
public static function getAllSortedByMake()
{
//to return "Illuminate\Database\Query\Builder" class object you can add another where as you want
return static::where('...');
//or return already as collection object
return static::where('...')->get();
}
Laravel model custom methods -> best way is using traits
Step #1: Create a trait
Step #2: Add the trait to model
Step #3: Use the method
User::first()->confirmEmailNow()
app/Model/User.php
use App\Traits\EmailConfirmation;
class User extends Authenticatable
{
use EmailConfirmation;
//...
}
app/Traits/EmailConfirmation.php
<?php
namespace App\Traits;
trait EmailConfirmation
{
/**
* Set email_verified_at to now and save.
*
*/
public function confirmEmailNow()
{
$this->email_verified_at = now();
$this->save();
return $this;
}
}

Categories