restFUL API对存储的依赖


restFUL API dependency on store

我的应用程序是一个图书管理器,我可以在其中创建图书和页面。

我的bookController在POST上有一个"存储",它存储标题和描述。

public function store()
{

    $rules = array(
        'title'         => 'required|min:3',
        'description'   => 'required|min:30'
    );

    $validator = Validator::make(Input::all(), $rules);
    if ($validator->fails()) {
        return Response::json(
            array(
                'metadata' => array(
                    'error'     => true,
                    'message'   => 'The book creation has failed'
                )
            ),
            400
        );
    }
    else {
        $slug           = Str::slug(Request::get('title'));
        $existSlug      = Book::where('slug',$slug)->get();
        if(count($existSlug) > 0) {
            return Response::json(
                array(
                    'metadata' => array(
                        'error'     => true,
                        'message'   => 'This title is already taken'
                    )
                ),
                400
            );
        }
        else {
            $book               = new Book;
            $book->title        = Request::get('title');
            $book->slug         = $slug;
            $book->description  = Request::get('description');
            $book->user_id      = Auth::user()->id;
            $book->status       = false;
            $book->save();
            $stored = $book->toArray();
            $metadata = array(
                'metadata' => array(
                    'error' => false,
                )
            );
            return Response::json(
                array_merge($stored,$metadata),
                201
            );
        }
    }
}

我还有一个pageController,在POST上有一个"存储",它存储页面内容:

public function store()
{
    $rules = array(
        'content'       => 'required|between:300,350',
        'book_id'       => 'required|exists:books,id'
    );
    $validator = Validator::make(Input::all(), $rules);
    if($validator->fails()) {
        return Response::json(
            array(
                'metadata'  => array(
                    'error'     => true,
                    'message'   => 'The page must be between 300 and 350 characters'
                )
            ),
            400
        );
    }
    else {
        $book               = Book::find(Input::get('book_id'));
        $content            = Input::get('content');
        $parent             = Page::where('book_id',$book->id)->where('status',1)->orderBy('id', 'desc')->first();
        if($parent){
            $parent_id      = $parent->id;
            $parent_number  = $parent->number;
            $status         = 0; //Define the status of the created page
        }
        else{
            //If it's the first page of the book
            $parent_id      = 0;
            $parent_number  = 0;
            $status         = 1; //if there's no parent page, the new page is the first - auto validated - page of the book.
            if($book->user_id != Auth::user()->id) {
                return Response::json(
                    array(
                        'metadata'  => array(
                            'error'     => true,
                            'message'   => 'You have to be the author of a book to write the first page.'
                        )
                    ),
                    403
                );
            }
        }
        $page               = new Page;
        $page->content      = $content;
        $page->book_id      = $book->id;
        $page->parent_id    = $parent_id;
        $page->number       = $parent_number + 1;
        $page->user_id      = Auth::user()->id;
        $page->status       = $status;
        $page->save();
        $stored     = $page->toArray();
        $metadata   = array(
            'metadata'  => array(
                'error' => false
            )
        );
        return Response::json(
                array_merge($stored,$metadata),
                201
            );
    }
}

每当有人创作一本书时,他至少要写第一页。这将产生一个带有输入标题、描述和内容的表单。

我向[…]/books发送了一个POST,其中包含我输入的标题和描述

如果成功=>我获得图书id,并将其与输入内容一起发送到[…]/pages。

以下是我的问题:

  • 有人可以在[…]/books上发帖,并将存储一本没有页面的新书
  • 我想用更"restFUL的方式"来解决这个问题,这意味着没有像将内容发送到/books并在bookController中进行页面验证这样的"黑客解决方案"
  • 此外,即使我选择了破解的方式,我的API仍然不安全:我可以停止要发送的第二个请求(到/页面)

如何处理这种共同依赖关系?

1st

你的控制器做得太多了,他们不应该知道你的业务逻辑——这是应该由特定类(模型、存储库、域逻辑类)处理的事情。

创建一些类来处理这个逻辑,将Input发送给它们并实现它。使用Laravel可以随心所欲地调用它们,因为你可以用代码做任何你想做的事情。

第二

如果您有不同的数据约束需要强制执行,您可以:

根据相同的请求处理它们

根据您的界面,如果您在一个页面上拥有所需的一切,您只需发送数据并在存储库中处理即可,存储库可以访问您的所有模型。

一个可以用于两者的例子是:

使用依赖注入的图书存储库,这意味着book和Page将由Laravel:自动实例化

class BookRepository {
    
    __construct(Book $book, Page $page)
    {
        $this->book = $book;
        $this->page = $page;
    }
    public function store($input)
    {
        if ( ! $this->book->validate($input) || !$this->page->validate($input))
        {
            return 'error';
        }
        $book->create(input);
        $page->create($input);
    }
}

基础模型与您的验证:

class Book extends BaseModel {
    public function validate($input)
    {
        /// validate here and return
    }   
}

您的模型和规则:

class Book extends BaseModel {
    $book_rules = array(
        'title'         => 'required|min:3',
        'description'   => 'required|min:30'
    );

}
class Page extends BaseModel {
    $page_rules = array(
        'content'       => 'required|between:300,350',
        'book_id'       => 'required|exists:books,id'
    );
    
}

然后创建一个包含图书信息和页面信息的视图,该视图将POST到BookController@store:

class BookController extends Controller {
    public function __controller(BookRepository $book_repository)
    {
        $this->book_repository = $book_repository;
    }
    public function store()
    {
        if ( ! $this->book_repository->store($input))
        {
            return Redirect::back()
                        ->withErrors(
                                        $this->book_repository
                                        ->validation
                                        ->messages()
                                        ->all()
                                    );
        }
        return Redirect::to('success');
    }
    
}

我们再次使用依赖注入$book_repository将自动实例化。因此,您的Controller不需要知道Book或Page的功能,只需要获取请求并将其传递给负责处理所有内容的存储库即可。

这还不是全部,但这只是一个开始。

处理不同的请求

这是常有的事。用户发送请求,应用程序检查并存储数据。用户发送第二个请求,应用程序检查所有内容,并在需要时发回错误。

在后台处理它们

这是一种更聪明的方法。你的应用程序将在一个或多个请求中接收所有数据,存储它们,使用队列工作者进行检查,并向用户发送电子邮件,告诉他有一些数据需要填写。没有页面的书可以在一段时间后删除。你不会冒数据不好的风险,一旦你这样做了,你的用户就会知道缺少了什么。