Using the Scope Resolution Operator in Laravel - php

I'm confused about an aspect of OOP/Laravel.
I'm following an OOP tutorial (nothing to do with Laravel) that states that you can only use the Scope Resolution Operator when the method you are calling is static.
So I created a quick script;
class Student {
function welcome_students($var="Hello") {
echo "{$var} students.";
}
}
echo Student::welcome_students("Greetings") ."<br />";
And I get the error;
Strict Standards: Non-static method Student::welcome_students() should
not be called statically in /static_modifier.php on line 11 Greetings students.
But in Laravel 5, I've noticed that I've been using calls like
`ClassName::whereIn($var = `
in quite a few of my Controllers. I've checked the package where the whereIn method is stored and it's not static. It's just a public function.
So how is Laravel 5 allowing me to get away with it? I'm in development mode so I don't know why I'm not seeing that same message.

There are some fairly advanced concepts here that need to be understood in order to fully grasp how this is happening.
First, this would only work for the facades in Laravel. These can be found in the app.php config file in the aliases array. Each of these facades can be thought of as entry points for their real classes which are in the Laravel core. So even though the syntax is telling you that you are calling static methods, what's really happening is Laravel is resolving the underlying classes to these proxy classes and calling methods non-statically on those.
You can see this better if you go to some of those facade classes where you will see the methods you are calling are not actually present on those classes.
To really understand how this is happening, read up on Laravel's inversion of control container (IoC), its use of Facades, and the php magic method __callStatic, and the php method class_alias which is what Laravel is using to setup the aliases.
Again, these are fairly complex concepts so don't get discouraged if they seem confusing or the purpose eludes you.
Basically, the workflow looks like this...
You call Config::get()
Laravel looks up the alias for Config which is a facade.
Using the __callStatic magic method, the facade figures out the underlying class to instantiate and calls the appropriate method on that non-statically.
http://laravel.com/docs/5.0/facades#explanation
http://laravel.com/docs/4.2/ioc#introduction
http://php.net/manual/en/language.oop5.overloading.php#object.callstatic

First of all, your function is not static, you need change it if you want to call it like Student::welcome_students()
public static function welcome_students($var="Hello") {
echo "{$var} students.";
}

Related

PHP-DI Potentially polymorphic call when using the set method

The issue
I have an unexpected warning from PHPStorm when I try to set a new value in a PHP-DI container.
Given the following code:
function inject(Psr\Container\ContainerInterface $container){
$container->set(RandomClass::class, new RandomClass());
}
$container = new DI\Container(); class is instantiated
inject($container);
The following warning is triggered
Potentially polymorphic call. does not have members in its hierarchy
I understand what the warning means, but I do not see why it pops up, especially since I have not found any occurrences of this situation while looking on Google and SO and the documentation does not mention it.
Is there something I am missing, or is this a "false positive" ?
The set() method is not part of Psr\Container\ContainerInterface.
If you want to use that method, you can't typehint against the interface because your code explicitly needs a PHP-DI instance.
Your code doesn't have to be generic, don't overthink things too much. The PSR is useful mostly for frameworks and libraries (who need to be compatible with multiple containers), not for end-users.
The day you switch container library you will have many more complex things to do than just replacing the set() call.
The reason behind the issue
Given the following code (which is very similar to the one I use)
function inject(Psr\Container\ContainerInterface $container){
$container->set(RandomClass::class, new RandomClass());
}
$container = new DI\Container(); class is instantiated
inject($container);
The $container->set(...) call is going to trigger the following warning
Potentially polymorphic call. does not have members in its hierarchy
This is to be expected as Psr\Container\ContainerInterface only contains definitions for the following methods
get($id)
has($id)
The solution
Two possible solutions for this issue:
Type the methods directly with the container, making sure to not use the FQN of the class but only use Container and "use the namespace", it will make changing to a new container package easier (because this is still the goal behind PSRs, being able to almost hot-swap packages).
Create a custom interface based on Psr\Container\ContainerInterface and add the required methods to it.
Or, eventually, you can try to make PHP-FIG extend the PSR-11 standard to include a standard set($id, $value) method.

PHPUnit gives error: Target [Illuminate\Contracts\View\Factory] is not instantiable

I created a simple test for my new Laravel 7 application. But when I run php artisan test I get the following error.
Target [Illuminate\Contracts\View\Factory] is not instantiable.
The error doesn't appear when I go to the page in the browser.
$controller = new HomeController();
$request = Request::create('/', 'GET');
$response = $controller->home($request);
$this->assertEquals(200, $response->getStatusCode());
Although "Just write feature tests" may seem like a cop-out ("They're not unit tests!"), it is sound advice if you do not want to get bogged down by framework-specific knowledge.
You see, this is one of those problems that come from using facades, globals, or static methods. All sorts of things happen outside of your code (and thus your test code) in order for things to work.
The problem
To understand what is going on, you first need to know how Laravel utilizes Containers and Factories in order to glue things together.
Next, what happens is:
Your code (in HomeController::home() calls view() somewhere.
view() calls app() to get the factory that creates Views1
app() calls Container::make
Container::make calls Container::resolve1
Container::resolve decides the Factory needs to be built and calls Container::build to do so
Finally Container::build (using PHP's ReflectionClass figures out that \Illuminate\Contracts\View\Factory can not be Instantiated (as it is an interface) and triggers the error you see.
Or, if you're more of a visual thinker:
The reason that the error is triggered is that the framework expects the container to be configured so that a concrete class is known for abstracts (such as interfaces).
The solution
So now we know what is going on, and we want to create a unit-test, what can we do?
One solution might seem to not use view. Just inject the View class yourself! But if you try to do this, you'll quickly find yourself going down a path that will lead to basically recreating loads of framework code in userland. So not such a good idea.
A better solution would be to mock view() (Now it is really a unit!). But that will still require recreating framework code, only, within the test code. Still not that good.[3]
The easiest thing is to simply configure the Container and tell it which class to use. At this point, you could even mock the View class!
Now, purists might complain that this is not "unit" enough, as your tests will still be calling "real" code outside of the code-under-test, but I disagree...
You are using a framework, so use the framework! If your code uses glue provided by the framework, it makes sense for the test to mirror this behavior. As long as you don't call non-glue code, you'll be fine![4]
So, finally, to give you an idea of how this can be done, an example!
The example
Lets say you have a controller that looks a bit like this:
namespace App\Http\Controllers;
class HomeController extends \Illuminate\Routing\Controller
{
public function home()
{
/* ... */
return view('my.view');
}
}
Then your test[5] might look thus:
namespace Tests\Unit\app\Http\Controllers;
use App\Http\Controllers\HomeController;
use Illuminate\Contracts\View\Factory;
class HomeControllerTest extends \PHPUnit\Framework\TestCase
{
public function testHome()
{
/*/ Arange /*/
$mockFactory = $this->createMock(Factory::class);
app()->instance(Factory::class, $mockFactory);
/*/ Assert /*/
$mockFactory->expects(self::once())
->method('make')
->with('my.view')
;
/*/ Act /*/
(new HomeController())->home();
}
}
A more complex example would be to also create a mock View and have that be returned by the mock factory, but I'll leave that as an exercise to the reader.
Footnotes
app() is asked for the interface Illuminate\Contracts\View\Factory, it is not passed a concrete class name
The reason Container::make does nothing other than call another function is that the method name make is defined by PSR-11 and the Laravel container is PSR compliant.
Also, the Feature test logic provided by Laravel already does all of this for you...
Just don't forget to annotate the test with #uses for the glue that is needed, to avoid warnings when PHPUnit is set to strict mode regarding "risky" tests.
Using a variation of the "Arrange, Act, Assert" pattern
This is not how you test endpoints in Laravel. You should let Laravel instantiate the application as it is already setup in the project, the examples you can see here.
What you already wrote can be rewritten to something like this.
$response = $this->call('GET', route('home')); // insert the correct route
$response->assertOk(); // should be 200
For the test to work, you should extend the TestCase.php, that is located in your test folder.
If you're finding this in The Future and you see #Pothcera's wall of text, here's what you need to know:
The ONLY reason he's doing any of that and the ONLY reason you're seeing this in the first place in a Unit test is because he and you haven't changed from PHPUnit\Framework\TestCase to Tests\TestCase in the test file. This exception doesn't exist when you extend the test case that includes app().
My advice would be to simply extend the correct base test case and move on with your life.

why is it impossible to test a static method with mockery or anything else

I have read in laravel's facade documentation the following sentence:
Typically, it would not be possible to mock or stub a truly static
class method.
1) question 1: I'm trying to understand facade in laravel. As I guess, it's implemented because if we have classes, and they have big namespaces and big names and every time we want to use this class and we don't want to use new keyword and use statements, we use the facade which is an easier code and readable. I also think that laravel implemented facades because they wanted to write non-static functions in their classes so that they could be tested. After all of this, we use facades like static classes (because of readability and not using new and use), but in reality, it makes new instances.
Am I right?
2) If the above is right, can you provide me an example why it's not possible to test a static method as laravel docs said?
A facade does not solve the big namespaces problem you mentioned. Big namespaces are solved using aliases. You can declare them in your config/app.php and internally Laravel will use class_alias when you call them. This is how e.g. \Cache or \DB work.
A facade is basically a proxy class to a singleton object instance of another class (the facade itself ensures the instance is a singleton).
Typically to register a singleton in Laravel you:
Add app()->singleton(ABC::class) in your service provider
Access it via app()->make(ABC::class)->...
A facade basically takes care of that for you if you haven't already registered that class as a singleton.
Basically a facade is a way to proxy that singleton instance of another class.
Also it's generally not possible to mock or stub static methods however if you are using facades you can do \ABCFacade::swap($mockObject) and therefore your facades can be mocked.
It is also false that you cannot test a static method. You can absolutely test a static method. For example:
public testStaticMethod() {
$this->assertEquals(1, ABC::method()); // We tested a static method against a desired behaviour
}
What you usually can't do is mock a static method. Here's how you would typically mock something with PHPUnit:
public testWithDependency() {
$dependency = $this->getMockBuilder(Dependency::class)->getMock();
$dependency->expects($this->once())->method('dependantMethod')->willReturn(true);
$objectToTest = new ABC($dependency); //We're passing a fake dependency which behaves in an ideal way
$this->assertEquals(1, $objectToTest->methodToTest()); //Any calls to the dependency will call mock methods and not real ones
}
The problem arises when trying to mock a static method. As you can see mocking creates mock instances of a certain type. It can't mock the static members of that type because the mock object itself is not actually of that type.
However as I just found out the statement that it's not possible to mock or stub a static method is not entirely true. There's the AspectMock you can mock static methods or helper methods. This seems to work by intercepting all function calls via a custom autoloader.
This being said, just because you can doesn't mean it's good practice to use static methods, there's other issues to consider like e.g. you normally can't have static interfaces in most programming languages or you normally can't override static methods in most programming languages. Note the "in most programming languages" part here. In PHP it's entirely possible to override static methods with late static binding but that means you need to make a conscious decision about this when implementing the static method.
Another disadvantage is that a class of statics can't implement an interface because interfaces apply to object behaviours and not the static behaviour. Therefore you can't swap out one interface for another if you are using statics which is a major disadvantage.
In general the aversion to static methods is not because of testability but because if you are coding in OOP you are really limited if you are using statics.
Hopefully this will help clear up some confusion.

Create class instances like Laravel

I was looking at some Laravel code for some new ideas for my framework, and I found they declare their class instances like:
Request $request;
Which then acts as a Request class reference, I also noticed that there's a namespace being added in the Laravel code.
I looked all around the PHP Manual, re-read the OOP Manual as well as re-read the namespace section as well, some code I've tried:
// Includes a class called PSM
PSM $psm;
$psm->version();
I understand that'd be under "alternate syntax" structure, and also couldn't find anything under that name either.
I realized this was similar to C# Syntax, whereas when declaring variables or class instances, you give them a specific data-type which with instances is their own class-name. (Might be the wrong definition, the main thing to take from that was the C# similarities of this syntax)
As it might be confusing, I'm talking about the creation of the $request instance from simply typing:
Request $request;
Which then brings the class instance into the scope of the Controller.
You're talking about IoC. You can bind any class using Laravel container and then use this syntax to resolve this class:
function index(Request $request)
The Laravel service container is a powerful tool for managing class dependencies and performing dependency injection. Dependency injection is a fancy phrase that essentially means this: class dependencies are "injected" into the class via the constructor or, in some cases, "setter" methods.

Laravel Package Development simple function does not work

Sorry for the bad title, but I had no idea to give a good title for my problem.
I created a package with the workbench. And now I have a package. Let's say strernd/mypkg
Inside the workbench directory Strernd/Mypkg I've got following code:
http://laravel.io/bin/oNzoa
Why does Mypkg::test()work and $mypkg = new Mypkg(); $mypkg->test(); not? (Inside my app)
The error is
Call to undefined method Strernd\Mypkg\Facades\Mypkg::test()Call to undefined method Strernd\Mypkg\Facades\Mypkg::test()
I think I'm not understanding some basic principles of PHP here. I'm more like a copy&paste / try&error "developer", but it works out well in most cases.
When using it the 'static' way, Laravel is accessing the facade you wrote and instantiating the underlying class, then calling the method on that instance. When you are calling the test method with new Mypkg, you are actually instantiating the facade class, and not the underlying class. That facade class does not have a test method on it, hence the error.
Try it like this:
use Strernd\Mypkg as MypkgActual;
$mypkg = new MypkgActual;
$mypkg->test();
I believe that, because your facade and actual class implementation have the same exact class name, you're running in to a pseudo-class name conflict with Laravel's facade system winning out. You can also try changing the name of your facade to MypkgFacade.

Categories