I'm building a RESTful API using Laravel 5.
Trying to keep the Http controllers to being as minimal as possible, so I'm using a Service layer (and Repositories) to handle most of the logic.
As most of the controllers have similar methods (e.g. show
, index
, update
) I've written some traits that handle each one. Because these talk directly to the service, I can reuse these for each controller.
For example:
<?php namespace API\Http\Controllers\Restful;
trait UpdateTrait
{
protected $updater;
public function update($itemID)
{
if (!$this->updater->authorize($itemID)) {
return response(null, 401);
}
if (!$this->updater->exists($itemID)) {
return response(null, 404);
}
$data = $this->request->all();
$validator = $this->updater->validator($data);
if ($validator->fails()) {
return response($validator->messages(), 422);
}
$this->updater->update($itemID, $data);
return response(null, 204);
}
}
Because all the controllers share the same traits they can all depend on a single interface.
For example:
<?php namespace API\Services\Interfaces;
interface UpdaterServiceInterface
{
public function validator(array $data);
public function exists($itemID);
public function update($itemID, array $data);
public function authorize($itemID);
}
However, this causes a few issues with automatic dependency injection.
1) I have to use context aware binding:
$this->app->when("API\Http\Controllers\ThingController")
->needs("API\Services\Interfaces\UpdateServiceInterface")
->give("API\Services\Things\ThingUpdateServiceInterface")
This isn't problematic in and of itself - although it does lead to some rather large Service Provider code, which isn't ideal. However, it means I can't seem to use method injection, as the automatic dependency resolution doesn't seem to work for controller methods when using context aware binding: I just get back a could not instantiate API\Services\Interfaces\UpdateServiceInterface
message.
This means that the controller constructor has to handle all of the dependency injection, which gets quite messy:
class ThingsController extends Controller
{
use Restful\IndexTrait,
Restful\ShowTrait,
Restful\UpdateTrait,
Restful\PatchTrait,
Restful\StoreTrait,
Restful\DestroyTrait;
public function __construct(
Interfaces\CollectionServiceInterface $collection,
Interfaces\ItemServiceInterface $item,
Interfaces\CreatorServiceInterface $creator,
Interfaces\UpdaterServiceInterface $updater,
Interfaces\PatcherServiceInterface $patcher,
Interfaces\DestroyerServiceInterface $destroyer
) {
$this->collection = $collection;
$this->item = $item;
$this->creator = $creator;
$this->updater = $updater;
$this->patcher = $patcher;
$this->destroyer = $destroyer;
}
}
This isn't good - it's hard to test and all those dependencies have to get instantiated, even when only one of them is being used.
But I can't think of a nicer way round it.
I could use more specific interface, e.g. ThingUpdateServiceInterface
, (then I wouldn't need the contextual binding and could inject directly into the traits), but then I'd have lots of interfaces that are only different in name. Which seems daft.
The other alternative I thought of was to use lots of smaller controllers, so a Things\UpdateController
and a Things\ShowController
- at least that way unnecessary dependencies won't get instantiated every time.
Or maybe trying to abstract away to using traits is the wrong way to do things. Traits do sometimes seem like they're potentially an anti-pattern.
Any advice would be appreciated.
The application structure in Laravel is basically the structure of folders, sub-folders and files included in a project. Once we create a project in Laravel, we get an overview of the application structure as shown in the image here.
Scalability: Laravel is built with scalability in mind, so it can easily handle large projects and be scaled to meet the needs of growing businesses.
Laravel is a PHP-based Web framework that's fully based on the MVC architecture and much more. The goal is to get started building PHP projects easily using modern tools and techniques.
Laravel 9.x requires a minimum PHP version of 8.0.
Your code looks great and well thought to generalisation.
I would like to suggest this option for using a Factory for creating your services, and not predefine all the dependency injections.
interface UpdateServiceInterface {
public function action($itemID);
//....
}
class ThingUpdateServiceInterface implements UpdateServiceInterface {
public function action($itemID)
{
// TODO: Implement exists() method.
}
}
class ApiServiceFactory {
protected $app;
public function __construct(Application $app) {
$this->app = $app;
}
public function getUpdateService($type) {
if ($type == 'things')
return $this->app->make('ThingUpdateServiceInterface');
}
public function getCreateService($type) {
if ($type == 'things') {
//same idea here and for the rest
}
}
}
class ApiController {
protected $factory;
protected $model;
public function __construct(ApiServiceFactory $factory) {
$this->factory = $factory;
}
public function update($data) {
$updater = $this->factory->getUpdateService($this->model);
$updater->action($data);
}
}
class ThingsController extends ApiController {
protected $model = 'App\Thing';
}
what is the idea of all of this is:
you could create general action for the action, like
DeleteServiceInterface
will be implemented by
class GeneralDeleteService implements DeleteServiceInterface {
public function delete($id){......}
}
and in the factory when you request for that DeleteService if no name is passed so you will return the default service
hope this post wont be "TLDR"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With