⬆️ ⬇️

How do you work with Laravel?

I'm curious how other developers work with the Laravel framework. I saw Adam Wathan talk about writing resource controller code and how simple / clean it looks.

I would like to share with the community how they work with Laravel. I would like to learn something new and see what I can improve with my design patterns.



In my code right now I use the following approach:



Controller -> Service -> Repository -> Model



Where possible, I try to follow the principles of SOLID as a general guide. So, without further introduction, let's move on to the code.



Routes



I like using Laravel Resource Controllers. As an example, let's create a pizza list page (index). I also added two examples to show the order sub-page pertaining to pizza only. (page to create, and then, finally, page to save the order).

')

 Route::resource('/pizzas', 'PizzaController', ['only' => [ 'index', ]]); Route::group(['prefix' => 'pizzas'], function() { Route::resource('/orders', 'Pizza\OrderController', ['only' => [ 'create', 'store', ]]); }); 


Final routes:



GET /pizzas

App\Http\Controllers\PizzaController@index



GET /pizzas/orders/create

App\Http\Controllers\Pizza\OrderController@create



POST /pizzas

App\Http\Controllers\Pizza\OrderController@store





You will notice that I define my URL-based namespaces. The reason I am doing this is that it is easy for me to understand where to debug, in the event of a crash. Plus, in my opinion, it separates the tasks and allows me to control my controllers with only 7 actions that the resource controller processes.





Controllers



As you can see above, I try to use only 7 methods, as suggested in the Laravel documentation for Resource Controllers:





I admit that there are times when I can use additional methods within a particular controller class if I feel that this makes sense, but I try to do it rarely.



My controller methods will use automatic injection to load the Service class. So for our pizza list page we want to use PizzaService to get all the pizza from the database.



 public function index(PizzaService $pizzaService) { return view('pizza.index', [ 'pizzas' => $pizzaService->all(), ]); } 


Note: the view also matches the same folder template as the namespace.



Services



I like to use Services to process logic in my applications. Service for me can be the concept of Domain Driven or 1-to-1 using a model (database table). I have an abstract class that handles common methods that I use a lot in my Services. (Note: comments / dockblock removed in code examples)



 <?php namespace App\Services; abstract class BaseService { public $repo; public function all() { return $this->repo->all(); } public function paginated() { return $this->repo->paginated(config('paginate')); } public function create(array $input) { return $this->repo->create($input); } public function find($id) { return $this->repo->find($id); } public function update($id, array $input) { return $this->repo->update($id, $input); } public function destroy($id) { return $this->repo->destroy($id); } } 


Therefore, my Domain / Model based Service looks like this:



 <?php namespace App\Services; use App\Repositories\PizzaRepository; class PizzaService extends BaseService { private $pizzaRepository; public function __construct(PizzaRepository $pizzaRepository) { $this->pizzaRepository = $pizzaRepository; } } 


In this PizzaService, I can add my own logic-specific methods that I am trying to implement. In the $pizzaService->all() list page continuation, $pizzaService->all() calls the all () method in the BaseRepository, since we do not overwrite it.



Repositories



The repositories in my code are mostly methods that use Eloquent to get or write data to the database. Only the Service can call the repository level. (I doubted this approach, but now I always try to follow it).



 <?php namespace App\Repositories; use Illuminate\Database\Eloquent\Model; abstract class BaseRepository { public $sortBy = 'created_at'; public $sortOrder = 'asc'; public function all() { return $this->model ->orderBy($this->sortBy, $this->sortOrder) ->get(); } public function paginated($paginate) { return $this ->model ->orderBy($this->sortBy, $this->sortOrder) ->paginate($paginate); } public function create($input) { $model = $this->model; $model->fill($input); $model->save(); return $model; } public function find($id) { return $this->model->where('id', $id)->first(); } public function destroy($id) { return $this->find($id)->delete(); } public function update($id, array $input) { $model = $this->find($id); $model->fill($input); $model->save(); return $model; } } 


So, PizzaRepository, which is loaded by PizzaService, looks like this:



 <?php namespace App\Repositories; use App\Models\Pizza; class PizzaRepository extends BaseRepository { protected $model; public function __construct(Pizza $pizza) { $this->model = $pizza; } } 


It should also be noted that in my Services and Repositories, if I need to, I can overwrite the default methods to use my own implementation. You remember earlier in our example of the pizza list, BaseService called the all () method in the repository. Now, since PizzaRepositoryis does not overwrite the BaseRepository, it uses the all () method in the BaseRepository to return a list of all the pizzas from the database.



As an example, when overwriting the BaseRepository method, one of my methods uses stored procedures to insert data, so I could overwrite the create method from BaseRepository, for example:



 <?php namespace App\Repositories; use App\Models\Pizza; class PizzaRepository extends BaseRepository { protected $model; public function __construct(Pizza $pizza) { $this->model = $pizza; } public function create(array $input) { return $this->model->hydrate( DB::select( 'CALL create_pizza(?, ?)', [ $name, $hasCheese, ] ) ); } } 


This is a simple example, but now I return the hydrated result from my stored procedure.



Traits



I just introduced the idea of ​​Traits into my code. This happened when I discovered that some of my repository layers need the ability to customize sorting (you will notice that my BaseRepository has two properties, sortBy and sortOrder. So I created the Sortable feature. Now I can sort the page with a list of pizza with these properties.



 <?php namespace App\Repositories\Traits; trait Sortable { public $sortBy = 'created_at'; public $sortOrder = 'asc'; public function setSortBy($sortBy = 'created_at') { $this->sortBy = $sortBy; } public function setSortOrder($sortOrder = 'desc') { $this->sortOrder = $sortOrder; } } 


So, now in my Service, where I applied the Trait, I can set the sorting. (The example below establishes order in the constructor, but you can also run this method in your custom Service methods.)



 <?php namespace App\Services; use App\Repositories\PizzaRepository; class PizzaService extends BaseService { private $pizzaRepository; public function __construct(PizzaRepository $pizzaRepository) { $this->pizzaRepository = $pizzaRepository; $this->pizzaRepository->setSortBy('sort_order'); } } 


I also had some difficulties trying to figure out how to handle a greedy boot. I didn’t like the idea of ​​returning data to my controller, and then using a lazy greedy download. This was not very convenient for optimizing database queries. As soon as I made Sortable Trait, I decided to make a similar Relationable trait.



 <?php namespace App\Repositories\Traits; trait Relationable { public $relations = []; public function setRelations($relations = null) { $this->relations = $relations; } } 


Then I added the with () method to my BaseRepository methods:



 public function all() { return $this->model ->with($this->relations) ->orderBy($this->sortBy, $this->sortOrder) ->get(); } 


Through my service, I can add the following code to any method (orders is the model's relationship method).



 $this->repo->setRelations(['orders']); 


I'm worried that over time it can be easy to complicate my application with too many Traits, but now it works very well.

Source: https://habr.com/ru/post/350778/



All Articles