A PHP trait cannot declare a constant, but is it possible to declare using a DocComment (or otherwise) a constant which the trait might "use" if the class using the trait defines it?
For example, imagine a class Book is a model bound to the books table. The framework requires that the table is defined as a TABLE constant:
class Books extends Model
{
const TABLE = 'books';
}
When creating my model, my IDE autocompletes the TABLE constant because it's declared on Model. Wonderful.
Now, say I have a trait called Sluggable which is a trait which will help the developer manage the book's URL slug (e.g. treasure-island), and one of the configurations options is whether to automatically sluggify the book's title, or not. Say this is controlled by the AUTOMATIC_SLUGS constant.
trait Sluggable {
public function generateSlug()
{
if (defined('static::AUTOMATIC_SLUGS') && static::AUTOMATIC_SLUGS) {
// do the thing
}
}
}
Obviously, the trait cannot define the AUTOMATIC_SLUGS constant because that's not allowed, however the IDE cannot suggest, or otherwise verify the AUTOMATIC_SLUGS constant. So my model Books now has a warning on it (in my IDE) telling me that AUTOMATIC_SLUGS is unused:
class Books extends Model
{
use Sluggable;
const TABLE = 'books';
// IDE complains about this constant
const AUTOMATIC_SLUGS = true;
}
Is there a way for me to – on the trait, and without refactoring to use static properties – declare that Sluggable will be checking a constant called AUTOMATIC_SLUGS and have the IDE suggest it/not consider it useless?
Related
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
The PHP docs says the following about overriding trait properties:
If a trait defines a property then a class can not define a property
with the same name unless it is compatible (same visibility and
initial value), otherwise a fatal error is issued.
However, when you use a trait in an abstract class, then you can override the properties defined in the trait in a class extending that abstract class:
<?php
trait PropertyTrait
{
public $prop = 'default';
}
abstract class A
{
use PropertyTrait;
}
class B extends A
{
public $prop = 'overridden';
public function write()
{
echo $this->prop;
}
}
$b = new B();
$b->write(); // outputs "overridden"
Live demo
The code above works, but I can't find any reference about it in the documentation. Is this an intended feature?
Because for all intents and purposes B is not using PropertyTrait. That's used by A to compose the abstract class.
B has no visibility of what traits A is using. If you were to execute class_uses on B, you'd get an empty array. Docs, and example.
Since B is not using any traits, the class is free to override any inherited properties.
The fact that A is an abstract class has no bearing on this. The same behaviour would happen with any class that extended a class that was composed using traits.
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.
I have a method run() in TreeGen class:
class TreeGen implements TreeGenerable{
public function run()
{
...
$this->pushEmptyGroupsToTree($numFighters);
...
}
}
Thing is $this could be one of the 4 Subclasses of tree gen, and all those method implement different versions of pushEmptyGroupsToTree.
But PhpStorm is only looking for pushEmptyGroupsToTree in TreeGen class and off course, I don't have it defined in the super class, so it doesn't detect it.
Is there a way to make him recognize subclass methods?
IDE behaves correctly. You either need to declare this class abstract, or declare abstract method here or in superclass.
UPDATE
Method run of TreeGen could be called from instance of Treegen
$treeGen = new TreeGen;
$treeGen->run();
and that will cause Call to undefined method error.
If TreeGen is not supposed to be called directly it should be abstract.
If you dont want TreeGreen to be used directly, declare it abstract as such:
abstract class TreeGen implements TreeGenerable {
// ...
If you need the child classes to have that method declared, declare it as abstract in your superclass:
abstract class TreeGen implements TreeGenerable
{
// ...
abstract public function pushEmptyGroupsToTree($numFighters);
}
Now you cant extend TreeGen without implementing pushEmptyGroupsToTree as well
For this case you must use PHPDoc and type hinting
And don`t forget about:
ide-helper:generate Generate a new IDE Helper file.
ide-helper:meta Generate metadata for PhpStorm
ide-helper:models Generate autocompletion for models
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__;
}