Laravel -- Why the `::class` Constant - php

In Laravel 5.1, the kernel for the CLI class looks something like this
#File: app/Console/Kernel.php
class Kernel extends ConsoleKernel
{
//...
protected $commands = [
\App\Console\Commands\Inspire::class,
];
//...
}
Is the change to using the predefined/magic constant ::class
\App\Console\Commands\Inspire::class
functionally different than simply using the class name?
\App\Console\Commands\Inspire

Nope, using ::class on a class returns the fully qualified class name, so it's the same thing as writing 'App\Console\Commands\Inspire' (in quotes, since it's a string). The class keyword is new to PHP 5.5.
It looks silly in this example, but it can be useful in e.g. testing or in defining relations. For instance, if I have an Article class and an ArticleComment class, I might end up doing
use Some\Long\Namespace\ArticleComment;
class Article extends Model {
public function comments()
{
return $this->hasMany(ArticleComment::class);
}
}
Reference: PHP Docs.

For executing the code it doesn't make a difference, but the ::class constant is most useful with development tools. If you use the class name you have to write it as string '\App\Console\Commands\Inspire' - that means:
No IDE auto completion
No suport of automatic refactoring ("rename class")
No namespace resolving
No way to automatically detect usages (IDE) or dependencies (pDepend)
Side note: Before PHP 5.5 came out, I used to define a constant __CLASS in most of my own classes for exactly this purpose:
class X {
const __CLASS = __CLASS__;
}

Related

PHP 8.2 Dynamic Properties Deprecated: how to use them anyway in a compatible way

In PHP 8.2, Dynamic Properties are deprecated, and will result in a fatal error from PHP 9 onwards.
Using Dynamic Properties on Classes running PHP 8.2 will lead to PHP Deprecated: Creation of dynamic property is deprecated E_DEPRECATED warnings.
Now, while this is generally a bad OO practice to have public/dynamic properties in a Class, this question is not about the best OO practice but instead, how to make actual code that uses Dynamic Properties compatible with PHP 8.2 onwards.
How to make actual codebases that make use of Dynamic Properties compatible with the new behaviour?
As suggested by ADyson, the solution is to use the #[AllowDynamicProperties] attribute just above the class definition.
Classes marked with #[AllowDynamicProperties] as well as their children can continue using dynamic properties without deprecation or removal.
For classes that intentionally don't have a fixed set of properties, it's possible to either implement magic __get()/__set() or mark the class using the #[AllowDynamicProperties] attribute. Marking a class with #[AllowDynamicProperties] is fully backwards-compatible with earlier PHP versions, because prior to PHP 8.0 this would be interpreted as a comment, and the use non-existent classes as attributes is not an error.
This is a full example, as contained in this github repository that I've created to test this feature on Traits and Extended Classes
<?php
namespace App\Classes;
/**
* Use the fully-qualified AllowDynamicProperties, otherwise the #[AllowDynamicProperties] attribute on "MyClass" WILL NOT WORK.
*/
use \AllowDynamicProperties;
#[AllowDynamicProperties]
class MyClass
{
/**
* Dynamic attributes will work with no deprecation warnings
*/
public function __construct()
{
$this->first_name = 'George';
$this->last_name = 'Orwell';
}
}
class MyExtendedClass extends MyClass
{
/**
* Even if "MyExtendedClass" is not using #[AllowDynamicProperties], it extends "MyClass", that is using it.
* Dynamic attributes will work with no deprecation warnings
*/
public function __construct()
{
parent::__construct();
}
}
replace this:
class yourClassName {
for this:
class yourClassName extends \stdClass {

How to prevent a false-warning in PhpStorm when the return-type is deduced incorrectly?

Consider the following code:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
/**
* Class MyModel
* #package App\Models
* #mixin Builder
*/
class MyModel extends Model
{
public static function getGreens(): Builder
{
return (new self())->where('color', '=', 'green');
}
}
On the return statement, the PhpStorm (2020.3) complains that:
Return value is expected to be '\Illuminate\Database\Eloquent\Builder', 'MyModel' returned
And suggest to:
Change return type from '\Illuminate\Database\Eloquent\Builder' to 'MyModel'
which is weirdly incorrect (the where method does return an instance of \Illuminate\Database\Eloquent\Builder, while the IDE deduces the return type as being of MyModel type). By removing the return type, the IDE issues another warning:
Missing function's return type declaration
The code works without any problems, but the IDE shouldn't report any false warnings! How should I avoid these warnings in PhpStorm?
It's a result of not following the "best practices". The class-hierarchy of MyModel does not provide a method of where; in other words, such a method does not exist in the class-hierarchy. But! The parent class of Model does provide a magic method of __call() which gets triggered when an inaccessible method in the object context is invoked (in your case, the method of where). It essentially forwards the "call" to a new instance of \Illuminate\Database\Eloquent\Builder, that has the implementation of the requested method (it's acquired from invoking the method of newQuery()). This mechanism is not only IDE-unfriendly, but also slower.
Thus; drop the #mixin tag, and instead of utilizing the "magic methods", use "native access":
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class MyModel extends Model
{
public static function getGreens(): Builder
{
return (new self())->newQuery()->where('color', '=', 'green');
// ^^^^^^^^^^^^
}
}
As I understand (based on how Laravel works) it's because of #mixin line.
#mixin tag works similar to how PHP's native trait works. So if you have a method in a trait that returns $this / self and then use that trait in a class, then return of that method ($this/self) points to the class where it is used.
Now, Builder::where() method also returns $this or self ... but it's not actually a trait but Laravel magically makes that where() method available in this class.
And here comes the problem: that #return $this actually points to the Builder class but when used "as a trait" (because of #mixin) it gets resolved to the current MyModel class by the IDE.
You either use #mixin and live with ignoring the issue (you can use error suppression via Alt + Enter quick fix menu -- it will add a comment for IDE to tell to ignore that specific issue here) .. or remove #mixin and declare those methods differently.
AFAIK Laravel helper package should add all such Builder methods to the Model class via #method PHPDoc lines (look into that for details, go through past issues there to see how and why it does that etc, e.g. #541).
Another suggestion: try Laravel Idea plugin -- it's a PAID plugin but it makes working with Laravel code much easier and AFAIK it should cover such basic stuff.
For reference:
https://youtrack.jetbrains.com/issue/WI-1730 -- original request for documenting/supporting mixins
Outstanding #mixin tickets that can be related here (at least partially):
https://youtrack.jetbrains.com/issue/WI-34809
https://youtrack.jetbrains.com/issue/WI-49567
or try your own search, e.g. https://youtrack.jetbrains.com/issues/WI?q=mixin%20laravel

Laravel Eloquent - accessing parent model from child model returns child model

Extending Eloquent models seems to be a thing people do. I have an interesting issue:
FooBase.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class FooBase extends Model {
protected $table = 'foo_bar';
}
FooExtends.php
namespace App\Models;
class FooExtends extends FooBase {
public function method() {
return FooBase::first(); // or even parent::first()
}
}
Calling (new FooExtends())->method() returns an instance of FooExtends instead of FooBase. (Just static methods affected, which may answer my own question, but one would think Laravel would handle this. Calling (new FooBase())->first() from within the child class works.) What's going on here?
PHP 7.3, Laravel 5.7
This is a really interesting PHP quirk that doesn't apply static context when calling an ancestor class.
Basically, the "static" call to FooBase::first() gets interpreted the same as parent::first(), because PHP knows that FooBase is the parent of the current class context FooExtends. And since calls to parent stay within the context of the current object, the first() call ends up being routed to __call() and not __callStatic() (which would create a new context using the FooBase class).
Really interesting thing to learn about PHP internals and class contexts. Thanks for giving me a reason to poke around. :)

PHP's "use" Keyword and Autoloading

My question is in three parts:
Does putting in a use statement trigger the autoloader immediately, or does it wait until the class is used? (lazy-loading)
If autoloading isn't done in a lazy-load fashion, could that negatively affect performance?
Which pattern is best to follow, and why? PhpStorm shows "Unnecessary fully qualified name..." as a code issue when the use statement isn't employed.
Here's an example class definition for a Laravel controller with a use statement:
namespace App\Http\Controllers;
use Carbon\Carbon;
class FooController extends Controller
{
/**
* This action uses the Carbon class
*/
public function bar1()
{
return view('foo.bar1', ['now' => new Carbon()]);
}
/**
* This action does not use the Carbon class
*/
public function bar2()
{
return view('foo.bar2');
}
}
The same class without the use statement:
namespace App\Http\Controllers;
class FooController extends Controller
{
/**
* This action uses the Carbon class
*/
public function bar1()
{
return view('foo.bar1', ['now' => new \Carbon\Carbon()]);
}
/**
* This action does not use the Carbon class
*/
public function bar2()
{
return view('foo.bar2');
}
}
1) The class is autoloaded when you perform a new Class() statement.
2) see 1)
3) Which pattern is best to follow and why?:
I'd recommend to use use because you might get into a situation where you have really long namespaces and your code will become unreadable.
From the php docs:
This example attempts to load the classes MyClass1 and MyClass2 from
the files MyClass1.php and MyClass2.php respectively.
<?php
spl_autoload_register(function ($class_name) {
include $class_name . '.php';
});
$obj = new MyClass1();
$obj2 = new MyClass2();
?>
Namespaces are only an additional feature to organize classes.
EDIT: As #IMSoP pointed out in the comments, new is not the only time the autoloader is triggered. Accessing a class constant, static method, or static property will also trigger it, as will running class_exists.
The use statement can be thought of like a C pre-processing macro, if you're familiar with those: it rewrites the current file at compile time to let you write a short name for a long class, function, or constant name. It doesn't trigger autoloading, as it doesn't care if a class exists or not.
For instance, if you write use Foo\Bar\Baz as X, then everywhere that X is mentioned as a class name, the PHP compiler rewrites that to mention Foo\Bar\Baz instead. Only when code mentioning the class (e.g. new X, X::FOO, X::doSomething()) is actually run does it see if there really is a class Foo\Bar\Baz, and trigger the autoloader as necessary.
The common form use Foo\Bar\Baz is just shorthand for use Foo\Bar\Baz as Baz, assigning the alias Baz to the class name Foo\Bar\Baz.
As the manual points out the alias is only processed at compile time, so dynamic lookups will not use it. In the example above, class_exists('X') will return false, but you can use class_exists(X::class) to expand out the alias - the compiler will automatically substitute the full class name as a string, so at run-time, the expression will be class_exists('\Foo\Bar\Baz').
Whether use statements make your code better is therefore entirely a matter of style: the intent is that your code will be more readable without the long fully-qualified class names, but it will make no difference to how the code actually runs.

How to get the trait name with namespace inside itself?

I want to know if there is a way to get the traits namespace inside itself, I know that I can use self::class to get the classname, but inside a trait it gets the namespace of the class that is using the trait, I don't want to type it's name fixed like new ReflectionClass('trait')
Is there any function or const that can do this?
I'm a little bit confused by your question but if you need the fully qualified name from the trait then you may use __TRAIT__ magic constant and if you only need the namespace of the trait then you may use __NAMESPACE__. For example, declare a trait using a namespace:
namespace App\Http\Controllers\Traits;
trait Methods
{
public function getNamespace()
{
// Get fully qualified name of the trait
echo __TRAIT__; // App\Http\Controllers\Traits\Methods
echo PHP_EOL;
// Get namespace of the trait
echo __NAMESPACE__; // App\Http\Controllers\Traits
}
}
Now, declare a class using another namespace and use that trait inside this class:
namespace App\Http\Controllers;
use App\Http\Controllers\Traits\Methods;
class TraitController
{
use Methods;
public function index()
{
// Call the method declared in trait
$this->getNamespace();
}
}
(new TraitController)->index();
The predefined magic constants __TRAIT__ (since 5.4.0) and __NAMESPACE__ (since 5.3.0) is used so use which one is needed. Tested in php v-5.4.0. Check the demo here.
Also, if you want to get the fully qualified name of the trait from the class that is using it then you may use NameOfTheTrait::class (NameOfTheClass::class/NameOfTheInterface::class) but this is available since php v-5.5.
Also be careful when using self::class. The self::class will give the fully qualified name of the class where you've used it because the self always references the lexical scope (where it's physically used) since the scope of self is determined during the compile time so you may get unexpected results if you inherit a class where a self::class statement is used. In other words, if you call any static method from a child class then the calling context will be still the parent class if you use self in your parent class, in that case you need to use static instead of self. This is actually another topic so please read more on php manual about Late Static Binding.

Categories