Introduce
Laravel Facades like Auth
, View
, Mail
, or helpers like auth()
, view()
, … have many hidden magic to make our code shorter and faster. But because it is so magic, it is quite difficult to understand deeply or when debugging, we only know how to use it but not know how it works? Then in a project, some use Facade, some use helpers all over the place ??
Have you ever seen the code of the Auth
class, actually it is IlluminateSupportFacadesAuth
, Auth
is alias config in config/app.php
file, its code is like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | <span class="token php language-php"><span class="token delimiter important"><?php</span> <span class="token keyword">namespace</span> <span class="token package">Illuminate Support Facades</span> <span class="token punctuation">;</span> <span class="token comment">/** * @method static mixed guard(string|null $name = null) * @method static void shouldUse(string $name); * @method static bool check() * @method static bool guest() * @method static IlluminateContractsAuthAuthenticatable|null user() * @method static int|null id() * @method static bool validate(array $credentials = []) * @method static void setUser(IlluminateContractsAuthAuthenticatable $user) * @method static bool attempt(array $credentials = [], bool $remember = false) * @method static bool once(array $credentials = []) * @method static void login(IlluminateContractsAuthAuthenticatable $user, bool $remember = false) * @method static IlluminateContractsAuthAuthenticatable loginUsingId(mixed $id, bool $remember = false) * @method static bool onceUsingId(mixed $id) * @method static bool viaRemember() * @method static void logout() * @method static SymfonyComponentHttpFoundationResponse|null onceBasic(string $field = 'email',array $extraConditions = []) * @method static null|bool logoutOtherDevices(string $password, string $attribute = 'password') * @method static IlluminateContractsAuthUserProvider|null createUserProvider(string $provider = null) * @method static IlluminateAuthAuthManager extend(string $driver, Closure $callback) * @method static IlluminateAuthAuthManager provider(string $name, Closure $callback) * * @see IlluminateAuthAuthManager * @see IlluminateContractsAuthFactory * @see IlluminateContractsAuthGuard * @see IlluminateContractsAuthStatefulGuard */</span> <span class="token keyword">class</span> <span class="token class-name">Auth</span> <span class="token keyword">extends</span> <span class="token class-name">Facade</span> <span class="token punctuation">{</span> <span class="token comment">/** * Get the registered name of the component. * * @return string */</span> <span class="token keyword">protected</span> <span class="token keyword">static</span> <span class="token keyword">function</span> <span class="token function">getFacadeAccessor</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token single-quoted-string string">'auth'</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </span> |
How Facade works
If you look into the Auth::user()
method from the IDE or the editor, it will point you to the line:
1 2 3 4 | <span class="token comment">/** * @method static IlluminateContractsAuthAuthenticatable|null user() */</span> |
You think to yourself, “oh, where’s the ?? code ??”. As you probably already know, the above line is just a comment in the docblock used when generating documents with phpdoc or to suggest IDE autocomplete the “magic” method. It is just a document type. And the code is of course magic. The magic here is the PHP Magic Method __callStatic()
:
vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <span class="token comment">/** * Handle dynamic, static calls to the object. * * @param string $method * @param array $args * @return mixed * * @throws RuntimeException */</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">function</span> <span class="token function">__callStatic</span> <span class="token punctuation">(</span> <span class="token variable">$method</span> <span class="token punctuation">,</span> <span class="token variable">$args</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token variable">$instance</span> <span class="token operator">=</span> <span class="token keyword">static</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token function">getFacadeRoot</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> <span class="token operator">!</span> <span class="token variable">$instance</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">RuntimeException</span> <span class="token punctuation">(</span> <span class="token single-quoted-string string">'A facade root has not been set.'</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token variable">$instance</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token variable">$method</span> <span class="token punctuation">(</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token variable">$args</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> |
When you call Auth::user()
, it will run like this:
- Since the
Auth
class has no public static method,user()
, the__callStatic('user')
magic method will be called. - Inside Laravel will retrieve the
auth
instance from the service container - Call the
user()
method of theauth
instance
To know where the auth
instance is bound to the service container, you have to look in the service provider, fortunately it is often named by convention, for example, with Auth Facade, there will be Auth Service Provider => vendor/laravel/framework/src/Illuminate/Auth/AuthServiceProvider.php
:
1 2 3 4 5 6 7 8 9 | <span class="token variable">$this</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token property">app</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token function">singleton</span> <span class="token punctuation">(</span> <span class="token single-quoted-string string">'auth'</span> <span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span> <span class="token variable">$app</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Once the authentication service has actually been requested by the developer</span> <span class="token comment">// we will set a variable in the application indicating such. This helps us</span> <span class="token comment">// know that we need to set any queued cookies in the after event later.</span> <span class="token variable">$app</span> <span class="token punctuation">[</span> <span class="token single-quoted-string string">'auth.loaded'</span> <span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token boolean">true</span> <span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">AuthManager</span> <span class="token punctuation">(</span> <span class="token variable">$app</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
So the auth
instance here is an instance of class IlluminateAuthAuthManager
. Inside this class there is an additional magic method, you can read the code and learn more
Contracts (Interface)
This auth
is a service of core service type, so it also has additional alias declared in vendor/laravel/framework/src/Illuminate/Foundation/Application.php
:
1 2 3 4 | <span class="token punctuation">[</span> <span class="token single-quoted-string string">'auth'</span> <span class="token operator">=</span> <span class="token operator">></span> <span class="token punctuation">[</span> <span class="token package">Illuminate Auth AuthManager</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token keyword">class</span> <span class="token punctuation">,</span> <span class="token package">Illuminate Contracts Auth Factory</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token keyword">class</span> <span class="token punctuation">]</span> <span class="token punctuation">,</span> <span class="token punctuation">]</span> |
It mean:
1 2 3 4 | <span class="token function">app</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token function">make</span> <span class="token punctuation">(</span> <span class="token single-quoted-string string">'auth'</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token function">app</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token function">make</span> <span class="token punctuation">(</span> <span class="token package">Illuminate Auth AuthManager</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token keyword">class</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token function">app</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token function">make</span> <span class="token punctuation">(</span> <span class="token package">Illuminate Contracts Auth Factory</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token keyword">class</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
These three lines will produce the same result, both resolved instances of class IlluminateAuthAuthManager
.
So we have the idea for using DI here is to inject IlluminateAuthAuthManager
or the most accurate way is inject contract (interface) is IlluminateContractsAuthFactory
instead of concrete (concrete? ) class, just like the code of the auth()
function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <span class="token comment">/** * Get the available auth instance. * * @param string|null $guard * @return IlluminateContractsAuthFactory|IlluminateContractsAuthGuard|IlluminateContractsAuthStatefulGuard */</span> <span class="token keyword">function</span> <span class="token function">auth</span> <span class="token punctuation">(</span> <span class="token variable">$guard</span> <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> <span class="token function">is_null</span> <span class="token punctuation">(</span> <span class="token variable">$guard</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">app</span> <span class="token punctuation">(</span> AuthFactory <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token keyword">class</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token function">app</span> <span class="token punctuation">(</span> AuthFactory <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token keyword">class</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token function">guard</span> <span class="token punctuation">(</span> <span class="token variable">$guard</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> |
The explanation is somewhat dangerous, but Laravel docs – Facades , Laravel docs – Contracts also mentioned about this issue. The use of facades or DI depends on the experience of each individual or team, we have used a lot of facade and helper functions, try using contract to inject dependency to see how it is.
Some commonly used contracts and facades:
Contract | Facade | Class | Core Service |
---|---|---|---|
IlluminateContractsAuthAccessGate | Gate | IlluminateContractsAuthAccessGate | |
IlluminateContractsAuthFactory | Auth | IlluminateAuthAuthManager | auth |
IlluminateContractsAuthGuard | Auth::guard() | IlluminateContractsAuthGuard | auth.driver |
IlluminateContractsAuthPasswordBroker | Password::broker() | IlluminateAuthPasswordsPasswordBroker | auth.password.broker |
IlluminateContractsAuthPasswordBrokerFactory | Password | IlluminateAuthPasswordsPasswordBrokerManager | auth.password |
IlluminateContractsBroadcastingFactory | Broadcast | IlluminateContractsBroadcastingFactory | |
IlluminateContractsBroadcastingBroadcaster | Broadcast::connection() | IlluminateContractsBroadcastingBroadcaster | |
IlluminateContractsCacheFactory | Cache | IlluminateCacheCacheManager | cache |
IlluminateContractsCacheRepository | Cache::driver() | IlluminateCacheRepository | cache.store |
IlluminateContractsConfigRepository | Config | IlluminateConfigRepository | config |
IlluminateContractsConsoleKernel | Artisan | IlluminateContractsConsoleKernel | artisan |
IlluminateContractsContainerContainer | App | ||
IlluminateContractsCookieFactory | Cookie | IlluminateCookieCookieJar | cookie |
IlluminateContractsEventsDispatcher | Event | IlluminateEventsDispatcher | events |
IlluminateContractsFilesystemCloud | Storage::cloud() | filesystem.cloud | |
IlluminateContractsFilesystemFactory | Storage | IlluminateFilesystemFilesystemManager | filesystem |
IlluminateContractsFilesystemFilesystem | Storage::disk() | IlluminateContractsFilesystemFilesystem | filesystem.disk |
IlluminateContractsFoundationApplication | App | IlluminateFoundationApplication | app |
IlluminateContractsHashingHasher | Hash | IlluminateContractsHashingHasher | hash |
IlluminateContractsMailMailer | Mail | IlluminateMailMailer | mailer |
IlluminateContractsNotificationsFactory | Notification | IlluminateNotificationsChannelManager | |
IlluminateContractsQueueFactory | Queue | IlluminateQueueQueueManager | queue |
IlluminateContractsQueueQueue | Queue::connection() | IlluminateQueueQueue | queue.connection |
IlluminateContractsRedisFactory | Redis | IlluminateRedisRedisManager | redis |
IlluminateContractsRoutingRegistrar | Route | IlluminateRoutingRouter | router |
IlluminateContractsRoutingResponseFactory | Response | IlluminateRoutingResponseFactory | |
IlluminateContractsRoutingUrlGenerator | URL | IlluminateRoutingUrlGenerator | url |
IlluminateContractsSessionSession | Session::driver() | IlluminateSessionStore | session.store |
IlluminateContractsTranslationTranslator | Lang | IlluminateTranslationTranslator | translator |
IlluminateContractsValidationFactory | Validator | IlluminateValidationFactory | validator |
IlluminateContractsValidationValidator | Validator::make() | IlluminateValidationValidator | |
IlluminateContractsViewFactory | View | IlluminateViewFactory | view |
IlluminateContractsViewView | View::make() | IlluminateViewView | |
IlluminateLogLogManager | log | ||
IlluminateHttpRequest | request | ||
IlluminateRoutingRedirector | redirect | ||
IlluminateDatabaseConnection | db.connection | ||
IlluminateDatabaseDatabaseManager | db |
For example
We will take the code in repo laravel-test-example to perform refactor, because it was written unit test with coverage ratio of ~ 90% =)), so you can rest assured refactor without affecting behavior or function of the system.
1 2 3 | git clone https://github.com/tuanpht/laravel-test-example/ code laravel-test-example |
If you use vscode you can search this regex to find places using the facade: [AZ][az]+::w+(
.
Start with the app/Http/Middleware/RedirectIfAuthenticated.php
:
Before:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <span class="token keyword">namespace</span> <span class="token package">App Http Middleware</span> <span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Closure</span> <span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate Contracts Auth Factory</span> <span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">RedirectIfAuthenticated</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">handle</span> <span class="token punctuation">(</span> <span class="token variable">$request</span> <span class="token punctuation">,</span> Closure <span class="token variable">$next</span> <span class="token punctuation">,</span> <span class="token variable">$guard</span> <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> Auth <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token function">guard</span> <span class="token punctuation">(</span> <span class="token variable">$guard</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token function">check</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">redirect</span> <span class="token punctuation">(</span> <span class="token single-quoted-string string">'/home'</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token variable">$next</span> <span class="token punctuation">(</span> <span class="token variable">$request</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
After:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <span class="token keyword">namespace</span> <span class="token package">App Http Middleware</span> <span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Closure</span> <span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate Contracts Auth Factory</span> <span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">RedirectIfAuthenticated</span> <span class="token punctuation">{</span> <span class="token keyword">protected</span> <span class="token variable">$authFactory</span> <span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">__construct</span> <span class="token punctuation">(</span> Factory <span class="token variable">$authFactory</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token variable">$this</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token property">authFactory</span> <span class="token operator">=</span> <span class="token variable">$authFactory</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">handle</span> <span class="token punctuation">(</span> <span class="token variable">$request</span> <span class="token punctuation">,</span> Closure <span class="token variable">$next</span> <span class="token punctuation">,</span> <span class="token variable">$guard</span> <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> <span class="token variable">$this</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token property">authFactory</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token function">guard</span> <span class="token punctuation">(</span> <span class="token variable">$guard</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token function">check</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">redirect</span> <span class="token punctuation">(</span> <span class="token single-quoted-string string">'/home'</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token variable">$next</span> <span class="token punctuation">(</span> <span class="token variable">$request</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Diff:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | namespace AppHttpMiddleware; use Closure; <span class="token deleted">-use IlluminateSupportFacadesAuth;</span> <span class="token inserted">+use IlluminateContractsAuthFactory;</span> class RedirectIfAuthenticated { <span class="token inserted">+ protected $authFactory;</span> <span class="token inserted">+</span> <span class="token inserted">+ public function __construct(Factory $authFactory)</span> <span class="token inserted">+ {</span> <span class="token inserted">+ $this->authFactory = $authFactory;</span> <span class="token inserted">+ }</span> <span class="token inserted">+</span> public function handle($request, Closure $next, $guard = null) { <span class="token deleted">- if (Auth::guard($guard)->check()) {</span> <span class="token inserted">+ if ($this->authFactory->guard($guard)->check()) {</span> return redirect('/home'); } |
Tests still pass! Because there is actually no test case for this class =))
Next is to go to the app/Http/Controllers/Web/RegisterController.php
has 2 places using the Mail
facade, we can refactor as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | use AppHttpRequestsWebRegisterRequest; use AppServicesWebUserService; <span class="token deleted">-use IlluminateSupportFacadesMail;</span> use AppMailUserRegistered; use IlluminateHttpRequest; <span class="token inserted">+use IlluminateContractsMailMailer;</span> class RegisterController extends Controller { protected $userService; <span class="token deleted">- public function __construct(UserService $userService)</span> <span class="token inserted">+ protected $mailer;</span> <span class="token inserted">+</span> <span class="token inserted">+ public function __construct(UserService $userService, Mailer $mailer)</span> { $this->userService = $userService; <span class="token inserted">+ $this->mailer = $mailer;</span> } /** @@ -39,7 +42,7 @@ class RegisterController extends Controller $user = $this->userService->create($inputs); <span class="token deleted">- Mail::to($user)->send(new UserRegistered($user->getKey(), $user->name));</span> <span class="token inserted">+ $this->mailer->to($user)->send(new UserRegistered($user->getKey(), $user->name));</span> return redirect()->action([static::class, 'showRegisterSuccess']); } @@ -68,7 +71,7 @@ class RegisterController extends Controller $user = $this->userService->findByEmail($request->input('email')); if ($user && !$user->hasVerifiedEmail()) { <span class="token deleted">- Mail::to($user)->send(new UserRegistered($user->getKey(), $user->name));</span> <span class="token inserted">+ $this->mailer->to($user)->send(new UserRegistered($user->getKey(), $user->name));</span> } return redirect()->action([static::class, 'showFormVerification'])->with('resent', true); |
This time, of course, the test failed because we changed the constructor of the class. Fix the tests!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | use AppModelsUser; <span class="token inserted">+use IlluminateSupportTestingFakesMailFake;</span> use SymfonyComponentHttpKernelExceptionNotFoundHttpException; class RegisterControllerTest extends TestCase @@ -21,13 +22,17 @@ class RegisterControllerTest extends TestCase /** @var UserService|MockeryMockInterface */ private $userService; <span class="token inserted">+ /** @var MailFake */</span> <span class="token inserted">+ private $mailer;</span> <span class="token inserted">+</span> public function setUp(): void { parent::setUp(); $this->userService = Mockery::mock(UserService::class); <span class="token inserted">+ $this->mailer = new MailFake;</span> <span class="token deleted">- $this->registerController = new RegisterController($this->userService);</span> <span class="token inserted">+ $this->registerController = new RegisterController($this->userService, $this->mailer);</span> } public function testShowFormRegister() @@ -60,11 +65,10 @@ class RegisterControllerTest extends TestCase ->shouldReceive('create') ->with($filteredInputs) ->andReturn(new User($filteredInputs)); <span class="token deleted">- Mail::fake();</span> $response = $this->registerController->register($request); <span class="token deleted">- Mail::assertQueued(UserRegistered::class);</span> <span class="token inserted">+ $this->mailer->assertQueued(UserRegistered::class);</span> $this->assertInstanceOf(RedirectResponse::class, $response); $this->assertEquals( action([RegisterController::class, 'showRegisterSuccess']), @@ -121,11 +125,10 @@ class RegisterControllerTest extends TestCase ->shouldReceive('findByEmail') ->with($inputs['email']) ->andReturn($fakeUser); <span class="token deleted">- Mail::fake();</span> $response = $this->registerController->resendVerificationLink($request); <span class="token deleted">- Mail::assertQueued(UserRegistered::class);</span> <span class="token inserted">+ $this->mailer->assertQueued(UserRegistered::class);</span> $this->assertInstanceOf(RedirectResponse::class, $response); $this->assertEquals( action([RegisterController::class, 'showFormVerification']), |
Here you can use Mockery, then setup expectation for to()
, send()
methods but it will be a bit more complicated, so I will not mention here =)) The solution in this article is to use the MailFake
class by Laravel Provided to support testing, quite convenient, the code does not have to change much.
Refactor with Rector
Similar to other classes. Manually manual to understand more, but actually a tool that automatically refactor some of these common tasks which is Rector – Upgrade Your Legacy App to a Modern Codebase . Referring to this, this tool is specialized in supporting automatic refactor code by analyzing source code, similar to some static code analysis tools , some features:
- Rename classes, methods, properties, namespaces or constants
- Upgrade PHP code from version to 7.4
- Migrate from Nette to Symfony
- Apply PHP 7.4 typed property
- Refactor Laravel facades to DI
- Technical debt repayment =))
- …
I tried to install with composer but got conflicted with Laravel, so I tried using docker:
1 2 3 4 | docker run -it --rm -v <span class="token variable"><span class="token variable">$(</span> <span class="token function">pwd</span> <span class="token variable">)</span></span> :/project --entrypoint <span class="token string">"/bin/bash"</span> rector/rector:latest <span class="token function">cd</span> /project /rector/bin/rector process app --set laravel-static-to-injection --dry-run |
The tool is also a software, but a software must have a bug. Should still need to review and improve, for example some refactor that Rector is using is concrete class => switch to the corresponding interface …
- Cannot apply DI to the Service Provider constructor because it only accepts a parameter of
IlluminateContractsFoundationApplication $app
- Cannot use DI in Model class
- The
IlluminateContractsRoutingUrlGenerator
does not have atemporarySignedRoute()
method, instead the concrete classIlluminateRoutingUrlGenerator
must be used. - With the Mailable
UserRegistered
class, for convenience, it will not use the constructor DI, instead inject inject into thebuild(UrlGenerator $urlGenerator)
methodbuild(UrlGenerator $urlGenerator)
because it is called by the service container (similar to Queue Job ‘s methodhandle()
)12<span class="token variable">$this</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token property">mailer</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token function">to</span> <span class="token punctuation">(</span> <span class="token variable">$user</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token function">send</span> <span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token class-name">UserRegistered</span> <span class="token punctuation">(</span> <span class="token variable">$user</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token function">getKey</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">,</span> <span class="token variable">$user</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token property">name</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span>
Seevendor/laravel/framework/src/Illuminate/Mail/Mailable.php
:123456789101112131415161718192021<span class="token comment">/*** Send the message using the given mailer.** @param IlluminateContractsMailMailer $mailer* @return void*/</span><span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">send</span> <span class="token punctuation">(</span> MailerContract <span class="token variable">$mailer</span> <span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">return</span> <span class="token variable">$this</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token function">withLocale</span> <span class="token punctuation">(</span> <span class="token variable">$this</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token property">locale</span> <span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token keyword">use</span> <span class="token punctuation">(</span> <span class="token variable">$mailer</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span>Container <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token function">getInstance</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token function">call</span> <span class="token punctuation">(</span> <span class="token punctuation">[</span> <span class="token variable">$this</span> <span class="token punctuation">,</span> <span class="token single-quoted-string string">'build'</span> <span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span><span class="token keyword">return</span> <span class="token variable">$mailer</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token function">send</span> <span class="token punctuation">(</span> <span class="token variable">$this</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token function">buildView</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">,</span> <span class="token variable">$this</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token function">buildViewData</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span> <span class="token variable">$message</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token variable">$this</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token function">buildFrom</span> <span class="token punctuation">(</span> <span class="token variable">$message</span> <span class="token punctuation">)</span><span class="token operator">-</span> <span class="token operator">></span> <span class="token function">buildRecipients</span> <span class="token punctuation">(</span> <span class="token variable">$message</span> <span class="token punctuation">)</span><span class="token operator">-</span> <span class="token operator">></span> <span class="token function">buildSubject</span> <span class="token punctuation">(</span> <span class="token variable">$message</span> <span class="token punctuation">)</span><span class="token operator">-</span> <span class="token operator">></span> <span class="token function">runCallbacks</span> <span class="token punctuation">(</span> <span class="token variable">$message</span> <span class="token punctuation">)</span><span class="token operator">-</span> <span class="token operator">></span> <span class="token function">buildAttachments</span> <span class="token punctuation">(</span> <span class="token variable">$message</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span><span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span><span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span><span class="token punctuation">}</span>
Using PHPCS?
In the project you can also set up a convention that doesn’t use the facade or helpers, use the custom snifffs shared at the repo: https://github.com/vladyslavstartsev/laravel-strict-coding-standard
Using:
1 2 | composer require --dev vladyslavstartsev/laravel-strict-coding-standard |
Then add the rule to the project’s phpcs.xml
file:
1 2 3 4 5 6 7 8 9 10 | <span class="token prolog"><?xml version="1.0"?></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> ruleset</span> <span class="token attr-name">name</span> <span class="token attr-value"><span class="token punctuation">=</span> <span class="token punctuation">"</span> Project convention <span class="token punctuation">"</span></span> <span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> rule</span> <span class="token attr-name">ref</span> <span class="token attr-value"><span class="token punctuation">=</span> <span class="token punctuation">"</span> LaravelStrictCodingStandard.Laravel.DisallowUseOfGlobalFunctions <span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> rule</span> <span class="token attr-name">ref</span> <span class="token attr-value"><span class="token punctuation">=</span> <span class="token punctuation">"</span> LaravelStrictCodingStandard.Laravel.DisallowUseOfFacades <span class="token punctuation">"</span></span> <span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> properties</span> <span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span> property</span> <span class="token attr-name">name</span> <span class="token attr-value"><span class="token punctuation">=</span> <span class="token punctuation">"</span> laravelApplicationInstancePath <span class="token punctuation">"</span></span> <span class="token attr-name">type</span> <span class="token attr-value"><span class="token punctuation">=</span> <span class="token punctuation">"</span> string <span class="token punctuation">"</span></span> <span class="token attr-name">value</span> <span class="token attr-value"><span class="token punctuation">=</span> <span class="token punctuation">"</span> ../bootstrap/app.php <span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span> properties</span> <span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span> rule</span> <span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span> ruleset</span> <span class="token punctuation">></span></span> |
For example:
1 2 3 4 5 6 7 8 9 10 11 12 | $ ./vendor/bin/phpcs app/Mail/UserRegistered.php -s FILE: /home/ubuntu/Projects/laravel-test-example/app/Mail/UserRegistered.php ------------------------------------------------------------------------------------------------------------- FOUND 2 ERRORS AFFECTING 2 LINES ------------------------------------------------------------------------------------------------------------- 43 <span class="token operator">|</span> ERROR <span class="token operator">|</span> It is strongly discouraged not to use URL Laravel Facade, switch to constructor injection <span class="token operator">|</span> <span class="token operator">|</span> <span class="token punctuation">(</span> LaravelStrictCodingStandard.Laravel.DisallowUseOfFacades.LaravelFacadeInstanceUsage <span class="token punctuation">)</span> 45 <span class="token operator">|</span> ERROR <span class="token operator">|</span> Laravel <span class="token keyword">function</span> now <span class="token punctuation">(</span> <span class="token punctuation">)</span> has been deprecated, it is highly recommended not to use it <span class="token operator">|</span> <span class="token operator">|</span> <span class="token punctuation">(</span> LaravelStrictCodingStandard.Laravel.DisallowUseOfGlobalFunctions.LaravelGlobalFunctionUsage <span class="token punctuation">)</span> ------------------------------------------------------------------------------------------------------------- |
DI in Blade view?
What about blade view, how to avoid using the facade or global helper?
Laravel also supports using DI in blades, using the @inject
directive https://laravel.com/docs/7.x/blade#service-injection
1 2 3 4 5 6 | @ <span class="token function">inject</span> <span class="token punctuation">(</span> <span class="token single-quoted-string string">'authFactory'</span> <span class="token punctuation">,</span> <span class="token single-quoted-string string">'IlluminateContractsAuthFactory'</span> <span class="token punctuation">)</span> <span class="token operator"><</span> div <span class="token operator">></span> Welcome <span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token punctuation">{</span> <span class="token variable">$authFactory</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token function">guard</span> <span class="token punctuation">(</span> <span class="token single-quoted-string string">'web'</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token function">user</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token property">name</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">.</span> <span class="token operator"><</span> <span class="token operator">/</span> div <span class="token operator">></span> |
Conclude
What is this for?
The problem with the facade is that it makes the logic code tightly tied to the framework, making it difficult to extend or customize. In fact, very rarely “people” change the framework or customize the core service, but just list out here for anyone who wants to dig deeper =))
Because the facade is related to magic methods, it is difficult for the IDE to support autocomplete effectively, requiring an additional package such as the IDE helper .
The most obvious benefit of using DI is that we know the class depends on which other classes or interfaces, it is easier to track which methods are called, the IDE and the code editor support better because the variable has been type-hinting (also is a step towards more strongly type ) in the contructor.
Other major frameworks that often apply DI are Symfony and Magento 2, there will be yaml or xml configuration files to bind interfaces and classes (I will talk in the series about Magento), it is easy to know which concrete class is injected with the interface and it is also easy to replace or extend core service. As for the track down facade, service provider, and alias of Laravel to find the corresponding implement function class, it is a bit difficult for beginners.
Using DI, the code is a bit longer because it requires initialization in the constructor, but if you feel that the constructor has too many dependencies, it is because your class is doing too much work, until refactor. Laravel docs also have notes:
However, some care must be taken when using facades. The primary danger of facades is class scope creep. Since facades are so easy to use and do not require injection, it can be easy to let your classes continue to grow and use many facades in a single class. Using dependency injection, this potential is mitigated by the visual feedback a large constructor gives you that your class is growing too large. So, when using facades, pay special attention to the size of your class so that its scope of responsibility stays narrow.
Temporarily translated: However, you must take care when using the facade. Because it is easy to use the facade without injection, it can make the class scope bigger, the class does too much work. If using DI, this risk can easily be answered by constructors becoming more verbose. So when using the facade, pay attention to the scope and functionality of the class to make sure it doesn’t do too much work.
The habit of using the Laravel facade may be difficult to replace, but hopefully the article will help you gain a little more knowledge and progress toward the purpose of making larger, more diverse projects so that you can have more advanced skills. degrees, experience
References
- https://laravel.com/docs/5.8/facades
- https://laravel.com/docs/5.8/contracts
- https://github.com/rectorphp/rector/
- https://www.tomasvotruba.com/blog/2019/03/04/how-to-turn-laravel-from-static-to-dependency-injection-in-one-day/
- https://www.freecodecamp.org/news/moving-away-from-magic-or-why-i-dont-want-to-use-laravel-anymore-2ce098c979bd/
- https://github.com/vladyslavstartsev/laravel-strict-coding-standard
- https://github.com/Dealerdirect/phpcodesniffer-composer-installer