Laravel 5 - 干净的代码,在哪里保存业务逻辑(控制器示例)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/30201372/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Laravel 5 - Clean code, where to keep business logic (controller example)
提问by Kevin
Below example of 'store' method of my controller Admin/MoviesController. It already seems quite big, and 'update' method will be even bigger.
下面是我的控制器 Admin/MoviesController 的“存储”方法示例。它看起来已经很大了,'update' 方法会更大。
The algoritm is:
算法是:
- Validate request data in CreateMovieRequest and create new movie with all fillable fields.
- Upload poster
- Fill and save all important, but not required fields (Meta title, Meta Description..)
- Then 4 blocks of code with parsing and attaching to movie of Genres, Actors, Directors, Countries.
- Request of IMDB's rating using third-party API
- 验证 CreateMovieRequest 中的请求数据并创建包含所有可填写字段的新电影。
- 上传海报
- 填写并保存所有重要但不是必需的字段(元标题、元描述..)
- 然后是 4 块代码,解析并附加到类型、演员、导演、国家的电影。
- 使用第三方 API 请求 IMDB 的评级
My questions:
我的问题:
- Should I just move all this code to Model and divide it into smaller methods like: removeGenres($id), addGenres(Request $request), ...
Are there some best practices? I'm talking not about MVC, but Laravel's features. At the moment to keep some logic behind the scene I'm using only Request for validation.
public function store(CreateMovieRequest $request) { $movie = Movies::create($request->except('poster')); /* Uploading poster */ if ($request->hasFile('poster')) { $poster = \Image::make($request->file('poster')); $poster->fit(250, 360, function ($constraint) { $constraint->upsize(); }); $path = storage_path() . '/images/movies/'.$movie->id.'/'; if(! \File::exists($path)) { \File::makeDirectory($path); } $filename = time() . '.' . $request->file('poster')->getClientOriginalExtension(); $poster->save($path . $filename); $movie->poster = $filename; } /* If 'Meta Title' is empty, then fill it with the name of the movie */ if ( empty($movie->seo_title) ) { $movie->seo_title = $movie->title; } /* If 'Meta Description' is empty, then fill it with the description of the movie */ if ( empty($movie->seo_description) ) { $movie->seo_description = $movie->description; } // Apply all changes $movie->save(); /* Parsing comma separated string of genres * and attaching them to movie */ if (!empty($request->input('genres'))) { $genres = explode(',', $request->input('genres')); foreach($genres as $item) { $name = mb_strtolower(trim($item), 'UTF-8'); $genre = Genre::where('name', $name)->first(); /* If such genre doesn't exists in 'genres' table * then we create a new one */ if ( empty($genre) ) { $genre = new Genre(); $genre->fill(['name' => $name])->save(); } $movie->genres()->attach($genre->id); } } /* Parsing comma separated string of countries * and attaching them to movie */ if (!empty($request->input('countries'))) { $countries = explode(',', $request->input('countries')); foreach($countries as $item) { $name = mb_strtolower(trim($item), 'UTF-8'); $country = Country::where('name', $name)->first(); if ( empty($country) ) { $country = new Country(); $country->fill(['name' => $name])->save(); } $movie->countries()->attach($country->id); } } /* Parsing comma separated string of directors * and attaching them to movie */ if (!empty($request->input('directors'))) { $directors = explode(',', $request->input('directors')); foreach($directors as $item) { $name = mb_strtolower(trim($item), 'UTF-8'); // Actors and Directors stored in the same table 'actors' $director = Actor::where('fullname', trim($name))->first(); if ( empty($director) ) { $director = new Actor(); $director->fill(['fullname' => $name])->save(); } // Save this relation to 'movie_director' table $movie->directors()->attach($director->id); } } /* Parsing comma separated string of actors * and attaching them to movie */ if (!empty($request->input('actors'))) { $actors = explode(',', $request->input('actors')); foreach($actors as $item) { $name = mb_strtolower(trim($item), 'UTF-8'); $actor = Actor::where('fullname', $name)->first(); if ( empty($actor) ) { $actor = new Actor(); $actor->fill(['fullname' => $name])->save(); } // Save this relation to 'movie_actor' table $movie->actors()->attach($actor->id); } } // Updating IMDB and Kinopoisk ratings if (!empty($movie->kinopoisk_id)) { $content = Curl::get('http://rating.kinopoisk.ru/'.$movie->kinopoisk_id.'.xml'); $xml = new \SimpleXMLElement($content[0]->getContent()); $movie->rating_kinopoisk = (double) $xml->kp_rating; $movie->rating_imdb = (double) $xml->imdb_rating; $movie->num_votes_kinopoisk = (int) $xml->kp_rating['num_vote']; $movie->num_votes_imdb = (int) $xml->imdb_rating['num_vote']; $movie->save(); } return redirect('/admin/movies'); }
- 我是否应该将所有这些代码移动到 Model 并将其划分为更小的方法,例如:removeGenres($id)、addGenres(Request $request)、...
是否有一些最佳实践?我说的不是 MVC,而是 Laravel 的特性。目前为了在幕后保留一些逻辑,我仅使用请求进行验证。
public function store(CreateMovieRequest $request) { $movie = Movies::create($request->except('poster')); /* Uploading poster */ if ($request->hasFile('poster')) { $poster = \Image::make($request->file('poster')); $poster->fit(250, 360, function ($constraint) { $constraint->upsize(); }); $path = storage_path() . '/images/movies/'.$movie->id.'/'; if(! \File::exists($path)) { \File::makeDirectory($path); } $filename = time() . '.' . $request->file('poster')->getClientOriginalExtension(); $poster->save($path . $filename); $movie->poster = $filename; } /* If 'Meta Title' is empty, then fill it with the name of the movie */ if ( empty($movie->seo_title) ) { $movie->seo_title = $movie->title; } /* If 'Meta Description' is empty, then fill it with the description of the movie */ if ( empty($movie->seo_description) ) { $movie->seo_description = $movie->description; } // Apply all changes $movie->save(); /* Parsing comma separated string of genres * and attaching them to movie */ if (!empty($request->input('genres'))) { $genres = explode(',', $request->input('genres')); foreach($genres as $item) { $name = mb_strtolower(trim($item), 'UTF-8'); $genre = Genre::where('name', $name)->first(); /* If such genre doesn't exists in 'genres' table * then we create a new one */ if ( empty($genre) ) { $genre = new Genre(); $genre->fill(['name' => $name])->save(); } $movie->genres()->attach($genre->id); } } /* Parsing comma separated string of countries * and attaching them to movie */ if (!empty($request->input('countries'))) { $countries = explode(',', $request->input('countries')); foreach($countries as $item) { $name = mb_strtolower(trim($item), 'UTF-8'); $country = Country::where('name', $name)->first(); if ( empty($country) ) { $country = new Country(); $country->fill(['name' => $name])->save(); } $movie->countries()->attach($country->id); } } /* Parsing comma separated string of directors * and attaching them to movie */ if (!empty($request->input('directors'))) { $directors = explode(',', $request->input('directors')); foreach($directors as $item) { $name = mb_strtolower(trim($item), 'UTF-8'); // Actors and Directors stored in the same table 'actors' $director = Actor::where('fullname', trim($name))->first(); if ( empty($director) ) { $director = new Actor(); $director->fill(['fullname' => $name])->save(); } // Save this relation to 'movie_director' table $movie->directors()->attach($director->id); } } /* Parsing comma separated string of actors * and attaching them to movie */ if (!empty($request->input('actors'))) { $actors = explode(',', $request->input('actors')); foreach($actors as $item) { $name = mb_strtolower(trim($item), 'UTF-8'); $actor = Actor::where('fullname', $name)->first(); if ( empty($actor) ) { $actor = new Actor(); $actor->fill(['fullname' => $name])->save(); } // Save this relation to 'movie_actor' table $movie->actors()->attach($actor->id); } } // Updating IMDB and Kinopoisk ratings if (!empty($movie->kinopoisk_id)) { $content = Curl::get('http://rating.kinopoisk.ru/'.$movie->kinopoisk_id.'.xml'); $xml = new \SimpleXMLElement($content[0]->getContent()); $movie->rating_kinopoisk = (double) $xml->kp_rating; $movie->rating_imdb = (double) $xml->imdb_rating; $movie->num_votes_kinopoisk = (int) $xml->kp_rating['num_vote']; $movie->num_votes_imdb = (int) $xml->imdb_rating['num_vote']; $movie->save(); } return redirect('/admin/movies'); }
回答by manix
You need to think on how you could re-utilize the code if you need to use it in another classes or project modules. For starting, you could do something like this:
如果您需要在另一个类或项目模块中使用它,您需要考虑如何重新利用代码。首先,您可以执行以下操作:
Movie model, can improved in order to:
电影模型,可以改进以便:
- Manage the way on how the attributes are setted
- Create nice functions in functions include/manage the data of relationships
- 管理如何设置属性的方式
- 在函数中创建漂亮的函数包括/管理关系数据
Take a look how the Movie implements the functions:
看看电影是如何实现这些功能的:
class Movie{
public function __construct(){
//If 'Meta Title' is empty, then fill it with the name of the movie
$this->seo_title = empty($movie->seo_title)
? $movie->title
: $otherValue;
//If 'Meta Description' is empty,
//then fill it with the description of the movie
$movie->seo_description = empty($movie->seo_description)
? $movie->description
: $anotherValue;
$this->updateKinopoisk();
}
/*
* Parsing comma separated string of countries and attaching them to movie
*/
public function attachCountries($countries){
foreach($countries as $item) {
$name = mb_strtolower(trim($item), 'UTF-8');
$country = Country::where('name', $name)->first();
if ( empty($country) ) {
$country = new Country();
$country->fill(['name' => $name])->save();
}
$movie->countries()->attach($country->id);
}
}
/*
* Update Kinopoisk information
*/
public function updateKinopoisk(){}
/*
* Directors
*/
public function attachDirectors($directors){ ... }
/*
* Actores
*/
public function attachActors($actors){ ... }
/*
* Genders
*/
public function attachActors($actors){ ... }
}
Poster, you may considere using a service provider (I will show this example because I do not know your Poster model looks like):
Poster,您可能会考虑使用服务提供商(我将展示此示例,因为我不知道您的 Poster 模型是什么样的):
public class PosterManager{
public static function upload($file, $movie){
$poster = \Image::make($file);
$poster->fit(250, 360, function ($constraint) {
$constraint->upsize();
});
$path = config('app.images') . $movie->id.'/';
if(! \File::exists($path)) {
\File::makeDirectory($path);
}
$filename = time() . '.' . $file->getClientOriginalExtension();
$poster->save($path . $filename);
return $poster;
}
}
Config fileTry using config files to store relevant application constanst/data, for example, to store movie images path:
配置文件尝试使用配置文件来存储相关的应用常量/数据,例如存储电影图片路径:
'images' => storage_path() . '/images/movies/';
Now, you are able to call $path = config('app.images');
globally. If you need to change the path only setting the config file is necessary.
现在,您可以$path = config('app.images');
全局调用了。如果您需要更改路径,只需设置配置文件即可。
Controllers as injected class. Finally, the controller is used as a class where you only need to inject code:
控制器作为注入类。最后,控制器用作一个类,您只需要在其中注入代码:
public function store(CreateMovieRequest $request) {
$movie = Movies::create($request->except('poster'));
/* Uploading poster */
if ($request->hasFile('poster')) {
$file = $request->file('poster');
$poster = \PosterManager::upload($file, $movie);
$movie->poster = $poster->filename;
}
if (!empty($request->input('genres'))) {
$genres = explode(',', $request->input('genres'));
$movie->attachGenders($genders);
}
// movie->attachDirectors();
// movie->attachCountries();
// Apply all changes
$movie->save();
return redirect('/admin/movies');
}