Skip to content

Instantly share code, notes, and snippets.

@dmitrybubyakin
Last active September 20, 2017 06:35
Show Gist options
  • Save dmitrybubyakin/9a63ec609d1863ae9219b2e2d1ffe98d to your computer and use it in GitHub Desktop.
Save dmitrybubyakin/9a63ec609d1863ae9219b2e2d1ffe98d to your computer and use it in GitHub Desktop.
  1. Обязательно
  1. Создаем laravel проект

composer create-project laravel/laravel news-app

Инициализируем git репозиторий и комитим

git init
git add -A
git commit -m "initial commit"

По мере разработки, не забываем КОМИТИТЬ изменения, придерживаемся таких правил:

  • лучше закомитить 1 измененный файл и описать изменения, чем закомитить 10 файлов с коротеньким сообщением
  • закончили работать с файлом — комитим
  • сообщение в коммите должно соответствовать изменениям в файлах

И не забываем про коменты в коде, они должны быть написаны обсолютно к каждому методу (или переменной класса), которые Вы написали!!!

  1. Создаем БД news_app с кодировкой utf8mb4_general_ci и в файле config/database.php ставим такие вот значения:
// ..
    'mysql' => [
        // ..
        'charset' => 'utf8mb4',
        'collation' => 'utf8mb4_general_ci',
        // ..
    ],
// .. 

И комитим

git add config/database.php
git commit -m "Поправил кодировку с utf8_unicode_ci на utf8mb4_general_ci"

Дальше напоминать не буду, просто не забываем об этом.

  1. Перенесем модель User в папку App\Models
  • в файлах app\Http\Controllers\Auth\RegisterController.php, config/auth.php, database/factories/ModelFactory.php заменим App\User на App\Models\User, в User.php заменим namespace App на App\Models
  • выполним команду composer dump-autoload
  1. Добавим в проект debugbar laravel-tracy, выполним composer require recca0120/laravel-tracy и в файле config/app.php в массив providers в конце допишем:
'providers' => [
    // ..
    Recca0120\LaravelTracy\LaravelTracyServiceProvider::class,
    // ..
];

Затем выполним команду

php artisan vendor:publish --provider="Recca0120\LaravelTracy\LaravelTracyServiceProvider"

и в появившемся файле config/tracy.php отключаем параметр panels.terminal (ставим false), это опасная для безопасности штука и лучше ее не трогать

Про нее можно почитать тут 5.0 и тут 5.4

  1. Создадим модели и миграции для новостей и ролей:
php artisan make:auth
php artisan make:model Models/News -m
php artisan make:model Models/Role -m

Кто не знает, ключ -m создаем миграцию для модели.

Команда php artisan make:auth создает миграции (и views) для модели User, кому интересно, гуглите)

Структура таблиц должна быть примерно такой:

| news
|------
| id
| user_id - автор
| title название статьи
| slug - str_slug(title) (должно быть уникально)
| content - markdown контент новости
| created_at
| updated_at

| roles
|------
| id
| slug - unique

| role_user
|------
| role_id
| user_id

  • str_slug
  • Пример миграции, не копипастим, думаем
  • чтобы не создавать в таблицах поля created_at и updated_at (если они конечно там не нужны), в миграции удаляем $table->timestamps();, а в модели добавим public $timestamps = false;

Так же, добавим сидеры для ролей: php artisan make:seeder RolesTableSeeder, и в появившемся файле (database/seeds/RoleSeeder.php) добавим данные в бд:

// ..
public function run()
{
    $roles = collect(['admin', 'user', 'moderator']);
    $roles->each(function($role) {
        DB::table('roles')->insert([
            'slug' => $role
        ]);
    });
}
// ..

И аналогично сделаем для модели User (UsersTableSeeder), создадим одного пользователя (Посмотрите файл database/factories/ModelFactory.php).

Выполняем

php artisan migrate
php artisan db:seed --class=RolesTableSeeder
php artisan db:seed --class=UsersTableSeeder

Запустим tinker (php artisan tinker), выполним в нем App\Models\Role::all() и нам должно вернуть список наших ролей

  1. Добавим отношения к моделям Role, User и News (тут сами, не трудно же), сделайте так, чтобы работал код (tinker):
$user = App\Models\User::first();
$admin = App\Models\Role::where('slug', 'admin')->first();
$user->roles->count(); //вернет 0
$user->roles()->save($admin);
$user = $user->fresh();
$admin = $admin->fresh();
$admin->users; // тут будет User к которому добавили роль
$user->news()->create([
    'title' => 'Hello world!',
    'content' => '# Hello world content!!!'
]);
App\Models\News::first()->user // тут будет User к которому добавили новость

Чтобы у нас поле slug генерировалось автоматически, в laravel есть такая штука, как mutators, в модели News напишем метод:

public function setTitleAttribute($value) {
    $this->attributes['title'] = $value;
    $this->attributes['slug'] = str_slug($value);
}

и теперь достаточно указать только поле title, slug сгенерируется автоматически.

  1. Добавим в регистрацию поле для выбора роли пользователя, в app\Http\Controllers\Auth\RegisterController.php исправим валидацию и создание модели User:
    // ..
    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:6|confirmed',
            'role' => 'required|exists:roles,id' // добавили валидацию роли
        ]);
    }

    // ..

    protected function create(array $data)
    {
        $user = User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => bcrypt($data['password']),
        ]);
        $user->roles()->sync([$data['role']]); // добавили роль к модели
        return $user;
    }

    // ..

И на странице регистрации не забываем добавить поле в форму, для этого нам, нужно получить все доступные роли и передать их в view.

Для начала, выполним команду php artisan route:list и получим что-то вроде этого:

+--------+----------+------------------------+------------------+------------------------------------------------------------------------+--------------+
| Domain | Method   | URI                    | Name             | Action                                                                 | Middleware   |
+--------+----------+------------------------+------------------+------------------------------------------------------------------------+--------------+
|        | GET|HEAD | /                      |                  | Closure                                                                | web          |
|        | GET|HEAD | api/user               |                  | Closure                                                                | api,auth:api |
|        | GET|HEAD | home                   | home             | App\Http\Controllers\HomeController@index                              | web,auth     |
|        | GET|HEAD | login                  | login            | App\Http\Controllers\Auth\LoginController@showLoginForm                | web,guest    |
|        | POST     | login                  |                  | App\Http\Controllers\Auth\LoginController@login                        | web,guest    |
|        | POST     | logout                 | logout           | App\Http\Controllers\Auth\LoginController@logout                       | web          |
|        | POST     | password/email         | password.email   | App\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail  | web,guest    |
|        | GET|HEAD | password/reset         | password.request | App\Http\Controllers\Auth\ForgotPasswordController@showLinkRequestForm | web,guest    |
|        | POST     | password/reset         |                  | App\Http\Controllers\Auth\ResetPasswordController@reset                | web,guest    |
|        | GET|HEAD | password/reset/{token} | password.reset   | App\Http\Controllers\Auth\ResetPasswordController@showResetForm        | web,guest    |
|        | GET|HEAD | register               | register         | App\Http\Controllers\Auth\RegisterController@showRegistrationForm      | web,guest    |
|        | POST     | register               |                  | App\Http\Controllers\Auth\RegisterController@register                  | web,guest    |
+--------+----------+------------------------+------------------+------------------------------------------------------------------------+--------------+

Это список всех наших маршрутов, нам нужен всего 1, это register, видим что он у нас ссылается на RegisterController@showRegistrationForm, откроем этот файл, но в нем не будет метода showRegistrationForm, потому что он находится в трейте Illuminate\Foundation\Auth\RegistersUsers (лежит в папке vendor) и примерно такой:

// ..
public function showRegistrationForm()
{
    return view('auth.register');
}
// ..

Все что мы сделаем, это переопределим его в RegisterController:

use App\Models\Role;

// ..
public function showRegistrationForm()
{
    $roles = Role::all()->pluck('slug', 'id');
    return view('auth.register', compact('roles'));
}
// ..

И теперь в файле resources/views/auth/register.blade.php добавим поле с ролями:

<!-- email -->

<!-- role -->
<div class="form-group{{ $errors->has('role') ? ' has-error' : '' }}">
   <label for="role" class="col-md-4 control-label">Role</label>
   <div class="col-md-6">
       <select id="role" class="form-control" name="role" required>
           @foreach($roles as $id => $slug)
               <option value="{{ $id }}" {{ (int)$id === (int) old('role') ? 'selected' : '' }}>{{ $slug }}</option>
           @endforeach
       </select>
       @if ($errors->has('role'))
           <span class="help-block">
               <strong>{{ $errors->first('role') }}</strong>
           </span>
       @endif
   </div>
</div>


<!-- password -->

Чтобы у нас не было таких громоздких конструкций как:

@foreach($roles as $id => $slug)
   <option value="{{ $id }}" {{ (int)$id === (int) old('role') ? 'selected' : '' }}>{{ $slug }}</option>
@endforeach

Придумали LaravelCollective, его мы подключим и настроим позже, и при желании можете переписать авторизацию c его помощью.

Теперь у нас полностью готова авторизация.

  1. Добавим новости на сайт.

Выполним команду php artisan make:controller NewsController --model=Models/News, у нас появится файл App\Controllers\NewsController, в нем будут методы:

  • index — отвечает за отображение всех новостей
  • create — показываем форму создания новости
  • store — отвечает за сохранение созданной новости (form.action = 'news/store', form.method = post)
  • show — отвечает за отображение какой-либо новости на сайте
  • edit — показываем форму редактирования новости
  • update — сохраняем новость (form.action = 'news/{news}/update', form.method = post)
  • destroy — отвечает за удаление новости

Откроем файл routes/web.php и добавим:

Route::middleware('auth')->resource('news','NewsController');

Выполним php artisan route:list и увидим, что добавились примерно такие маршруты:

+--------+-----------+------------------------+------------------+------------------------------------------------------------------------+--------------+
| Domain | Method    | URI                    | Name             | Action                                                                 | Middleware   |
+--------+-----------+------------------------+------------------+------------------------------------------------------------------------+--------------+
|                        ------------------------------------------------------------------------------------                                            |
|        | POST      | news                   | news.store       | App\Http\Controllers\NewsController@store                              | web,auth     |
|        | GET|HEAD  | news                   | news.index       | App\Http\Controllers\NewsController@index                              | web,auth     |
|        | GET|HEAD  | news/create            | news.create      | App\Http\Controllers\NewsController@create                             | web,auth     |
|        | PUT|PATCH | news/{news}            | news.update      | App\Http\Controllers\NewsController@update                             | web,auth     |
|        | GET|HEAD  | news/{news}            | news.show        | App\Http\Controllers\NewsController@show                               | web,auth     |
|        | DELETE    | news/{news}            | news.destroy     | App\Http\Controllers\NewsController@destroy                            | web,auth     |
|        | GET|HEAD  | news/{news}/edit       | news.edit        | App\Http\Controllers\NewsController@edit                               | web,auth     |
|                        ------------------------------------------------------------------------------------                                            |
+--------+-----------+------------------------+------------------+------------------------------------------------------------------------+--------------+

  • middleware('auth') — доступ к маршрутам будет только у авторизированных пользователей.

Создайте в папке resources/views папки news, news/forms и внутри news файлы index.blade.php, show.blade.php, forms/create.blade.php, forms/edit.blade.php, forms/form.blade.php. Это все что нам нужно будет для работы.

Самостоятельно подключим Laravel Collective и HTMLPurifier, почитаем про parsedown

В модели News напишем еще два мутатора для поля content, так как это у нас markdown-текст будет, туда могут напихать много плохих вещей, таких как XSS (гуглите):

use Parsedown;
// ..

// удаляем опасный код
public function setContentAttribute($value){
    $this->attributes['content'] = clean($value);
}

// преобразуем markdown в html
public function getMarkdownContentAttribute() {
    return (new Parsedown)->text($this->attributes['content']);
}

// в news.show выводить новость так:  {!! $news->markdownContent !!}

В config/purufier.php отключим опцию AutoFormat.AutoParagraph

И в tinker можно потренироваться:

$news = new App\Models\News(['title' => 'Hello', 'content' => '#Hello from <script>alert("XSS")</script>']);
$news->content; //вернет html

Самостоятельно напишем логику для NewsController:

  • не забываем о валидации, можно сделать по анологии с RegisterController, а можно почитать
  • только модератор может создать или отредактировать новость
  • только админ может новость удалить
  • только зарегистрированные пользователи могут смотреть новости (уже реализовали, middleware('auth'))

Если не сверстали еще нужные странички (что создали выше), верстаем, посмотрите на страницу home и сделайте по анологии (если хотите). Вот и все, потом потренеруйтесь писать markdown посты на своем сайте

Думаю, у Вас будем много коммитов ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment