Just here to vent a bit.
So, I wanted to find how is the password reset functionality tied to a field called `email` on the user model.
First off, sending. The documentation [says](https://laravel.com/docs/5.6/passwords#password-customization) that one can override the `sendPasswordNotification` method on the user. Okay then, it must be calling the method, so let's look at the default which is:
```php
public function sendPasswordNotification($token)
{
$this->notify(new ResetPasswordNotification($token));
}
```
Notify? That must come from the `Notifiable` trait on the user model, right? It's `Illuminate\Notifications\Notifiable`. Well, here's the whole file:
```php
send($this, $instance);
}
```
Dispatcher? We can see `use Illuminate\Contracts\Notifications\Dispatcher;` at the top of this file. I should get used to this by now, but this catches me off guard every time - I visit the file. And it's a fucking interface! Ah, the contract magic! Whatever, let's look up the [docs](https://laravel.com/docs/5.6/contracts#contract-reference) - it's equivalent to `Notification` facade. Hit the [docs](https://laravel.com/docs/5.6/facades#facade-class-reference) again. And we've finally found the class: `Illuminate\Notifications\ChannelManager` (how is that name related to anything we are looking for??), thus we're back in the same folder again.
```php
/**
* Send the given notification to the given notifiable entities.
*
* @param \Illuminate\Support\Collection|array|mixed $notifiables
* @param mixed $notification
* @return void
*/
public function send($notifiables, $notification)
{
return (new NotificationSender(
$this, $this->app->make(Bus::class), $this->app->make(Dispatcher::class), $this->locale)
)->send($notifiables, $notification);
}
```
Great, a one-liner again! And not only we are cycling through a dozen of single line methods (or even files), but these one-liners are not even real one-liners. Does it really cost you that much to make up a couple of short lived variables? Couldn't you make it at least a bit readable like this?
```php
public function send($notifiables, $notification)
{
$bus = $this->app->make(Bus::class);
$dispatcher = $this->app->make(Dispatcher::class);
$notificationSender = new NotificationSender($this, $bus, $dispatcher, $this->locale);
return $notificationSender->send($notifiables, $notification);
}
```
Whatever, back to reading Laravel. Opening the next file (`NotificationSender.php`) I found the function:
```php
/**
* Send the given notification to the given notifiable entities.
*
* @param \Illuminate\Support\Collection|array|mixed $notifiables
* @param mixed $notification
* @return void
*/
public function send($notifiables, $notification)
{
$notifiables = $this->formatNotifiables($notifiables);
if ($notification instanceof ShouldQueue) {
return $this->queueNotification($notifiables, $notification);
}
return $this->sendNow($notifiables, $notification);
}
```
Are you kidding me? Did the `send` method invoke a `send` method on another class with exactly the same docblock? OK, whatever. I inspected `formatNotifiables` and it just wraps it in a collection or array if it's a single item. Queueing is also something that doesn't seems involved in password resets, right? So let's go to `sendNow` (this is the first time we get to find the next method in the same file).
```php
public function sendNow($notifiables, $notification, array $channels = null)
{
$notifiables = $this->formatNotifiables($notifiables);
$original = clone $notification;
foreach ($notifiables as $notifiable) {
if (empty($viaChannels = $channels ?: $notification->via($notifiable))) {
continue;
}
$this->withLocale($this->preferredLocale($notifiable, $notification), function () use ($viaChannels, $notifiable, $original) {
$notificationId = Str::uuid()->toString();
foreach ((array) $viaChannels as $channel) {
$this->sendToNotifiable($notifiable, $notificationId, clone $original, $channel);
}
});
}
}
```
Hello? Didn't we just `formatNotifiables`? It seems that the next step is `sendToNotifiable` method. This checks if it should send notification, send it and dispatches an event. The main line is this:
```php
$response = $this->manager->driver($channel)->send($notifiable, $notification);
```
`$this->manager` - that's assigned in the constructor as the first argument. Stepping back a bit we see that `ChannelManager` instantiated the `NotificationSender` and passed `$this` as the first argument. BUT THERE IS NO `driver` METHOD! The `ChannelManager` extends `Illuminate/Support/Driver`. And it seems to retrieve or create a driver. It seems that the argument (`$channel`) was filled with `$notification->via($notifiable)` as no `$channels` were passed to `sendNow`.
What's the `$notification`? Well it's the `new ResetPasswordNotification($token)` from the very start. Except the problem that there is no such class in Laravel. Where does that come from? We have to find up the trait that made that call - `Illuminate/Auth/Passwords/CanResetPassword`. We see that `ResetPasswordNotification` is `Illuminate\Auth\Notifications\ResetPassword`. And we also see a method `getEmailForPasswordReset`. Maybe it's the end of our journey? Not so soon. You can [search on github](https://github.com/laravel/framework/search?q=getEmailForPasswordReset) and you'll find that this method is only used when working with tokens. How does an address get into `To:` field of email is still a mystery.
Notice that `ResetPassword` has the `via` method returning `['mail']`. Looking back into manager, it should either create or retrieve a previously created `'mail'` driver. The `createDriver` method calls a custom creator and defaults to constructed `$this->createMailDriver()` method if nothing custom exists. Of course the `createMailDriver` does not exist on `Manager` and nothing in the codebase extends manager with a `'mail'` driver. Excellent.
It turns out that the `ChannelManager` (which extends `Manager`) has the method:
```php
protected function createMailDriver()
{
return $this->app->make(Channels\MailChannel::class);
}
```
Finally, we're pretty much done. We open up `Illuminate\Notifications\Channels\MailChannel` and `send` is indeed there. It does this and that but the crucial part is here:
```php
$this->mailer->send(
$this->buildView($message),
array_merge($message->data(), $this->additionalMessageData($notification)),
$this->messageBuilder($notifiable, $notification, $message)
);
```
Inspecting the calls we see that most are not what we need, except for `messageBuilder` maybe.
```php
protected function messageBuilder($notifiable, $notification, $message)
{
return function ($mailMessage) use ($notifiable, $notification, $message) {
$this->buildMessage($mailMessage, $notifiable, $notification, $message);
};
}
```
I am honestly tired of these super-composite one-liners extracted everywhere... `buildMessage`, could that be the one? Not really, but it contains this suspiciously named method call.
```php
$this->addressMessage($mailMessage, $notifiable, $notification, $message);
```
In that `addressMessage` we have
```php
$mailMessage->to($this->getRecipients($notifiable, $notification, $message));
```
And finally
```php
protected function getRecipients($notifiable, $notification, $message)
{
if (is_string($recipients = $notifiable->routeNotificationFor('mail', $notification))) {
$recipients = [$recipients];
}
return collect($recipients)->mapWithKeys(function ($recipient, $email) {
return is_numeric($email)
? [$email => (is_string($recipient) ? $recipient : $recipient->email)]
: [$email => $recipient];
})->all();
}
```
The `$notifiable->routeNotificationFor('mail', $notification)` seems to call that method on user and it must be the method implemented in `Illuminate\Notifications\RoutesNotifications` which either calls `routeNotificationForMail` if it exists on the user model or returns the `email` attribute of user. GG.
Sounds like Laravel. I still love it though, but I wish it was easier to find certain things like this.
Familiarity grows with practice. When something doesn’t fit your mental model it’s easy to bitch that they’re wrong, but really it’s your perspective. Write a few sites, get used to the structure, and you’re good.’
Edit: mention an alternative that has *approximate* penetration in the market and let’s discuss these alternatives rather than downvoting out of weak concordance. Either you know something or you don’t and either you learn these quirks or you write a scathing post.
Why would you do so much manual searching for methods, Interface implementations etc.? What IDE are you using?
I’m kind of in love with this post. I chuckled a bunch. Thanks for putting the effort into typing it up!
But yeah – using PHPStorm and Ctrl+Clicking on things makes it much quicker to trace down. Also https://github.com/barryvdh/laravel-ide-helper helps a lot to make PHPStorm less confused about some of the magic of Laravel. It generates class stubs that help PHPStorm understand what’s going on.
In reality, I think the shortcut would’ve been to find-in-all-files for `’email’` or `”email”` or `->email` and see what comes up for those.
I once wanted to figure out how the password is crypted and was totally lost in totally “clean” code. Thats not really my definition of pragmatic, but hey, it’s a framework and a (the) popular one, so they’ll be right. Thanks for writing up!
Or you could just check the docs: [https://laravel.com/docs/5.7/notifications#mail-notifications](https://laravel.com/docs/5.7/notifications#mail-notifications) “Customizing The Recipient” section 🙂
Yes, Laravel has some interesting code… but this is ok. How about this:
`class DatabaseManager implements ConnectionResolverInterface`
`{`
`//…`
`/**`
`* Dynamically pass methods to the default connection.`
`*`
`* @param string $method`
`* @param array $parameters`
`* @return mixed`
`*/`
`public function __call($method, $parameters)`
`{`
`return $this->connection()->$method(…$parameters);`
`}`
`}`
It makes the method searching task very difficult sometimes…
Ravioli is delicious!
It is! You can knock up that functionality in about 15 lines normally haha
I still enjoy using it though
I love Laravel for being super clean on the application side, but you’re right- what’s happening behind scenes could use some cleanup and best practices.
This comment is ravioli itself. Can’t follow through.
Yes, yes and a thousand times yes!
formatting please
Holy shit that’s my experience with laravel as well! An abyss!
I disagree with most of the things you’re complaining. I agree sending a password reset to a user is more complicated in Laravel than it needs to be. That complexity comes from leveraging general solutions to bigger problems for the “small” problem you’re using it for here.
>There are no wrong answers only trade offs.
They traded off notification complexity for the ability to send SMS (and other non-email based) notifications and the ability to utilize a queue. Sorry its causing you problems now, but if you want ever to create and queue SMS based notifications, you’ll be grateful.
They traded off “super-composite one-liners” for improved performance.
Please don’t complain about interfaces. Interfaces are great, and you should dig deeper into OOP to understand why.
Many people mentioned it, but get an ide that lets you search in path.
A better answer than just search in path is a stack trace from XDebug with a break point at every step you found.
I find it useful to use xdebug and breakpoints to follow statements execution and figure out some of the Laravel magic. PHPStorm makes this very easy to do.
Pretty much the exact reason Symfony just “clicked” for me after trying to waddle through the black box that is Laravel.
I literally tried Laravel so many times and just couldn’t find it pleasurable, picked up Symfony over a weekend and haven’t looked back.
Over engineering kills developer experience!
Sounds like the kind of experience I get when working with legacy code.
The Laravel ‘consumer’ experience is pretty good though. One thing I will say is that when there is a bug or unexpected behaviour, trying to figure out the internals can be pretty rough