Я на стадии рефакторинга своего кода и натолкнулся на интересную головоломку.

В моем ArticleController у меня есть стандартный метод хранения болота для хранения статьи в моей таблице базы данных статей.

/**
 * Store a newly created resource in storage.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Http\Response
 */
public function store(StoreArticle $request)
{
    $article = new Article();

    $defauultPublished = "draft";
    $IntranetOnly = false;
    $isFeatured = false;

    $isFeatured = ($request->get('featuredArticle') == "1" ? true : false);
    $IntranetOnly = ($request->get('IntranetOnly') == "1" ? true : false);

    $article->title = $request->get('title');
    $article->slug = str_slug($request->get('title'));
    $article->author = $request->get('author');
    $article->category = $request->get('category');
    $article->excerpt = $request->get('excerpt');
    $article->content = clean($request->get('content'));
    $article->featuredImage = $request->get('featuredImage');
    $article->featuredVideo = $request->get('featuredVideo');
    $article->readingTime = $this->calculateReadTime($request);
    $article->featuredArticle = $isFeatured;
    $article->IntranetOnly = $IntranetOnly;
    $article->published = $defauultPublished;

    $article->save();

    $article->handleTags($request);

    return redirect('editable/news-and-updates')->with('success', 'Article has been added');
}

У меня также есть функция для расчета времени чтения:

/**
 * Calculate a rough reading time for an articles by counting the words present
 * These words are then divided by a given reading time and rounded to the nearest whole number
 * Reading time average is roughly 267 words per minute, so this also accounts for relatively slow readers
 *
 * @param Request $request
 * @return void
 */
public function calculateReadTime(Request $request)
{
    $readingSpeed = 200;

    $title = str_word_count(strip_tags($request->get('title')));
    $excerpt = str_word_count(strip_tags($request->get('excerpt')));
    $content = str_word_count(strip_tags($request->get('content')));

    $words = ($title + $excerpt + $content);

    $minutes = round($words / $readingSpeed);

    return $minutes . ' minute' . ($minutes == 1 ? '' : 's');
}

Мой вопрос: должны ли эти методы быть перемещены в модель Article?

-1
Jesse Orange 20 Авг 2018 в 14:45

3 ответа

Лучший ответ

Контроллер должен быть максимально тонким. Следуя изобретательному подходу (который вы, похоже, делаете), метод store() в вашем классе ArticleController должен максимально стараться выглядеть следующим образом:

class ArticleController extends Controller
{
    public function store(CreateArticleRequest $request)
    {
        $article = Article::create($request->validated());

        // Redirect with success message
    }
}

Здесь ваши данные запроса проверяются в классе запроса формы еще до того, как они достигают метода контроллера; и затем из этих проверенных данных создается экземпляр модели Article.

Пара других заметок ...

Утверждения типа ($data['featuredArticle'] == "1" ? true : false) слишком многословны. Вы делаете проверку условия, которая оценивается как true или false; вам не нужно вручную возвращать каждое значение в троичном операторе. Так что это может быть уменьшено до $data['featuedArticle'] == '1'. Кроме того, если вы передадите значение 0 по умолчанию, вы можете просто полностью избавиться от проверки. Если в вашем шаблоне Blade вы поставили скрытый ввод перед своим флажком:

<input type="hidden" name="featuredArticle" value="0" />

<input type="checkbox" name="featuredArticle" value="1" />

Затем 1 будет отправлено, если флажок установлен (поскольку он переопределяет значение скрытого ввода, или 0 отправлен, если флажок не установлен).

Кроме того, старайтесь придерживаться соглашений Laravel, чтобы сделать вашу жизнь проще. Если вы используете snake_case для своих входных имен, то это просто облегчает жизнь, сопоставляя их с именами атрибутов модели и столбцов таблицы. Поэтому используйте featured_article, в вашей модели есть атрибут с тем же именем, который снова сопоставляется со столбцом базы данных с тем же именем. Это позволяет вам делать сокращенные вызовы, такие как create() (согласно примеру с моим контроллером) и update().

Наконец, такие методы, как вычисление времени чтения, безусловно, относятся к вашей модели. Модели представляют что-то в вашем приложении. Отсюда следует, что вы можете делать вещи со своими моделями. Поэтому расчет времени чтения экземпляра модели Article позволяет использовать метод calculateReadingTime() в модели Article.

Немного скучно, но, надеюсь, должны быть некоторые полезные советы для вас выше. Я работаю над проектами Laravel уже около пяти лет и обнаружил, что этот подход и соглашения - это то, что работает лучше всего.

2
Martin Bean 20 Авг 2018 в 13:32

store статья вашего контроллера в порядке, потому что она заполняет ваш экземпляр статьи на основе данных запроса. Он может использовать некоторый рефакторинг, и вы можете инкапсулировать больше логики в свой Article (например, назначать поле slug внутри вашей модели Article при каждом изменении заголовка и т. Д.).

Но строка $article->handleTags($request); является подозрительной, потому что ваша модель никогда не должна работать с запросами - она быстро перенесет код вашей модели с очень специализированными зависимостями, которые вам не нужны (что происходит, когда вы получаете ваши теги из кеша и экземпляра запроса? Что произойдет, если другой тип запроса содержит теги по-другому? и т. д.). Ваша модель не должна знать о запросах или других частях вашего приложения. Ваш контроллер соединяет точки между ними, поэтому убедитесь, что ваш handleTags принимает некоторые базовые абстрактные типы / структуры в качестве параметра (например, массив), и убедитесь, что ваш контроллер принимает и преобразует данные из запроса соответствующим образом перед его подачей к вашей статье.

Что касается вашей calculateReadTime дилеммы, она определенно должна быть внутри вашей модели. Подумайте об этом так: есть ли у вас все, что нужно для расчета времени чтения вашей статьи внутри вашей Article модели? Ответ - да, это свойство объекта статьи, не имеет значения, сохраняете ли вы его в БД или рассчитываете по другим свойствам. Сделай getReadTime метод. Вы не хотите, чтобы контроллер вычислял что-то для вашей модели, потому что он связывает эту логику с конкретным местом в вашем приложении, что плохо (что происходит, когда вам нужно вычислить время чтения статьи в другом контроллере? Другая модель? И скоро).

Убедитесь, что вы читаете о has и is концепциях, касающихся объектно-ориентированного проектирования, это вам очень поможет.

1
d3jn 20 Авг 2018 в 11:59

Я думаю, что вы должны переместить эти назначения в класс обслуживания. Вы также можете создать класс репозитория. Таким образом, это станет вашей структурой кода:

Контроллер -> Сервис -> Репозиторий -> Модель.

Делать это $article = new Article(); плохо. У вас будет время для написания теста для вашего метода хранилища контроллеров.

Я бы посоветовал вам сделать это:

Создайте класс Service, скажем ArticleService.php. Определите метод магазина в нем.

    ArticleService.php

    use Article;

    class ArticleService {

        protected $article;

        public function __construct(Article $article){
            $this->article = $article;
        }

        public function store(array $data){
            $defauultPublished = "draft";
            $IntranetOnly = false;
            $isFeatured = false;

            $isFeatured = ($data['featuredArticle'] == "1" ? true : false);
            $IntranetOnly = ($data['IntranetOnly'] == "1" ? true : false);
            $this->article->title = $data['title'];
            $this->article->slug = str_slug($data['title']);
            $this->article->author = $data['author'];
            $this->article->category = $data['category'];
            $this->article->excerpt = $data['excerpt'];
            $this->article->content = clean($data['content']);
            $this->article->featuredImage = $data['featuredImage'];
            $this->article->featuredVideo = $data['featuredVideo'];
            $this->article->readingTime = $data['reading_time'];
            $this->article->featuredArticle = $isFeatured;
            //Capital letter I? You should be consistent with your naming convention                
            $this->article->IntranetOnly = $IntranetOnly;
            $this->article->published = $defauultPublished;

            if($this->article->save()){
                $this->article->handleTags($request);
                return true;
            }
            return false;
        }
    }

И ваш контроллер теперь становится:

    class ArticleController{

        protected $articleService;

        public function __construct(ArticleService $articleService){
            $this->articleService = $articleService;
        }

        public function store(Request $request){

            //Some Validation Logic
            $readingTime = $this->calculateReadTime($request)
            $data = array_merge(['reading_time' => $readTime], $request->all());
            return $this->articleService->store($request->all());
        }
    }

Я также вижу, что вы не проверяете входящий запрос. Вы всегда должны делать это, потому что вы не можете / никогда не должны доверять своим пользователям всегда предоставлять / вводить правильные данные. Это ваша обязанность заставить их сделать это. например, я, как ваш пользователь, мог бы принять решение ввести свое имя в поле вашей электронной почты. Если вы не подтвердите эти данные, вы получите неправильные данные.

Существует также проблема индивидуального присвоения вашего параметра запроса их соответствующему атрибуту модели. Я решил оставить это так, чтобы не перегружать вас информацией.

Таким образом, просто взгляните на следующие ресурсы для получения дополнительной информации. https://laravel.com/docs/5.1/quickstart-intermediate https://laravel.com/docs/5.6/validation Короче, прочитайте всю документацию Laravel! Удачи!

0
Chukwuemeka Inya 20 Авг 2018 в 13:46
51930067