Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Laravel 5.0 Application Structure

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.

like image 882
Small Hadron Collider Avatar asked May 27 '15 08:05

Small Hadron Collider


People also ask

What is the structure of Laravel?

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.

Is Laravel good for large applications?

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.

Does Laravel use MVC?

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.

Will Laravel 9 require PHP 8?

Laravel 9.x requires a minimum PHP version of 8.0.


1 Answers

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:

  • basic ApiController that holds all the methods: update, insert, delete...
  • each other controller for specific object will extend the api controller and override the $model with the Model full name.
  • in the action of the controller it uses the factory for creating the service for that specific object.
  • after creation of the service it uses it for the action desired.
  • 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"

like image 86
Tzook Bar Noy Avatar answered Oct 20 '22 06:10

Tzook Bar Noy