Создаем todo для удаленной команды на Laravel

  • Tutorial
Салют, хабровчане. Следующая статья была написана одним из наших постоянных читателей и определенно не претендует на звание хардкорного материала, но при этом вполне может послужить туториалом для новичка. Ждем в комментариях ваше мнение по статье, а за более хардкорными знаниями приглашаем на наш курс «Framework Laravel».




Всем привет! Сегодня, в столь «удаленное» для всех время работы, я бы хотел разобрать создание несложной todo, в которой можно создавать свои задачи. Звучит как то, что написано в официальной документации Laravel, и так оно и есть — я использую их todo в качестве базовой основы, немного его трансформирую, а основная часть моего рассказа будет про то, как же создавать роль администратора, и создать очередной нелепый клон Trello.

Создаем основание




Как я уже сказал, за основу мы возьмем следующий гайд. Он очень простой и больше концентрируется на работе с миграциями и с роутингом, чем на программировании контроллеров и моделей. К большому сожалению, его не обновляли с версии 5.1 и, если вы только начинаете программировать в Laravel на последней 7 версии (только 3 марта вышла, свеженькая)), роутинг вам придется писать не в app/Http/routes.php, а в routes/web.php (это правда уже с 6 версии поменялось), и в общем на этом вся разница заканчивается. В итоге, у вас должно получится что-то такое:



Так же я поменял фреймворк для верстки с bootstrap на bulma. Во-первых, мне он больше нравится, а вот вторых у меня уже была кодовая база blade шаблонов регистраций (хотя, с другой стороны, нарастить страницы регистраций и авторизаций очень просто, я покажу дальше, как).

Эту версию проекта можно скачать здесь в ветке мастер (а в ветке new вы можете скачать готовое приложение). Активировать вы её сможете с помощью следующих команд, при условии, что у вас уже установлен composer и laravel:

composer install
// создаете файл .env, копируете в него .env example, настраиваетесь под вашу базу данных
php artisan key:generate //устанавливаете ключ безопасности
php artisan serve //запускаете приложение

Создаем авторизацию и регистрацию

Авторизации «из коробки» поменялись с 6 версии. Теперь для быстрого создания нужно поставить пакет, который может вам быстро сгенерировать контроллеры и blade — шаблоны:

composer install laravel/ui
php artisan ui vue --auth
 

В вашем проекте должно появиться несколько новых файлов:

в папке resourses появится подпапка auth, в которой будут файлы blade шаблонов, посвященные авторизации, паролям. Изначально blade-шаблоны сверстаны в bootstrap, однако я их немного трансформировал в bulma, чтобы соблюсти общность css-фрейворка в проекте. Однако, чтобы удобно было пользоваться веб-сайтом, нам потребуется панель навигации. В resources я создал папку includes, в которой разместил вспомогательные файлы — header и nav. Чтобы не занимать много места, скажу, что в header у меня была только head с подключением bulma, а в nav.blade.php было следующее:

<nav class="navbar has-background-black-ter" role="navigation" aria-label="main navigation">
  <div class="navbar-brand">
    <a class="navbar-item has-text-white is-size-4" href="/">
      TODO
    </a>
    <a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false">
      <span aria-hidden="true"></span>
      <span aria-hidden="true"></span>
      <span aria-hidden="true"></span>
    </a>
  </div>
  <div class="navbar-menu">
    <div class="navbar-start">
      @if (Auth::check())
      <button type="button" class="button is-primary">
        <!-- если пользователь зарегистрирован, тогда ->отобразить его ник -->
        {{{ Auth::user()->name}}}
      </button>
      @else
      <!-- в другом случае -->
      <a class="navbar-item has-text-danger" href="{{route('register')}}">Регистрация</a>
      <a class="navbar-item has-text-danger" href="{{route('login')}}">Войти</a>
      @endif
      @if (Auth::check())
      <!-- если зарегистрирован, тогда нам нужно показать как выйти -->
      <!-- это можно сделать через форму -->

      <a class="navbar-item has-text-danger" href="{{url('/logout') }}" onclick="event.preventDefault();
                  document.getElementById('logout-form').submit();">
        Выйти </a>
      <form id="logout-form" action="{{ url('/logout') }}" method="POST" style="display: none;">
        {{ csrf_field() }}
      </form>

      @endif
      </ul>
    </div>

    <div class="navbar-end">
      <!-- navbar items -->
    </div>
  </div>
</nav>
 

Начинаем создавать нашу архитектуру


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

Окей, значит у нас будет две группы юзеров и условимся, что увидеть нашу доску задач смогут только авторизованные юзеры, которым дали доступ, а добавлять задачи — некоторые суперадмины. Давайте приступим.

Для небольшого упрощения я решил разбить приложение на две страницы: одну, на которой у нас будет только задачи без возможности редактирования, и вторую, которая будет доступна только суперадмину. Доступную команде страницу я решил назвать welcome.blade.php:

@include('includes.header')

<body>
    @include('includes.nav')
    <div class="columns is-centered">
        <div class="column is-half">
            <div class="panel">
                <div class="panel-heading">
                    Текущие задачи
                </div>

                <div class="panel-body">
                    @foreach ($tasks as $task)
                    <a class="panel-block">
                        <button class="button is-rounded">
                            <span>{{ $task->name }}</span>
                        </button>
                        @endforeach
                </div>
            </div>
        </div>
    </div>
<body>
</html>

Эта страница выводит только список задач. Для ее отображения я добавлю новый путь в web.php:

Route::get('/', function () {
    return view('welcome', [
        'tasks' => Task::orderBy('created_at', 'asc')->get(),
    ]);
});

Шаблон, в котором у нас тот же список, но с возможностью редактирования и добавления, называется task.blade.php. Он слишком велик, поэтому я размещу его под спойлером:

task.blade.php
@extends('layouts.app')

@section('content')
<div class="columns is-centered">
    <div class="column is-half">
        <div class="panel">
            <div class="panel-heading">
                Новое дело
            </div>
            <div class="panel-block">
                @include('common.errors')
                <!-- Форма для создания тасков -->
                <form action="{{ url('task')}}" method="POST">
                    {{ csrf_field() }}

                    <div class="field">
                        <label for="task-name" class="label is-medium">Дело</label>
                        <input type=" text" name="name" id="task-name" class="input is-medium"
                            value="{{ old('task') }}">
                    </div>

                    <div class="field">
                        <button type="submit" class="button is-success">
                            <span class="icon">
                                <i class="fa fa-btn fa-plus">
                            </span></i>
                            <span>Добавить дело</span>
                        </button>
                    </div>
                </form>
            </div>
        </div>

        <!-- Current Tasks -->
        @if (count($tasks) > 0)
        <div class="panel">
            <div class="panel-heading">
                Текущие задачи
            </div>

            <div class="panel-body">
                {{-- <table class="table table-striped task-table">
                    <thead>
                        <th>Дело</th>
                        <th> </th>
                    </thead>
                    <tbody> --}}
                @foreach ($tasks as $task)
                <a class="panel-block">
                    <button class="button is-rounded">
                    <span>{{ $task->name }}</span>
                </button>
                    <form action="{{ url('task/'.$task->id) }}" method="POST">
                        {{ csrf_field() }}
                        {{ method_field('DELETE') }}
                        <button type="submit" class="button is-danger">
                            <span class="icon is-small"> <i class="fa fa-btn fa-trash"></i></span>
                            <span> Удалить </span>
                        </button>
                    </form>
                </a>
                @endforeach
            </div>
        </div>
        @endif
    </div>
</div>
@endsection



И этот шаблон, как и оригинальном туториале, расширяется в app/layouts.blade.php:

@include('includes.header')

<body>
    @include('includes.nav')

    @yield('content')

</body>

</html>

Отлично! С внешним видом мы определились, теперь можно переходить к роутингу и созданию наших middlewares.

Для начала зайдем в модель Users и добавим новые типы пользователей:

const ADMIN_TYPE = 'admin';
const TEAM_TYPE = 'team';
#и добавляем в самый конец класса функцию проверки:

public function isAdmin(){        
    return $this->type === self::ADMIN_TYPE;
    // требуем доложить тип пользователя    
}

Далее заходим в последнюю миграцию таблицы users (папка database/migrations) и добавляем колонку с нашим новым типом данных:

$table->string('type')->default('team');
// пока предполагаем, что кроме команды у нас сервисе больше никого нету

Дальше проходим в app/Http/Controllers/Auth/RegisterController.php и немного редактируем функцию create (она тоже в версии 5 выглядела немного по другому):

protected function create(array $data)
    {
        return User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
            'type' => User::TEAM_TYPE,  
        ]);
    }


Окей, дальше нам нужно создать нашу middleware для админа. Проще всего это осуществить с помощью следующей команды:

php artisan make:middleware IsAdmin

Надеюсь, у вас все получилось. В папке HTTP/middleware у вас должен был появится isAdmin.php, в которой функцию handle нужно отредактировать следующим образом:

public function handle($request, Closure $next)
    {
        if(auth()->user()->isAdmin()) {
            return $next($request);
        // если пользовать админа, пропускаем. Если нет - тогда отправляем домой
    }
        return redirect('/');
    }

Дальше нам нужно зарегистрировать наш middleware в app/HTTP/Kernel, чтобы им можно было воспользоваться:

protected $routeMiddleware = [
    #множество других мидлевеиров, не так важных для этой истории
        is_admin' => \App\Http\Middleware\IsAdmin::class,
];

Трансформируем путь dash в web.php, который должен иметь возможность видеть только суперадмин:

Route::get('/dash', function () {
    return view('tasks', [
        'tasks' => Task::orderBy('created_at', 'asc')->get(),
    ]); 
})
->middleware('is_admin')    
->name('admin');

# не забываем добавить авторизацию и возможность разлогироваться

Auth::routes();
Route::post('/logout', 'Auth\LoginController@logout')->name('logout');

Осталось создать кастомный контролер для нашего администратора:

php artisan make:controller AdminController

Редактируем:

public function __construct()
    {
        $this->middleware('auth');
    }
    public function admin()
    {
        return view('admin');
    }
 

Чтобы у вас все с регистрацией заработало конкретно, нужно не забыть поправить редирект в RouteServiceProvider.php в app/HTTP/Providers, потому он изначально рассчитан на шаблон home:

public const HOME = '/';

Теперь администратор у нас может появится только с помощью нашего помощника в терминале tinker:

php artisan tinker
use App\User;
User::where('email', 'admin@mail.com')->update(['type' => 'admin']);
//даем нашем пользователю права админа

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

Возможно, у вас есть корпоративная почта, и тогда вы сможете ограничивать доступ в команду с помощью регулярок в валидизации почты. Или вы сможете добавить проверку на членство в команде точно так же, как админа, проделав схожие манипуляции. По традиции, приведу несколько полезный ссылок, для тех, кто только собирается начать осваивать Laravel:

Чуть более подробная статья, чем в документации, о миграциях в Laravel
Неплохая статья на Medium про роли в Laravel
Что у нас свеженького в Laravel 7



Разворот приложений и непрерывный деплой «без боли» с помощью Forge/Envoyer.


OTUS. Онлайн-образование
Цифровые навыки от ведущих экспертов

Комментарии 7

    0
    Я до конца ждал когда вы отрефакторите роут!

    Вы позиционируете себе как онлайн образование и при этом в роутах вы мало того, что дергаете вьюхи, так еще и тащите данные с базы! Я понимаю такой уровень статей в песочнице от тех кто хочет получить инвайт, но от компании которая пытается учить людей! Увы (

    В очередной раз по ларке у вас провал!
      –1
      Прочитайте пожалуйста заголовок. В нем написано о том, что статья написана нашим читателем и может претендовать максимум на звание туториала для начинающих
        0
        Читал, но публиковать такое я бы не стал. Тут явные ошибки работы с ларкой, кто-то же может посмотреть на это и начать так делать! Это же не личный бложик на котором можно писать что угодно! Эта статья на хабре от автора, который предлагает курсы!
          +1
          Частично согласен с аргументами Sky4eg

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

          Понятно, что статья написана начинающим, но я бы хотя бы подкорректировал её под минимальный стандарт качества, как кода, так и разметки хабра.
        0
        Костыли с ролями, когда тут прекрасно подойдут Policy из коробки. Где валидация в моделях?
          +1
          роутинг вам придется писать не в app/Http/routes.php, а в routes/web.php (это правда уже с 6 версии поменялось)

          Это в 5.6 уже точно было
            0
            В 5.3 это сделали

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое