【问题标题】:How to make a REST API first web application in Laravel如何在 Laravel 中创建一个 REST API 第一个 Web 应用程序
【发布时间】:2014-05-31 16:06:32
【问题描述】:

我想在 Laravel 中创建一个 API 第一个应用程序。我不知道这样做的最佳方法是什么,我将解释我正在尝试做的事情,但请随时给出答案如何以不同的方式做到这一点。

我不希望我的所有前端都用 javascript 编写,并使用 angular.js 或类似的东西解析 API 的 JSON 输出。我希望我的 Laravel 应用程序生成 HTML 视图。我正在尝试使用两个控制器,一个用于 API,一个用于 Web。对于 show User 操作,我的 routes.php 如下所示:

# the web controller
Route::controller('user', 'WebUserController');

# the api controller 
Route::group(array('prefix' => 'api'), function() {
    Route::resource('user', 'UserController');
});

所以/user 将带我到WebUserController 和/api/user 将带我到UserController。现在我想把我所有的逻辑放在 API UserController 中,并从 WebUserController 调用它的动作。这是它们的代码:

class UserController extends BaseController 
{
    public function show($id)
    {
        $user = User::find($id);
        return Response::json(array('success'=>true,'user'=>$user->toArray()));
    }
}

class WebUserController extends UserController 
{
    public function getView($id) 
    {
         # call the show method of the API's User Controller
         $response =  $this->show($id);
         return View::make('user.view')->with('data', $response->getData());
    }
}

在WebUserController 中,我可以使用getData() 获取响应的json 内容,但我无法获取标头和状态代码(它们是Illuminate\Http\JsonResponse 的受保护属性)。

我认为我的方法可能不是最好的,因此我愿意接受有关如何制作此应用程序的建议。

编辑:Drew Lewis 已经回答了如何获取响应的标题和状态的问题,但我仍然认为可能有更好的方法来设计它

【问题讨论】:

  • 你好 Martin,我在 Laravel 5.1 中有同样的问题要解决。那么,你是如何实现的?你用 Repositor 模式了吗?
  • @Ashish,当我问这个问题时,我同意了 Nyan 的回答。这似乎是最简单的解决方案,并且做了我需要的。不过我还没有使用过 Laraval 5.1,不知道从那以后发生了什么变化。
  • 你是否为 Web 和 API 创建了单独的控制器,如果是,你是如何设法防止代码重复的,我想通过存储库设计模式,我们只能将数据库逻辑移出控制器

标签: php api rest laravel controller


【解决方案1】:

这是 Jeffrey Way 的一段视频,他是 Laravel 中最优秀的开发者之一。在本教程中,他将 BackboneJS 应用程序连接到他在 Laravel 中设置的 RESTful 服务。没有比这更好的了。我可以给你写很多样板文件,但只需通过观看精彩视频和喝咖啡来学习。 ;)

https://www.youtube.com/watch?v=uykzCfu1RiQ

【讨论】:

  • hmm,这似乎展示了如何在 laravel 中制作一个后端 json api,并在主干中使用一个前端,这并不是我想要的。
  • 只需使用 json_decode() 并且您有一个 php 数组并忽略它用于 BackboneJS。它是 RESTful 的,所以调用 API 无关紧要。它返回 JSON 的事实很棒,因为任何语言都可以处理 JSON!
  • 我看不出让 Laravel 将 PHP 数组转换为 JSON 字符串,然后在你自己的代码中手动解码的意义。
【解决方案2】:

我还建议使用存储库。 尝试从另一个控制器调用一个控制器将落入一种称为 HMVC(分层模型-视图-控制器)的模式。 这意味着您的整个应用程序都依赖于较低的模块。 在这种情况下,您的 API 将充当数据的存储库(起初这并不是世界上最糟糕的事情)。

但是,当您随后修改 API 中数据返回方式的结构时,依赖它的所有其他内容都必须知道如何响应。 假设您想要进行授权检查,以查看登录用户是否应该能够看到返回用户的详细信息,但出现了错误。

在 API 中,您将返回一个带有 403 禁止代码和一些元数据的 Response 对象。 您的 HTML 控制器必须知道如何处理。

将此与可能引发异常的存储库进行对比。

public function findById ($id)
{
    $user = User::findOrFail($id);

    if (Auth::user->hasAccessTo($user)) {
        return $user;
    } else {
        throw new UnauthorizedAccessException('you do not have sufficient access to this resource');
    }
}

您的 API 控制器看起来更像这样:

public function show($id)
{
    try {
        return $this->user->findById($id);
    } catch (UnauthorizedAccessException $e) {
        $message = $e->getMessage();
        return Response::json('403', ['meta' => ['message' => $message]]));
    }
}

您的 HTML 控制器将如下所示:

public function show($id)
{
    try {
        $user = $this->user->findById($id);
    } catch (UnauthorizedAccessException $e) {
        Session::flash('error', $e->getMessage());
        // Redirect wherever you would like
        return Response::redirect('/');
    }
}

这为您提供了非常可重用的代码,让您可以独立更改控制器实现,而不必担心改变其他人的行为。 我在this post 中写了更多关于如何实现存储库模式的内容:如果您愿意,可以忽略接口并直接跳到实现。

【讨论】:

    【解决方案3】:

    您应该使用存储库/网关设计模式:请参阅答案here。

    例如,在处理 User 模型时,首先创建一个 User Repository。用户存储库的唯一职责是与数据库通信(执行 CRUD 操作)。此用户存储库扩展了一个公共基础存储库并实现了一个包含您需要的所有方法的接口:

    class EloquentUserRepository extends BaseRepository implements UserRepository
    {
        public function __construct(User $user) {
            $this->user = $user;
        }
    
    
        public function all() {
            return $this->user->all();
        }
    
        public function get($id){}
    
        public function create(array $data){}
    
        public function update(array $data){}
    
        public function delete($id){}
    
        // Any other methods you need go here (getRecent, deleteWhere, etc)
    
    }
    

    然后,创建一个服务提供者,它将您的用户存储库接口绑定到您的 eloquent 用户存储库。每当您需要用户存储库时(通过 IoC 容器解析它或在构造函数中注入依赖项),Laravel 会自动为您提供您刚刚创建的 Eloquent 用户存储库的实例。这样一来,如果您将 ORM 更改为 eloquent 以外的其他内容,您只需更改此服务提供者,无需对您的代码库进行其他更改:

    use Illuminate\Support\ServiceProvider;
    
    class RepositoryServiceProvider extends ServiceProvider {
    
        public function register() {
            $this->app->bind(
                'lib\Repositories\UserRepository',        // Assuming you used these
                'lib\Repositories\EloquentUserRepository' // namespaces
            );
        }
    
    }
    

    接下来,创建一个用户网关,其目的是与任意数量的存储库通信并执行应用程序的任何业务逻辑:

    use lib\Repositories\UserRepository;
    
    class UserGateway {
    
        protected $userRepository;
    
        public function __construct(UserRepository $userRepository) {
            $this->userRepository = $userRepository;
        }
    
            public function createUser(array $input)
            {
                // perform any sort of validation first
                return $this->userRepository->create($input);
            }
    
    }
    

    最后,创建您的用户网络控制器。此控制器与您的用户网关对话:

    class UserController extends BaseController 
    {
        public function __construct(UserGatway $userGateway)
        {
            $this->userGateway = $userGateway;
        }
    
        public function create()
        {
            $user = $this->userGateway->createUser(Input::all());
    
        }
    }
    

    通过以这种方式构建应用程序的设计,您可以获得几个好处:您实现了非常清晰的关注点分离,因为您的应用程序将遵循Single Responsibility Principle(通过将您的业务逻辑与数据库逻辑分离) .这使您能够以更轻松的方式执行单元和集成测试,使您的控制器尽可能纤薄,并且如果您将来需要,您可以轻松地将 Eloquent 替换为任何其他数据库。

    例如,如果从 Eloquent 更改为 Mongo,您唯一需要更改的是服务提供者绑定以及创建实现 UserRepository 接口的 MongoUserRepository。这是因为存储库是唯一与您的数据库对话的东西——它不知道其他任何东西。因此,新的 MongoUserRepository 可能类似于:

    class MongoUserRepository extends BaseRepository implements UserRepository
    {
        public function __construct(MongoUser $user) {
            $this->user = $user;
        }
    
    
        public function all() {
            // Retrieve all users from the mongo db
        }
    
        ...
    
    }
    

    现在服务提供者会将 UserRepository 接口绑定到新的 MongoUserRepository:

     $this->app->bind(
            'lib\Repositories\UserRepository',       
            'lib\Repositories\MongoUserRepository'
    );
    

    在所有网关中,您一直在引用 UserRepository,因此通过进行此更改,您实际上是在告诉 Laravel 使用新的 MongoUserRepository 而不是旧的 Eloquent。无需进行其他更改。

    【讨论】:

    • 谢谢,这是一个比 Nyan 的答案更复杂的设计......你能解释一下有什么好处吗?例如,如果您要为另一个 ORM 更改 Eloquent,则需要更改 EloquentUserRepository,还需要更改所有网关。
    • 请重新阅读我的答案 - 我添加了更多信息。换成其他ORM不需要换任何网关,只需要改变服务提供者和仓库即可。
    • 太多样板只是为了改变 ORM 的不太可能的情况。
    • 您好,我根据建议制作了一个模板。我想不通的一件事是如何处理验证。您能否建议如何将验证服务与其集成?代码:github.com/octabrain/Laravel4-Patterns
    • 终于把所有的部分拼凑在一起。代码github.com/octabrain/Laravel4-Patterns
    【解决方案4】:

    您应该为此设计使用存储库。

    例子-

    //UserRepository Class
    class UserRepository {
        public function getById($id)
        {
          return User::find($id);
        }
    }
    
    // WebUser Controller
    class WebUserController extends BaseController {
        protected $user;
    
        public function __construct(UserRepository $user)
        {
            $this->user = $user;
        }
    
        public function show($id)
        {
            return View::make('user.view')->with('data', $this->user->getById($id));
        }
    }
    
    // APIUser Controller
    class UserController extends BaseController {
        protected $user;
    
        public function __construct(UserRepository $user)
        {
            $this->user = $user;
        }
    
        public function show($id)
        {
            $data =>$this->user->getById($id);
            return Response::json(array('success'=>true,'user'= $data->toArray()));
        }
    }
    

    【讨论】:

    • 这看起来很简单,每个模型只有一个额外的类。
    【解决方案5】:

    我对您在 Response 中遇到的问题做出了回应。 您可以从响应中获取标头、状态代码和数据。

    // your data
    $response->getData();
    
    // the status code of the Response
    $response->getStatusCode(); 
    
    // array of headers
    $response->headers->all();
    
    // array of headers with preserved case
    $response->headers->allPreserveCase(); 
    

    $response->headers 是一个 Symfony\Component\HttpFoundation\ResponseHeaderBag,它继承自 Symfony\Component\HttpFoundation\HeaderBag

    【讨论】:

    • 是的,它可以获取我需要的所有东西。一般的方法怎么样……你能想出更好的主意吗?
    • @martin 我正在考虑一种方法,让两者都只有一个控制器。为响应构建数据并将其传递给基于它是否来自 api 路由的中间体将返回正确的响应。但这取决于您期望 Api@show 与 Web@getView 在数据方面有多大不同。
    【解决方案6】:

    查看 Laravel 的 RESTful 控制器:

    http://laravel.com/docs/controllers#restful-controllers

    他们的文档做得很好。

    但更好的是本教程:

    http://code.tutsplus.com/tutorials/laravel-4-a-start-at-a-restful-api-updated--net-29785

    【讨论】:

    • 我已经完成了那个教程......它没有回答我的问题如何将逻辑保留在 API 控制器中,并为将调用 API 控制器的 Web 应用程序创建单独的控制器/视图
    猜你喜欢
    • 2022-01-27
    • 2015-05-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-04
    • 2014-09-06
    • 1970-01-01
    • 2015-07-07
    相关资源
    最近更新 更多