Testing a trait function with an Eloquent relationship using anonymous classes - php

I'm trying to test a trait which is used in a few Eloquent models in my application. The testing approach I'm taking is making an anonymous class in my test class and testing that the trait method invoked on the anonymous class returns the correct results. I'm running into issues due to relationships being called in the trait method with which the anonymous class approach seems to be struggling.
I've created an anonymous class in my test class which extends the Eloquent model and uses my trait I'm trying to test. I've also specified the relationship in the anon class which is then used in the trait method I'm trying to test. However, since the relationship is many to many, when I try and call the relationship in the test, Eloquent is looking in a join table ai2nx3xla_emails (ai2nx3xla being the hidden name of the anon class) which of course doesn't exist in my database.
Relevant parts of the test class
public function setUp()
{
parent::setUp();
$this->testClass = new class extends Model {
use Contactable;
public function emails()
{
return $this->belongsToMany('App\Email')->withPivot('default');
}
};
}
/** #test */
public function it_can_return_the_default_email_for_a_model()
{
$email = factory(\App\Email::class)->make();
$this->testClass->setRelation('emails', $email);
$this->assertEquals($email->id, $this->testClass->defaultEmail());
}
Relevant parts of the trait (boiled down for simplicity)
abstract public function emails();
/**
* Return the default email if exists
*
*/
public function email()
{
$email = $this->emails()->wherePivot('default', true)->first() ?? $this->emails()->first();
return $email;
}
I'm expecting the test to pass but I'm actually getting this error message:
PDOException: SQLSTATE[42000]: Syntax error or access violation:
1064 You have an error in your SQL syntax; check the manual that >corresponds to your MySQL server version for the right syntax to use >near '.php0x10c63a39e_id as pivot_contactable_test.php0x10c63a39e_
id, `contactable' at line 1
My question would be, is there a way I can make this work in the current setup? Or should I approach this test in a different way? eg. testing a real model which uses the trait? Reluctant to do that though since if I remove the trait from that model the tests will fail. Any help would be greatly appreciated, thanks!

It's because queries done by Laravel are based on the classname (classnametolower_id) to get the foreign key but you can provide the table name as well i think this will resolve your problem :
new class extends Model {
use Contactable;
protected $table = 'yourtable';
public function emails()
{
return $this->belongsToMany('App\Email')->withPivot('default');
}
};

Related

Resolve Laravel Auth Authenticatable to User model to address static analysis issues

We have a Laravel 8 application.
We're using the standard Laravel Auth facade to retrieve the authenticated user.
Our User model has a few custom functions, the most important of which is a shorthand function, hasPermissionTo(). (The reason why is because we have a very custom RBAC setup.)
So in a lot of our controllers, we have something like this...
use Illuminate\Routing\Controller as BaseController;
class ExampleController extends BaseController
{
public function index()
{
if (\Auth::user()->hasPermissionTo('Management:View Users')) {
// do something.
}
// etc.
}
}
That's all well and good until we start running static analysis. We're using Larastan, which is giving me these errors:
------ -------------------------------------------------------------------------------------------
Line Http/Controllers/ExampleController.php
------ -------------------------------------------------------------------------------------------
48 Call to an undefined method Illuminate\Contracts\Auth\Authenticatable::hasPermissionTo().
This also makes sense because the Auth facade proxies Illuminate\Auth\AuthManager and Auth::user(), via __call() magic, normally resolves to Illuminate\Auth\SessionGuard::user() and that typehints this...
/**
* Get the currently authenticated user.
*
* #return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function user()
{
...
So finally, my question:
Where is the failure here? Do I need to a) configure my static analysis tool better, b) configure Laravel better to more accurately return a specific type, or c) do I need to add explicit if (Auth::user() instanceof User) { ... } clauses all throughout my code?
Is there a correct way to override one of the Laravel stock classes with a more specific one of my own to address more specific functionality? Is there way to type-hint the actual authenticated User into the function declaration so I can declare function index(User $authenticatedUser) and have Laravel autopopulate this in with a more specific type hint?
I understand that I could just add an exclusion for this particular issue in Larastan and move on with my life, but the error is designed to protect against a specific class of error--i.e. if I added Auth0 and replaced App\Model\User with Auth0\Login\User, then I would have an Authenticatable class that fails to run hasPermissionTo(), and I'd have to now fix a bunch of code.
Eventually, this is how we worked around the problem. We added a type-hint for Larastan, so it can infer that $user has this HasRolesContract trait which provides hasPermissionTo().
public function index()
{
/** #var \App\Traits\HasRolesContract */
$user = \Auth::user();
if ($user->hasPermissionTo('Management:View Users')) {
Hopefully this helps someone else!
(Thanks for the nudge, #djjavo)

Laravel mocking model with persistence

I'm testing a job that receives a customer model. In the job I create the
customer in shopify and store that id on the customer model. Then I call a sendShopifyInvite that needs to be mocked (I don't want to send an email in my test).
My test looks like this:
/** #test */
public function a_shopify_customer_is_created_if_it_does_not_yet_exists()
{
$this->partialMock(User::class, function ($mock) {
$mock->shouldReceive('sendShopifyInvite')->once()->andReturn(new User());
});
$customer = app(User::class)->fill(
factory(User::class)->create([
'shopify_id' => null
])->toArray()
);
$this->assertNull($customer->shopify_id);
CreateCustomerJob::dispatchNow($customer);
$customer->refresh();
$this->assertNotNull($customer->shopify_id);
}
The problem is that I receive this error:
PDOException: SQLSTATE[42S02]: Base table or view not found: 1146 Table 'jensssen_db.mockery_0__domain__customer__models__users' doesn't exist
Is it not possible to persist data in a mock object? Are there any other ways?
Your problem is due to this line. Since a partial mocks creates a new mock object and calls your original model through it, it will take that class basename.
return $this->table ?? Str::snake(Str::pluralStudly(class_basename($this)));
I can see two solutions, i don't feel like anyone is the perfect solutions, it will solve your problem. Firstly set your table hardcoded on the User.php model. This will avoid the class basename being called.
class User {
$protected table = 'users';
}
Another approach, i have been forced to make before (when you have a hard time mocking some classes). Is instead of mocking your User.php model, simply put the same logic in a service / proxy class and mock that instead.
class ShopifyService {
public function sendInvite(User $user)
{
...
}
}
In your User.php model now have.
public function sendShopifyInvite() {
resolve(ShopifyService::class)->sendInvite($this);
}
Now you are able to mock only the Shopify service and now not tinker with the inner workings of the Eloquent Model.
$this->partialMock(ShopifyService::class, function ($mock) {
$user = new User();
$mock->shouldReceive('sendInvite')->with($user)->once()->andReturn($user);
});

In PHP/Laravel, what is the proper term for what "Model::class" is, given `return $this->belongsTo(Model::class);`

In this example code here:
public function user()
{
return $this->belongsTo(User::class);
}
public function sandwich()
{
return $this->belongsTo(Sandwich::class);
}
I want to know what User::class is called, because you could also write that above example like this:
public function user()
{
return $this->belongsTo(\App\User);
}
public function sandwich()
{
return $this->belongsTo(\App\Sandwich);
}
So I like that Laravel "just knows" where the model is when you use that syntax sugar, but what is it called? I'd like to read some documentation about it so I better understand what's happening behind the scenes.
It reminds me in some ways of "route model binding", so the answer that I would like is a link to the relevant docs page somewhere, or a term I can Google to understand what exactly is going on there.
The ::class syntax is class name resolution. This syntax returns the string representation of the fully qualified class name, including any namespace information otherwise omitted.
A couple of the benefits of this feature are 1. not needing to explicitly specify the fully qualified namespace of the class, and 2. being able to pass around the fully qualified namespace as a string while allowing an IDE to locate the class name when refactoring or searching for references.
I want to know what User::class is called
A wholesome name to call this is class name resolution. It combines both the PHP resolution operator :: and the class keyword. lt is not a Laravel syntax sugar but a PHP one.
It, in all situations, returns the Fully qualified name of the class which is simply a string containing the absolute/relative path of the class file - depending on the namespace of the file/class where it is used.
From the PHP Manual
... use ClassName::class to get a fully qualified name of class ClassName
On the other hand, from the Laravel use-case you mentioned
public function user()
{
return $this->belongsTo(User::class);
}
The Laravel Eloquent method belongsTo() and all similar methods specify that the parameter to be passed is a string. These methods resolve the string parameter to locate the model's class definition.
From the Laravel Docs
The first argument passed to the hasOne method is the name of the related model.
Therefore using
return $this->belongsTo('\App\User');
OR
return $this->belongsTo(User::class);
are syntactically equivalent. That means that the method definition are exactly the same and there is no parameter type checking as both parameters are string.
So I like that Laravel "just knows" where the model is when you use that syntax sugar.
Yeah, it just knows. But it is really straight forward. It uses the string parameter of the Eloquent method (now we know that regardless of the syntax, it is a String) and the provided Namespace of the current class to locate the model definition.
For instance this class definition
<?php
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Get the phone record associated with the user.
*/
public function phone()
{
return $this->hasOne('App\Phone');
}
}
is equivalent to
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Get the phone record associated with the user.
*/
public function phone()
{
return $this->hasOne(Phone::class);
}
}
and also equivalent to
<?php
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Get the phone record associated with the user.
*/
public function phone()
{
return $this->hasOne(App\Phone::class);
}
}
You will notice that the first and third examples do not need the namespace directive because they are using the Absolute path names.
Actually Laravel is not aware where the model is
If this example works:
public function user()
{
return $this->belongsTo(User::class);
}
It's becase probably the models lives in the same folder or Namespace if not you probably should to import the required model from other namespace like this.
//At the top of the file you will import the class
use Maybe\Another\Folder\Namespace\OtherObject;
public function user()
{
return $this->belongsTo(OtherObject::class);
}
if you don't want to "import" the object you should use the complete path to the class like this.
public function user()
{
return $this->belongsTo(App\OtherFolder\OtherObject::class);
}
In summary laravel doesn't know where to look for class definitions, but if you pass an instance in a param that would be resolved for the Service Container but this is another topic more related to the model binding
public function method(MyObject $instance)
{
//Laravel will try to automatically generate an instance of $instance
}

PHPUnit - implementing abstract method in child

I have a set of read-only tests and a couple of ones that modify data (insert/update/delete). I'd like to back up my tables, so each test class would have a list of associated tables that they'll modify. This is just test data.
I thus thought of this:
abstract class DataAlteringTestBase extends PHPUnit_Framework_TestCase
{
protected function setUp()
{
echo "backing up tables: " . $this->GetAlteredTableNames();
}
public abstract function GetAlteredTaleNames();
}
One of the subclasses:
class DataAlteringTest extends DataAlteringTestBase
{
function GetAlteredTaleNames()
{
return array("some_table");
}
public function testDummyStuffChild()
{
$this->assertTrue(true);
}
}
The problem is, I think, that PHPUnit tries to get the method implementation from the abstract class, rather than its children.
Call to undefined method DataAlteringTest::GetAlteredTableNames() -
the implementation ...\tests\DataAlteringTestBase.php:6 - the abstract
class
How to fix it? or is there something wrong with the idea of implementing this in PHP/PHPUnit in the first place?
You have a few typos - you wrote GetAlteredTaleNames() in some places and GetAlteredTableNames() in other places.

Laravel4: How can I use Eloquent/Model

I've read and followed documentation found here http://laravel.com/docs/eloquent
and I tried some of the examples i found here
here is the code that I have so far
model
dbeloquent.php
<?php
class dbeloquent extends Eloquent {
protected $table = "users";
public function showTbl()
{
dd(dbeloquent::$table);
}
}
//end of model
?>
route.php
<?php
Route::get('/', function () {
$model = new dbeloquent();
dd($model->someFunction());
});
?>
I want to show my tables first but here is what I'm having
Access to undeclared static property: dbeloquent::$table
somebody help me please
Your dbeloquent class is extending Model class in the background. Eloquent alias is pointing to Model class, in the app/config/app.php file
'Eloquent' => 'Illuminate\Database\Eloquent\Model'
protected $table property is extended from the abstract Model class, and it is not static, so you can't redeclare it (static or nostatic) .The way you can access property from base model is by using:
__get($key) method
But the problem is in which point of execution your $table property is visible, since it is protected and modified at run time.
At the end, it is not declared and defined to be used in such a way - Laravel is internally looking for that property. Try to trace calls, and you will probably find what is happening inside.
Don't complicate things more then they should be complicated.

Categories