Code Review Stack Exchange is a question and answer site for peer programmer code reviews. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

Overview::

I have a Restful API that pushes all /api/{version}/{Repository} into my ResourceV{1}Controller depending on the {version} number. That Controller extends my Abstract Controller that has the majority of the controller logic, the ResourceV1Controller would load the V1 Interface for the API, and ResourceV2Controller would load V2 interface for the API etc.

I use a factory to load the correct API based on the {Repository} value to inject into my Controller.

I also inject the correct Entity into my Repositories when they are injected in my controller, so in the Repository I can reference School::Where(....)->get() as $this->entity->where(...)->get() instead.

This is two fold, I want to be able to write tests for each of these Repositories, and I want to use a Trait that moves duplicated code across all Repositories into one Location.

Here is what I have:

Controller::

<?php namespace App\Http\Controllers;

use App\Api\Resource\v1\ResourceAPIInterface;
use Illuminate\Http\Request;
use Illuminate\Routing\Router;


/**
* Class ResourceController
*/
class ResourceV1Controller extends RestController 
{

    /**
    * @param ResourceAPIInterface $api
    * @param Request $request
    * @param Router $router
    */
    public function __construct(ResourceAPIInterface $api, Request $request, Router $router)
    {
        $this->api = $api;
        parent::__construct($request, $router);
    }

}

Parent Controller::

<?php namespace App\Http\Controllers;

use App\Api\Resource\v1\ResourceAPIInterface;
use Illuminate\Http\Response;
use Illuminate\Http\Request;
use Illuminate\Routing\Router;


/**
 * Class RestController
 * @package App\Http\Controllers
 */
abstract class RestController extends Controller 
{

    /**
    * @var
    */
    public $api;


    /**
    * @param Request $request
    * @param Router $router
    */
    public function __construct(Request $request, Router $router)
    {
        $this->request = $request;
    }


    /**
     * Display a listing of the resource.
     * @return \Illuminate\Http\Response
     */
    public function index($version, $resource)
    {
        $input = $this->request->all();
        $data = $this->api->fetchAll($input);
        if(empty($data)){
             return (new Response(" ", 204))->header('Content-Type', 'application/json')->header('Cache-Control', 'max-age=3600');
       }else {
             return (new Response($data, 200))->header('Content-Type', 'application/json')->header('Cache-Control', 'max-age=3600');
       }
    }
 }

IoC Binding in Service Provider::

The $uri variable is exploded from the global REQUEST_URI variable

$this->app->bind('App\api\Resource\v2\ResourceAPIInterface',  function() use ($uri){
     $parts = explode('-', strtolower($uri[3]));
     $parts = array_map('ucfirst', $parts);
     $resource = implode('', $parts);
     $version = $uri[2];
     return Factory::getInstance($resource, $version);
});

$this->app->bind('App\api\Entity\v1\EntityInterface', function() use ($uri){
    $parts = explode('-', strtolower($uri[3]));
    $parts = array_map('ucfirst', $parts);
    $resource = implode('', $parts);

    $class = 'App\api\Entity\v1\\'.$resource;
    return new $class;
});

Factory::

<?php namespace App\api;

/**
 * API Factory creates the correct API/version to inject into controller
 *
 * @author  Dustin Parham <[email protected]>
 */
Class Factory
{

    const NS = "App\\api\\";

    /**
     *
     * @param string $api
     * @param int $version
     * @return APIInterface; 
     */
    public static function getInstance($api, $version='current')
    {
        if($version == 'current'){
            $apiConfig = \Config::get('api');
            //Check if API resource exists
            if(isset($apiConfig[$api])){
                $version = $apiConfig[$api]['currentVersion'];
            }else {
                //Need to throw 404 error
                \App::abort(404);
            }
            $version = $apiConfig[$api]['currentVersion'];
        }else {
            $apiConfig = \Config::get('api');
            $arr1 = str_split($version);
            //Check if API resource exists              
            if(isset($apiConfig[$api])){
                $arr2 = str_split($apiConfig[$api]['currentVersion']);
            }else {
                //Need to throw 404 error
                \App::abort(404);
            }

            if((int)$arr1[1]  >  (int)$arr2[1]){
                $version = $apiConfig[$api]['currentVersion'];
            }
        }

        $apiClass = self::NS.$api."\\".$version."\\".$api.'API';

        if (class_exists($apiClass)) {
            return \App::make($apiClass);
        }else {
            //Need to throw 404 error
            \App::abort(404);
        }
    }
 }

The config file api is an array of Repositories, and what their current version is.

Example Repository::

<?php namespace App\api\School\v1;

use App\api\Resource\v1\ResourceAPIInterface;
use App\api\Entity\v1\EntityInterface;

/**
 * Class SchoolAPI
 * @package App\api\School\v1
 */
class SchoolAPI implements ResourceAPIInterface
{

    const VERSION = '1';
    const NAME='School';
    public $entity;

    /**
     * @return string
     */
    public function version()
    {
        return self::VERSION;
    }

    /**
     * @return string
     */
    public function name()
    {
        return self::NAME;
    }

    /**
     * @return EntityInterface
     */
    public function getEntity()
    {
        return $this->entity;
    }

    /**
     * @param EntityInterface $entityInterface
     */
    public function __construct(EntityInterface $entityInterface){
        $this->entity = $entityInterface;
    }

    /**
     * @param $data
     * @return \Illuminate\Database\Eloquent\Collection
     */
    public function fetchAll($data)
    {
         $data = $this->entity->all();
         return $data;
    }
...

Trait::

<?php namespace App\api\Traits\v1;

use App\api\Entity\v1\EntityInterface;


trait ApiUtilities {

    /**
     * @return EntityInterface
     */
    abstract public function getEntity();

    /**
     * @param $id
     * @return \Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\Model
     */
    public function deleteOne($id)
    {
        $data = $this->getEntity()->find($id);
        return $data->delete();
    }


    /**
     * @param $id
     * @param string $fieldName
     * @return bool
     */
    public function toggle($id, $fieldName='active')
    {
        $item = $this->getEntity()->find($id);
        $item->{$fieldName} = (int) !$item->{$fieldName};
        return $item->save();
    }
}

Overall, Is this the best way to accomplish this? I've seen different approaches to dealing with versioning a RESTful API, but wanted to cut down on duplicated code, and excess files.

share|improve this question
    
You don't need to redefine your constructor in your ResourceV1Controller. – Ben Harold Feb 25 '15 at 17:03
    
@BenHarold I actually had to remove the API injection from the RestController constructor, it was causing issues when trying to load a V2 Interface. Made edit to reflect changes. – Dustin Parham Feb 26 '15 at 18:03

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Browse other questions tagged or ask your own question.