In software development, one of the “best practices” is to create reusable code that can be implemented in different parts of your application if needed.
Imagine you have a blog, and you need to show a “highlights” widget on a sidebar.
The “highlights” will be populated with the response of an API.
So, in your homepage controller You’d probably have something similar to the following:
<br></br><?php class HomeController extends Controller { protected $blog; public function __construct(BlogRepository $blog) { $this->blog = $blog } public function index() { return view('blog', [ 'posts' => $blog->latest(), 'highlights' => $blog->highlights() ]); }}
Nice and clean, but this approach starts to become a problem when you need to pass the same “highlights” variable to every page of your site; for example, a contact page:
<?php class ContactPageController extends Controller { protected $blog; public function __construct(BlogRepository $blog) { $this->blog = $blog } public function index() { return view('contact', [ 'highlights' => $blog->highlights() ]); }}
Imagine what would happen if you have 20 different controllers; You might end up having a lot of code duplication and as your application grows it will be harder to maintain.
Using Laravel’s view composers
View composers allow you to move the logic outside your controller and pass the data to the specified set of views.
<?php class HighLightsComposer{ protected $users; public function __construct(BlogRepository $blog) { $this->blog = $blog } public function compose(View $view) { $view->with('highlights', $this->blog->highlights()); }}
And then in your service provider, You’ll have something like this:
<?php class ComposerServiceProvider extends ServiceProvider{ public function boot() { View::composer( 'highlights', 'App\Http\ViewComposers\HighlighsComposer' ); }}
At this point you can refactor your controllers like so:
<?php class HomeController extends Controller { protected $blog; public function __construct(BlogRepository $blog) { $this->blog = $blog } public function index() { return view('blog', [ 'posts' => $blog->latest() ]); }} class ContactPageController extends Controller { public function index() { return view('contact'); } }
Thinking about the product
At first hand, this looks great, but let’s give it some thought.
When you use “view composers,” the process it’s a little magic and runs in a part of the app that is not that obvious, especially for those who do not have much experience using Laravel.
Imagine your client asks you to replace the content of the “highlights” widget for something static. In the current example, you can do it just by updating the content of your “highlights.blade.php” file.
The app will work as expected but, the API call is going to be running in the background (in the View Composer).
So, it doesn’t matter if you remove the logic from your views, you’ll need to change the name on the view, or update the ServiceProvider to stop the API call from the ViewComposer.
Using View Components
The following is an approach I’m using in one of the projects I’m working on right now, I made up the name, feel free to call it as you wish.
What we want is to reuse a common view that will be built using dynamic data (coming from any resource).
Creating a new Highlights
component class
This “View Component” classes could share an interface or contract to specify the type of data we want to return. In this case, the Laravel’s Htmlable
contract will suit perfectly.
<?php namespace App\ViewComponents; use Illuminate\Support\Facades\View;use Illuminate\Contracts\Support\Htmlable; class HighlightsComponent implements Htmlable{ protected $blog; public function __construct(BlogRepository $blog) { $this->blog = $blog; } public function toHtml() { return View::make('highlights') ->with('highlights', $this->blog->highlights()) ->render(); }}
Creating a new blade directive to render the view components
As we are using dependency injection in the class above, it would be a good idea to use the IOC to resolve those dependencies for us.
<?php class AppServiceProvider extends ServiceProvider{ public function register() { Blade::directive('render', function ($component) { return "<?php echo (app($component))->toHtml(); ?>"; }); }}
Finally, you can “render” the view component, which will return an HTML partial, on any view.
// home.blade.php @render(\App\ViewComponents\HighlightsComponent::class)
Wrapping up
Using this approach you can reuse complex components using dynamic data on any view within your application.
The component logic will only run if it’s included through the @render()
blade directive.
If you work with a large team of developers, you can be sure that the performance of your application will not be affected if anyone changes the implementation of the widget in the view without updating the implementation in the backend.