Recently I came across a class that uses use statement inside of the class definition.
Could someone explain what exactly does it do - as I can't find any information about it.
I understand that it might be a way of moving it away form a global scope of the given file, but does it perhaps allow the given class inherit from multiple parent classes as well - since extends only allows one parent class reference?
The example I saw was in the User model of the original installation of Laravel:
<?php
use Illuminate\Auth\UserTrait;
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableTrait;
use Illuminate\Auth\Reminders\RemindableInterface;
class User extends Eloquent implements UserInterface, RemindableInterface {
use UserTrait, RemindableTrait;
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'users';
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = array('password', 'remember_token');
}
and I've seen some examples of this model actually using methods included within the UserTrait class - hence my suspicion, but would really like to find out more about the meaning of the enclosed use statements.
PHP documentation says:
The use keyword must be declared in the outermost scope of a file (the
global scope) or inside namespace declarations. This is because the
importing is done at compile time and not runtime, so it cannot be
block scoped. The following example will show an illegal use of the
use keyword:
followed by the example:
namespace Languages;
class Greenlandic
{
use Languages\Danish;
...
}
which would indicate that it is an incorrect use of the use keyword - any clues?
They are called Traits and are available since PHP 5.4. They are imported into another class or namespace using use keyword which is included since PHP 5.0 like importing a regular class into another class. They are single inheritance. The primary reason for the implementation of traits is because of the limitation of single inheritance.
For more details see the PHP trait manual:
Related
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?
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
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.
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__;
}
I am trying to create model named CustomDataStore (models/custom_data_store.php) and it is extending Eloquent, so table is named as custom_data_stores, but it gives me error.
Eloquent wants table named customdatastores. Of course I can set manually table name, but how can I set such name automatically?
For it to be made automatically your model would have to be in models/custom/data/store.php
class Custom_Data_Store extends Eloquent
{
}
I have noticed that laravel's eloquent can't really "see" if there are underscores on the model's filename. I'm not really sure what's happening inside (still a newbie), but this is only based on my observation..
what I did was
I have two different models, named tblReport_date.php and tblReportdate.php, both have the same codes except that they are pointed at different tables.
tblReportdate.php code:
<?php
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;
class tblReportdate extends Eloquent implements UserInterface, RemindableInterface
{
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'tblClients';
...rest of the codes...
tblReport_date.php code:
<?php
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;
class tblReportdate extends Eloquent implements UserInterface, RemindableInterface
{
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'tblReport_date';
...rest of the codes...
on the controller, I have this code
$db = tblReportdate::all();
return View::make('db.index')->with('db', $db);
the result is that it will only load tblReportdate.php and not tblReport_date.php
I tried extracting tblReport_date.php alone and tested it.. it always returns an error, regardless of the class name, etc..and IDK why. If someone can explain this pls comment it out.. anyways, just avoid putting underscores on filenames XD