Laravel: объясняем основные понятия. Часть вторая: «Практика»

    Всем привет! Продолжаем серию авторских публикаций в преддверии старта курса «Framework Laravel». В прошлой статье мы с вами посмотрели на теоретические основы Laravel. Однако теорию любого фреймворка можно изучать достаточно долго и ничего не понять, пока сам не напишешь ничего на практике.



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

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

    Итак, нам потребуется:



    Composer. Для тех, кто в танке, Composer это менеджер пакетов для PHP (да, как npm для JavaScript). Скачать его можно здесь .

    Сам Laravel. Здесь есть несколько самых популярных вариантов: Homestead и Valet (и на самом деле множество других). Однако, последний подходит только для Mac OS. Технически, читатель может воспользоваться любой любимой сборкой, лишь бы она соответствовала этим требованиям.

    И малозаметная деталь, но Node.js нам тоже понадобится для установки npm пакетов.

    Выполняем команду: composer global require laravel/installer для установки Laravel.

    Не забудьте поместить каталог $Home/.composer/vendor/bin (или его эквивалент в вашей ОС) в вашу переменную PATH, чтобы команда Laravel заработала в вашей системе.

    Итак, хороший проект начинается с понятного ТЗ. Давайте попробуем сформулировать основные требования к нашему несложному приложению:



    У пользователя есть возможность загружать фотографии на сайт в созданные им самими альбомы. Он может зарегистрироваться, и может авторизоваться. База данных — MySQL. Фреймворк, который используется для упрощения процесса верстки — Bootstrap, как самый распространённый фреймворк для верстки.

    Первое действие, которое вы совершаете сразу после создания — это подключение базы данных. Если вы пока плохо понимаете, как установить и запустить свою базу данных, воспользуйтесь готовыми desktop-решениями от корпорации Зла: здесь и здесь. Этого поначалу будет достаточно.

    Итак, все готово. Проходим в свою любимую директорию и начинаем проект с

    laravel new gallery 
    

    Далее нужно подключить нашу базу данных. Для этого проходим в MySQL Branch создаем новую базу данных gallery (не забудьте настроить Сharacter Set на utf-8, и Сollation на utf_general_ci для избежания проблем с русским языком).



    Далее проходим в файл .ENV и настраиваем подобное подключение, не забыв поменять переменные на свое имя пользователя и пароль:

    DB_CONNECTION=mysql
    DB_HOST=127.0.0.1
    DB_PORT=3306
    DB_DATABASE=gallery
    DB_USERNAME=pavel
    DB_PASSWORD=
    

    После этого мы сможем сделать миграцию наших баз данных.

    php artisan migrate
    

    Если вы правильно настроили ваше подключение в .ENV, то у вас все должно сработать.

    Как мы с вами договорились на т/з, у нас будет процесс авторизации, регистрации, а также у пользователя будет возможность создавать альбомы и загружать туда фотографии. Для реализации всего нам потребуются соответствующие таблицы в базе данных.

    Создаем таблицу albums

    Итак, с чего начинается бэкэнд? С создания таблицы в нашей базе данных. Эта команда в терминале создаст нам возможность совершить миграцию в базу данных:

    php  artisan  make:migration  CreateAlbumsTable 
     

    Итак, нам нужно определить, какие у нас должны быть колонки в нашей таблице. Это нужно сделать в файле, который создался у вас за счет последней миграции. Он находится в вашем проекте в папке gallery/database/migrations и название у него связано со временем его создания. Его нужно отредактировать следующим образом:

    <?php
    use Illuminate\Database\Migrations\Migration;
    use Illuminate\Database\Schema\Blueprint;
    use Illuminate\Support\Facades\Schema;
    class CreateAlbumsTable extends Migration
    {
        /**
         * Run the migrations.
         *
         * @return void
         */
        public function up()
        {
            Schema::create('albums', function(Blueprint $table)
            {
              $table->increments('id')->unsigned();
              $table->string('name'); //имя нашего альбома
              $table->text('description'); // описание нашего альбома
              $table->string('cover_image'); //картинка на превью
              $table->timestamps(); // дата создания. Необязательно ее показывать, но может быть полезна
            });
          }
     
        /**
         * Reverse the migrations.
         *
         * @return void
         */
        public function down()
        {
            Schema::dropIfExists('albums');
        }
    }
     
    

    Не забудьте сделать

     php artisan migrate
    

    Теперь вкладка вашей базы данных в MySQL Branch должна принять следующий вид:



    Итак, мы создали нашу базу данных, теперь нам нужно связать нашу базу данных с моделью Eloquen, которая и будет взаимодействовать с нашей базой данных.

    Создаем модель Album




    Теперь нам нужно создать модель Eloquent ORM. Если вы не понимаете, как мы так быстро перешли от одного к другому, вам стоит почитать вот это. Воспользуемся для генерации шаблона модели все тем же помощником Artisan:


    php artisan make:model Album

    Далее мы проходим в gallery/app/Album.php и редактируем его:

    <?php
    namespace App;
    use Illuminate\Database\Eloquent\Model;
    class Album extends Model
    {
        protected $table = 'albums';
     
        protected $fillable = array('name','description','cover_image');
     
        public function Photos(){
            return $this->hasMany('App\Image');
        }
    }
    

    Таблица Images

    Здорово! Наша модель альбома готова, теперь можно заняться нашими изображениями. Здесь процесс повторится вновь:

    php  artisan  make:migration  CreateImagesTable 
    

    Идем редактировать нашу миграцию:

    <?php
    use Illuminate\Support\Facades\Schema;
    use Illuminate\Database\Schema\Blueprint;
    use Illuminate\Database\Migrations\Migration;
     
    class CreateImagesTable extends Migration
    {
     
        public function up()
        {
            Schema::create('images', function (Blueprint $table) {
                $table->increments('id');
                $table->integer('album_id')->unsigned();
                $table->string('image');
                $table->string('description');
                $table->foreign('album_id')->references('id')->on('albums')->onDelete('CASCADE')->onUpdate('CASCADE');
                $table->timestamps();
            });
        }
     
        public function down()
        {
            Schema::dropIfExists('images');
        }
    }
    

    И снова делаем миграцию:

    php artisan migrate
    

    Надеюсь, ваша миграция удалась! Однако нам нужно объяснить ещё одну вещь, которую слишком долго было бы расписывать в комментариях: мы использовали ключ foreign, чтобы связать две таблицы Albums и Images. У каждого альбома есть свои изображения, и если вы хотите удалить какой-то альбом, тогда вы наверное полагаете что и фотографии у вас тоже удалятся.

    Модель Image

    Пора создать и модель, которая будет работать с таблицей images. Надеюсь, вы уже догадываетесь что делать:

    php artisan make:model Image

    Шаблон создан, идем в gallery/app/Image.php его редактировать.
    Здесь тоже все должно быть понятно:

    class Image extends Model
    {
        protected $table = 'images'; //сама таблица, с которой мы собираемся связываться
      
        protected $fillable = array('album_id','description','image');
    //свойства
    }
    

    Наша модель Image готова. Теперь нам потребуется контроллер для создания альбомов в нашей базе данных.

    Создание альбома

    Что бы сохранять, показывать и удалять в нашем альбоме, нам нужны некоторые функции в нашем контроллере. Но для начала нужен сам контроллер:

    php  artisan  make:controller  AlbumsController
    

    Заходим в gallery/app/Http/Controllers/AlbumsController.php

    <?php
    namespace App\Http\Controllers;
     
    use Illuminate\Http\Request;
    use Illuminate\Support\MessageBag;
    use Validator;
    use App\Album;
     
    class AlbumsController extends Controller
    {
        public function getList() //получение списка альбомов
      {
          $albums = Album::with('Photos')->get(); 
          return view('index')->with('albums',$albums);
      }
     
      public function getAlbum($id)
      {
          $album = Album::with('Photos')->find($id);
          $albums = Album::with('Photos')->get();
          return view('album', ['album'=>$album, 'albums'=>$albums]);
      }
     
      public function getForm()
      {
          return view('createalbum'); //форма создания альбома
      }
     
      public function postCreate(Request $request)
      {
          $rules = ['name' => 'required', 'cover_image'=>'required|image'];  // нельзя создать альбом без имени и обложки
          $input = ['name' => null];
        //валидатор мы обсудим в следующей части
          $validator = Validator::make($request->all(), $rules);
          if($validator->fails()){
            return redirect()->route('create_album_form')->withErrors($validator)->withInput();
          }
          $file = $request->file('cover_image');
          $random_name = str_random(8);
          $destinationPath = 'albums/';
          $extension = $file->getClientOriginalExtension();
          $filename=$random_name.'_cover.'.$extension;
          $uploadSuccess = $request->file('cover_image')->move($destinationPath, $filename);
          $album = Album::create(array(
            'name' => $request->get('name'),
            'description' => $request->get('description'),
            'cover_image' => $filename,
          ));
     
          return redirect()->route('show_album',['id'=>$album->id]);
      }
     
      public function getDelete($id) // возможность удаления альбома
      {
          $album = Album::find($id);
          $album->delete();
          return Redirect::route('index');
      }
    }
    

    Далее нам предстоит заняться переопределением наших путей. Для тех, кто не знает, роутинг — это настройка путей, которые показываются пользователю в браузере, и действий, которое должно совершать наше приложение. Мы заходим в файл web.php:

    <?php
    //роутинг альбомов
    Route::get('/', array('as' => 'index','uses' => 'AlbumsController@getList'));
    //получение списка альбомов на главной странице
    Route::get('/createalbum', array('as' => 'create_album_form','uses' => 'AlbumsController@getForm'));
    //создание альбома из формы
    Route::post('/createalbum', array('as' => 'create_album','uses' => 'AlbumsController@postCreate'));
    //само создание альбома
    Route::get('/deletealbum/{id}', array('as' => 'delete_album','uses' => 'AlbumsController@getDelete'));
    // удаление альбома
    Route::get('/album/{id}', array('as' => 'show_album','uses' => 'AlbumsController@getAlbum'));
    // удаление альбома
     
    //Эта часть связана с обработкой самих фотографий
    Route::get('/addimage/{id}', array('as' => 'add_image','uses' => 'ImageController@getForm'));
    //добавление изображений в форме
    Route::post('/addimage', array('as' => 'add_image_to_album','uses' => 'ImageController@postAdd'));
    //добавление изображений в сам альбом
    Route::get('/deleteimage/{id}', array('as' => 'delete_image','uses' => 'ImageController@getDelete'));
    //удаление
    Route::post('/moveimage', array('as' => 'move_image', 'uses' => 'ImageController@postMove'));
    

    Здорово. Что бы наше приложение точно могло заработать, перед настройками представления вида нам нужно убедиться, что у нас есть необходимые ссылки на ярлыки (alias) необходимых нам классов в конце файла gallery/config/app.php. Это только технические настройки:

    'aliases' => [
     
            'App' => Illuminate\Support\Facades\App::class,
            'Artisan' => Illuminate\Support\Facades\Artisan::class,
            'Auth' => Illuminate\Support\Facades\Auth::class,
            'Blade' => Illuminate\Support\Facades\Blade::class,
            'Broadcast' => Illuminate\Support\Facades\Broadcast::class,
            'Bus' => Illuminate\Support\Facades\Bus::class,
            'Cache' => Illuminate\Support\Facades\Cache::class,
            'Config' => Illuminate\Support\Facades\Config::class,
            'Cookie' => Illuminate\Support\Facades\Cookie::class,
            'Crypt' => Illuminate\Support\Facades\Crypt::class,
            'DB' => Illuminate\Support\Facades\DB::class,
            'Eloquent' => Illuminate\Database\Eloquent\Model::class,
            'Event' => Illuminate\Support\Facades\Event::class,
            'File' => Illuminate\Support\Facades\File::class,
            'Gate' => Illuminate\Support\Facades\Gate::class,
            'Hash' => Illuminate\Support\Facades\Hash::class,
            'Lang' => Illuminate\Support\Facades\Lang::class,
            'Log' => Illuminate\Support\Facades\Log::class,
            'Mail' => Illuminate\Support\Facades\Mail::class,
            'Notification' => Illuminate\Support\Facades\Notification::class,
            'Password' => Illuminate\Support\Facades\Password::class,
            'Queue' => Illuminate\Support\Facades\Queue::class,
            'Redirect' => Illuminate\Support\Facades\Redirect::class,
            'Redis' => Illuminate\Support\Facades\Redis::class,
            'Request' => Illuminate\Support\Facades\Request::class,
            'Response' => Illuminate\Support\Facades\Response::class,
            'Route' => Illuminate\Support\Facades\Route::class,
            'Schema' => Illuminate\Support\Facades\Schema::class,
            'Session' => Illuminate\Support\Facades\Session::class,
            'Storage' => Illuminate\Support\Facades\Storage::class,
            'URL' => Illuminate\Support\Facades\URL::class,
            'Validator' => Illuminate\Support\Facades\Validator::class,
            'View' => Illuminate\Support\Facades\View::class,
            'Form' => Collective\Html\FormFacade::class,
            'Html' => Collective\Html\HtmlFacade::class,
     
        ],
     
    //потому что класса FORM у вас скорее всего нет)
    
    Отлично! Теперь нам нужно определить наши представления! Проходим в папку gallery/resources/views. Можете удалить welcome.blade.php, можете оставить, неважно. Создаем index.blade.php и подпапку includes, чтобы сложить туда повторяющиеся части view. Итак, наш index.blade.php должен принять следующий вид:

    @include('includes.header')
      <body>
      @include('includes.nav')
          <div class="container">
            <div class="starter-template">
            <div class="row">
              @foreach($albums as $album)
                <div class="col-lg-3">
                  <div class="thumbnail" style="min-height: 514px;">
                    <img alt="{{$album->name}}" src="/albums/{{$album->cover_image}}">
                    <div class="caption">
                      <h3>{{$album->name}}</h3>
                      <p>{{$album->description}}</p>
                      <p>{{count($album->Photos)}} image(s).</p>
                      <p>Created date:  {{ date("d F Y",strtotime($album->created_at)) }} at {{date("g:ha",strtotime($album->created_at)) }}</p>
                      <p><a href="{{route('show_album', ['id'=>$album->id])}}" class="btn btn-big btn-default">Показать галерею</a></p>
                    </div>
                  </div>
                </div>
              @endforeach
            </div>
          </div><!-- /.container -->
        </div>
      </body>
    </html>
    

    Содержимое includes/header.blade.php:

    <!doctype html>
    <html lang="ru">
      <head>
        <meta charset="UTF-8">
        <title>Мои альбомы</title>
        <link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0-rc1/css/bootstrap.min.css" rel="stylesheet">
        <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0-rc1/js/bootstrap.min.js"></script>
        <style>
          body {
            padding-top: 50px;
          }
          .starter-template {
            padding: 40px 15px;
          text-align: center;
          }
        </style>
      </head>
    
    Содержимое includes/nav.blade.php:

    <div class="navbar navbar-inverse navbar-fixed-top">
          <div class="container">
          <button type="button" class="navbar-toggle"data-toggle="collapse" data-target=".nav-collapse">
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <a class="navbar-brand" href="/">Мои альбомы</a>
          <div class="nav-collapse collapse">
            <ul class="nav navbar-nav">
              <li><a href="{{URL::route('create_album_form')}}">Создание нового альбома</a></li>
            </ul>
          </div><!--/.nav-collapse -->
        </div>
        </div>
    

    На этом на сегодня все! Это, понятное дело, только первая часть нашего приложения. Всем спасибо за внимание, в следующей части дополним наши view и обсудим вопрос валидизации изображений.

    Полезные ссылки:

    Документация на русском языке
    Полезный гайд на английском, который лучше, чем в документации объясняет как работает наследование и расширение Blade-шаблонов
    Множество полезной информации и практических примеров создания приложений
    OTUS. Онлайн-образование
    638,98
    Цифровые навыки от ведущих экспертов
    Поделиться публикацией

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

      +2
      php artisan migrate
      а не
      php artisan migration

      php artisan make:model ModelName -mrc
      и будет вам и миграции и контроллер
      Роуты жуть, почему не ресурсы?
      И в целом материал какой-то устаревший, например:
      <li><a href="{{URL::route('create_album_form')}}">Создание нового альбома</a></li>
      сейчас просто
      <li><a href="{{route('create_album_form')}}">Создание нового альбома</a></li>
        +1
        php artisan make:model ModelName -mrc

        Даже так:
        php artisan make:model ModelName -a


        А почему Bootstrap 3? Уже давно 4.3

        Судя по примерам кода автор не до конца знает фреймворк, пишет какой то самопал.
        +2
        Статья о том, как не нужно писать на Laravel, да и вообще на PHP

        В миграции стоило бы добавить nullable
        $table->text('description'); // описание нашего альбома

        Потому что при создании альбома поле description не обязательное по валидатору и этот код вызовет ошибку при пустом значении description
        $album = Album::create(array(
        'name' => $request->get('name'),
        'description' => $request->get('description'),
        'cover_image' => $filename,
        ));


        Про CodeStyle, похоже, никто не слышал
        Про CRUD через php artisan make:resource и соответствующие роуты написали выше. И еще куча всего

        Статья пойдет как для ну очень новичка, ИМХО

        UPD
        Зачем во вьюхах @include('includes.header')?
        Можно же наследовать,
        @extends('site.layouts.main')
        @section('content')
        @endsection
          0
          Данная статья действительно рассчитана на новичков, все верно
            +3

            Тогда стоит сразу учить хорошему

            0
            Для новичков больше вреда чем полезности. Навалено в кучу ИМХО автора.
            Начиная работу с Ларавелем перерыл кучу подобных мануалов. Пока тим лид не обьяснил на пальцах структуру где, что лежит и за что отвечает тот или иной кусок кода — в голове была жуткая каша.
            0
            Нарисовать сову очень просто!
              0

              Оно самое. Практика без теории новичков не научит ничему

              0
              Собираюсь к вам на курс, а тут такое… Я хотел-бы научиться так не делать.
                0
                Статья никак не связана с программой курса. Она написана автором не имеющим отношения к процессу обучения.
                0
                Благодарю всех за комментарии. Все замечания переданы автору, для того, чтобы в последующих публикациях такого не повторялось.
                  0
                  php  artisan  make:controller  AlbumsController -r

                  Что бы такого ужаса не было в именах функций

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

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