امروز گرم کارکردن بودم که اتفاق عجیبی افتاد؛ خیلی وقت بود که از Blade استفاده نکرده بودم و بعد از returnکردن ویو، پیام خطایی شبیه تصویر بالا دیدم. میگفت ویوی x را پیدا نکردم، منظورت ویوی x نیست؟ مطمئن بودم که ویو وجود دارد، وگرنه چنین پیشنهادی نمیداد، اما چرا باید نام ویویی که پیدا نشده را پیشنهاد دهد؟! اگر foo.bar وجود نمیداشت، بیمعنا بود که foo.bar را به عنوان شکل درست آن پیشنهاد دهد.
برای اینکه بدانیم چه اتفاقی افتاده است، باید اول بپرسیم که لاراول چگونه ویوها را پیدا میکند؟ وقتی از هلپر فانکشن view استفاده میکنیم:
return view('blog.post');
چگونه فایل مربوط به آن را پیدا میکند؟ آن هم وقتی پسوند فایل را ذکر نکردهایم؟ برای درک این مطلب، سراغ کلاس Illuminate\View\FileViewFinder میرویم:
protected $extensions = ['blade.php', 'php', 'css', 'html']; protected function getPossibleViewFiles($name) { return array_map(function ($extension) use ($name) { return str_replace('.', '/', $name).'.'.$extension; }, $this->extensions); }
متد getPossibleViewFiles نام ویو (در مثال ما blog.post) را دریافت میکند و سپس حالتهای ممکن (یعنی نام فایل به همراه پسوندهایی که در پراپرتی extensions آمده است) را به عنوان یک آرایه برمیگرداند. اگر دقت کنیم، از فانکشن str_replace استفاده شده است تا نقطه را تبدیل به slash کند. یعنی blog.post تبدیل به blog/post میشود، و به این صورت لاراول میتواند یک ویو را از پوشههای تو در تو پیدا کند.
وقتی چنین باشد، ما نمیتوانیم از نقطه در نام خود فایل استفاده کنیم! این نکته در داکیومنتیشن هم آمده است:
View directory names should not contain the . character.
به مثال قبلی برگردیم: نام ویو را foo.bar گذاشته بودم و لاراول تصور میکرد که چنین فایلی باید در این مسیر باشد:
resources/views/foo/bar.{blade.php/php/css/html}
از آنجا که ویویی به نام bar در آن مسیر وجود ندارد، متد findInPaths به اینجا میرسد:
throw new InvalidArgumentException("View [{$name}] not found.");
تا اینجای کار همهچیز طبیعی است و مشکل در کارکرد این کلاس نبود. مشکل از آنجا شروع میشود که پکیج facade/ignition به برنامهنویس پیشنهاداتی میدهد؛ مثلاً اگر در نوشتن نام ویو خطای تایپی داشته باشید، سعی میکند نام اصلی فایل را پیدا کند و پیشنهاد دهد.
این اتفاق، در کلاس Facade\Ignition\SolutionProviders\ViewNotFoundSolutionProvider میافتد:
public function getSolutions(Throwable $throwable): array { $suggestedView = $this->findRelatedView($missingView); if ($suggestedView) { return [ BaseSolution::create("{$missingView} was not found.") ->setSolutionDescription("Did you mean `{$suggestedView}`?"), ]; } return [ BaseSolution::create("{$missingView} was not found.") ->setSolutionDescription('Are you sure the view exists and is a `.blade.php` file?'), ]; }
قسمتهایی از کدهای متد را حذف کردهام تا قسمتهایی که مربوط به موضوع ما هستند را ببینید. با کمک متد findRelatedView تلاش میشود تا ویوی مرتبط با ویوی درخواستشده پیدا شود. اگر پیدا شد، عبارت Did you mean... برگردانده میشود (یعنی حالتی که کاربر خطای تایپی داشته است و ویویی با نام مشابه وجود دارد) و در غیر این صورت به کاربر پیشنهاد میشود که مطمئن شود که چنین ویویی وجود دارد.
از آنجا که ما foo.bar را واقعاً ساخته بودیم، متد findRelatedView آن را پیدا میکند و سناریوی اول اتفاق میافتد: پیشنهاددادن!
foo.bar was not found. Did you mean foo.bar?
شاید الآن خندهدار و جالب به نظر برسد، ولی من در ابتدا گیج شده بودم و حتی فکر میکردم که این دو با هم فرق دارند و چشم من از فرط خستگی قادر به تشخیص تفاوتشان نیست! اما واقعاً یکی بودند: foo.bar را پیدا نکردم، نکند که منظورت foo.bar بوده باشد؟! ?
بعد از ظهر، ساعت کاریام تمام شده بود و دوباره کنجکاو شدم که مشکل آن را حل کنم. به سرعت متوجه شدم که این مشکل از کلاس خود لاراول نیست، بلکه از پکیجی است که لاراول برای نمایش خطاها استفاده میکند. ریپازیتوری را فورک کردم و تغییرات لازم را برای حل این مشکل انجام دادم:
خوشبختانه ساعتی قبل این pull request تایید شد و پیام Freek شبم را ساخت:
Thanks!
نتیجهگیریِ اخلاقیِ پست این بود که همیشه داکیومنتیشن را بخوانید تا اشتباهات عجیبی مانند آنچه صبح مرتکب شدم -استفاده از نقطه در نام ویو- را مرتکب نشوید؛ نقطه ممنوع!