1. Czym jest LARAVEL

LARAVEL to jeden z wielu frameworków, który może w znacznym stopniu ułatwić pracę programisty aplikacji lub strony internetowej dzięki temu, że posiada implementację rozwiązań wielu typowych problemów związanych z typowym środowiskiem internetowym.

2. Instalacja środowiska

Do pracy będzie potrzebne kilka elementów: serwer WWW obsługujący technologię PHP, serwer bazy danych MySQL, silnik COMPOSER, edytor podświetlający składnię i ułatwiający nawigację pomiędzy plikami, dostęp do internetu.

Proponuję pobranie i instalację:

3. Utworzenie projektu

Korzystając z wiersza poleceń utwórz projekt strony.


		cd c:\xampp\htdocs
composer create-project --prefer-dist laravel/laravel laravel.cms

W celu skonfigurowania środowiska konieczne będzie utworzenie bazy danych. W tym celu można użyć dostępnego w pakiecie XAMPP narzędzia PhpMyAdmin. Warto również utworzyć nowego użytkownika i przypisać do niego utworzoną bazę danych.


		CREATE DATABASE cms
		DEFAULT CHARACTER SET utf8
		COLLATE utf8_polish_ci;
		
		CREATE USER 'laravel-user'@'%'
		IDENTIFIED VIA mysql_native_password
		USING 'laravel-password';
		
		GRANT USAGE ON *.* TO 'laravel-user'@'%' REQUIRE NONE;
		
		GRANT ALL PRIVILEGES ON `cms`.* TO 'laravel-user'@'%';
		

Po dodaniu bazy trzeba skonfigurować ją we frameworku:
plik .env

Projekt jest gotowy do pierwszego uruchomienia. Można do tego wykorzystać przygotowany wcześniej serwer Apache lub wbudowany miniserwer:


		cd c:\xampp\htdocs\laravel.cms
		php artisan serve
		

W wyniku otrzymamy domyślną stronę projektu Laravel.
http://127.0.0.1:8000/

Polecenie artisan ma wiele innych ciekawych zastosowań. Warto się z nimi pobieżnie zapoznać:


		php artisan list
		

Jeżeli chcemy wygenerować wirtualną domenę dla projektu możemy to zrobić dodając VirtualHost do serwera Apache.


		<VirtualHost *:80>
		 ServerName   stronka.pl
		 ServerAlias  www.stronka.pl
		 ServerAdmin  stronka@poczta.com
		 DocumentRoot /var/www/html/stronka/public
		#DocumentRoot c:/xampp/htdocs/stronka/public
		
		 ErrorLog     ${APACHE_LOG_DIR}/error.log
		 CustomLog    ${APACHE_LOG_DIR}/access.log combined
		</VirtualHost>
		

Domena musi również zostać dodana do pliku z lokalnymi nazwami hostów - /etc/hosts lub C:\Windows\System32\drivers\etc\hosts


		$ sudo echo 127.0.0.1 stronka.pl >> /etc/hosts
		$ sudo echo 127.0.0.1 www.stronka.pl >> /etc/hosts
		$ sudo service apache2 restart
		

4. Kontroler, czyli obsługa żądań


		cd \xampp\htdocs\laravel.cms
		
		php artisan make:controller PagesController -resource
		

app\Http\Controllers\PagesController


	    public function index() {
		  echo "To jest INDEX() w pliku ", __FILE__;
		}
		

Co i kiedy się uruchamia? google.pl/search?q=laravel+controller

app\Http\Controllers\PagesController


	    public function index() {
		  return view('pages.index');
		}
		

resources\views\layout.blade


		<!DOCTYPE html>
		<html lang="pl">
		<head>
		  <title>Tytuł strony</title>
		  <meta charset="UTF-8"/>
		  <!-- Latest compiled and minified CSS -->
		  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/css/bootstrap.min.css"/>
		  <!-- jQuery library -->
		  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
		  <!-- Latest compiled JavaScript -->
		  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/js/bootstrap.min.js"></script>
		</head>
		<body>
		  <div class="container">
		    <div class="content">
		      @yield('content')
		    </div>
		  </div>
		</body>
		</html>
		

resources\views\pages\index.blade


		@extends('layout')
		
		@section('content')
		  treść strony pages/index
		@endsection
		

5. Autoryzacja dostępu


		php artisan make:auth
		
http://127.0.0.1/projekt/public/login

6. Baza danych


		php artisan make:migration pages
		

database\migrations\2018_03_03_112500_pages


		public function up() {
		  Schema::create('pages', function(Blueprint $table) {
		    $table->increments('id');
		    $table->string('title')->unique();
		    $table->text('content');
		    $table->timestamps();
		  });
		}
		
		public function down() {
		  Schema::drop('pages');
		}
		

		php artisan make:seeder UsersTableSeeder
		
		php artisan make:model Pages
		
		php artisan make:seeder PagesTableSeeder
		

app\Http\Controllers\PagesController


	    public function index() {
		 # //use App\Pages; //na początku pliku
		 # $pages = Pages::all();
		 # $pages = \App\Pages::all(); //nie wymaga użycia "use..."
		  $pages = \App\Pages::paginate(10);
		  return view('pages.index', compact('pages'));
		}
		

resources\views\pages\index.blade


		<table>
		 <tr>
		    <th>ID</th>
		    <th>TITLE</th>
		    <th>OPTIONS</th>
		  </tr>

		@foreach ($pages as $page)
		 <tr>
		    <td>{{ $page->id    }}</td>
		    <td>{{ $page->title }}</td>
		    <td>
		      <a class="btn btn-info"   href="#">Edit</a>
		      <a class="btn btn-danger" href="#">Delete</a>
		    </td>
		  </tr>
		@endforeach

		</table>
		
		{{ $pages->links() }}
		

database\seeds\DatabaseSeeder


		public function run() {
		  $this->call(UsersTableSeeder::class);
		}
		

database\seeds\UsersTableSeeder


		use App\User;
		public function run() {
		  $user = new User();
		  $user->name = 'user';
		  $user->email = 'user@mail.com';
		  $user->password = bcrypt('haslo');
		  $user->save();
		}
		

database\seeds\PagesTableSeeder


		use App\Pages;
		public function run() {
		  $faker = Faker\Factory::create();
		  for ($i = 0; $i < 10; $i++) {
		    $pages = new Pages();
		    $pages->title = 'page_'.$i.' '.$faker->company();
		    $page->content = $faker->text(200);
		    $pages->save();
		  }
		}
		

		rem php artisan migrate:rollback
		
		php artisan migrate
		
		php artisan db:seed
		

		PDOException::("SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes")
		

app\Providers\AppServiceProvider


		use Illuminate\Support\Facades\Schema;
		
		public function boot() {
          Schema::defaultStringLength(191);
		}
		

7. Access Controll List (ACL), middleware i relacje bazodanowe


		php artisan make:model Roles -m

		php artisan make:seed RolesTableSeeder
		

database\migrations\2018_03_03_112500_create_roles


		public function up() {
		  Schema::create('roles', function(Blueprint $table) {
		    $table->increments('id');
		    $table->string('name');
		    $table->timestamps();
		  });
		}
		

database\seeds\RolesTableSeeder


		public function run() {
		  $role = new \App\Roles();
		  $role->name = 'Admin';
		  $role->save();

		  $role = new \App\Roles();
		  $role->name = 'Moderator';
		  $role->save();

		  $role = new \App\Roles();
		  $role->name = 'User';
		  $role->save();
		}
		

		php artisan migrate

		php artisan db:seed --class=RolesTableSeeder
		

		php artisan make:model RolesHasUsers -m
		

database\migrations\2018_03_03_112500_create_roles_has_users


		public function up() {
		  Schema::create('roles_has_users', function(Blueprint $table) {
		    $table->increments('id');
		    $table->integer('users_id')->unsigned();
		    $table->integer('roles_id')->unsigned();
		    $table->timestamps();
		  });

		  Schema::table('roles_has_users', function(Blueprint $table) {
		    $table->foreign('users_id')->references('id')->on('users');
		  });

		  Schema::table('roles_has_users', function(Blueprint $table) {
		    $table->foreign('roles_id')->references('id')->on('roles');
		  });
		}
		

		php artisan migrate
		

App\User


		public function roles() {//ta sama nazwa funkcji, co nazwa tabeli
		 return $this->belongsToMany(Roles::class, 'roles_has_users', 'users_id', 'roles_id')->withTimestamps();
		}
		

database\seeds\UsersTableSeeder


		use App\Roles;

		public function run() {
		 ...
		 $user->roles()->attach(1);
		 ...
		 $role = Roles::where('name', 'Admin')->first();
		 $user->roles()->attach($role);
		 ...
		}
		

database\seeds\DatabaseSeeder


		public function run() {
		  $this->call(RolesTableSeeder::class);//przed UsersTableSeeder, bo są zależne
		  $this->call(UsersTableSeeder::class);
		  $this->call(PagesTableSeeder::class);
		}
		

		php artisan migrate:refresh

		php artisan db:seed

		php artisan make:middleware CheckRoles
		

App\Http\Kernel


		$protected $routeMiddleware = [
		 ...
		 'roles' => \App\Http\Middleware\CheckRoles::class
		];
		

routes\web


		#Route::resource('pages', 'PagesController');
		Route::group([
		  'middleware' => 'roles',
		  'roles' => 'Admin',
		  //'roles' => ['Admin', 'Moderator'],
		], function() {
		  Route::resource('pages', 'PagesController');
		});
		

App\Http\Middleware\CheckRoles


		public function handle($request, Closure $next) {
		  echo 'xxx MiddleWare CheckRoles się uruchomił';

		  return $next($request);
		}
		

App\Http\Controllers\PagesController


		public function __construct() {
		  //$this->middleware('auth');
		}
		

App\Http\Middleware\CheckRoles


		public function handle($request, Closure $next) {
		  if ($request->user() === null) {
		    return redirect('/login');
		  }

		  $actions = $request->route()->getAction();
		    # zwraca przykładowo: array(
		    #   'middleware' => array('web','roles'),
		    #   'roles' => array('Admin', 'Moderator'),
		    #   'as' => 'pages.index',
		    #   'uses' => 'App\Http\Controllers\PagesController@index',
		    #   'controller' => 'App\Http\Controllers\PagesController@index',
		    #   'namespace' => 'App\Http\Controllers',
		    #   'prefix' => null,
		    #   'where' => array(),
		    # )

		  if (!isset($actions['roles']) ||
		      $request->user()->hasAnyRole($actions['roles'])) {
		    return $next($request);
		  }

		  return redirect('/login');
		}
		

App\User


		# public function hasRole($role) {
		#  return $this->roles()->where('name', $role)->first();
		# }

		public function hasAnyRole($roles) {
		  $roles = (array) $roles;
		  foreach ($roles as $role) {
		    if ($this->roles()->where('name', $role)->first()) {
		      return true;
		    }
		  }
		  return false;
		}
		

Logout (testy na innych userach)

8. Debugowanie


		composer require barryvdh/laravel-debugbar
		

config/app


		Barryvdh\Debugbar\ServiceProvider::class,//dodajemy na końcu listy
		...
		'Debugbar' => Barryvdh\Debugbar\Facade::class,//dodajemy na końcu pliku
		...
		

9. Formularze i walidacja danych

routes\web


		#Route::resource('pages', 'PagesController');
		Route::group([
		  'middleware' => 'roles',
		  'roles' => 'Admin',
		  //'roles' => ['Admin', 'Moderator'],
		], function() {
		  //Route::resource('pages', 'PagesController');
		  Route::get('pages', [
		    'uses' => 'PagesController@index',
		    'as'   => 'pages.index',
		  ]);
		  Route::get('pages/create', [
		    'uses' => 'PagesController@create',
		    'as'   => 'pages.create',
		  ]);
		  Route::post('pages/store', [
		    'uses' => 'PagesController@store',
		    'as'   => 'pages.store',
		  ]);
		});
		

		composer require "laravelcollective/html":"^5.3.0"
		

config/app


		Collective\Html\HtmlServiceProvider::class,//dodajemy na końcu listy
		...
		'Form' => Collective\Html\FormFacade::class,//dodajemy na końcu pliku
		'Html' => Collective\Html\HtmlFacade::class,
		...
		

Dokumentacja Laravel Collective

app\Http\Controllers\PagesController


	    public function create() {
		  return view('pages.create');
		}
	    public function store(Request $request) {
		  //@todo
		  return view('pages.create');
		}
		

resources\views\pages\create.blade (Nowy plik)


	    @extends('layout')
		
		# @section('content')
		#   <form>
		#     <input name="title"...> //nazwa pola zgodna z nazwą kolumny w bazie danych
		#   </form>
		# @endsection
		
		@section('content')
		  {!! Form::open(['route' => 'pages.store']) !!}
		
		  @if ($errors->any())
		    <ul class="alert alert-danger">
		      @foreach ($errors->all() as $error)
		        <li>{{ $error }}</li>
		      @endforeach
		    </ul>
		  @endif
		
		    <div class="form-group">
		      {!! Form::label('title', 'Tytuł:') !!}
		      {!! Form::text('title', null, ['class' => 'form-control']) !!}
		    </div>
		    <div class="form-group">
		      {!! Form::label('content', 'Treść:') !!}
		      {!! Form::textarea('content', null, ['class' => 'form-control']) !!}
		    </div>
		    <div class="form-group">
		      {!! Form::submit('Zapisz', ['class' => 'btn btn-primary']) !!}
		      {!! link_to(URL::previous(), 'Powrót', ['class' => 'btn btn-default']) !!}
		    </div>
		  {!! Form::close() !!}
		@endsection
		

app\Http\Controllers\PagesController


	    public function store(Request $request) {
		  $title = $request->input('title');
		  $content = $request->input('content');
		  return view('pages.create');
		}
		

		php artisan make:request PagesRequest
		

App\Http\Requests\PagesRequest


		public function authorize() {
		 # return false;
		  return true;
		}

		public function rules() {
		 return [
		    'title'   => 'required|max:255',
		    'content' => 'required',
		 ];
		}
		
		//odblokować po przetestowaniu walidacji bez metody messages()
		# public function messages() {
		#   return [
		#     'title.required'   => 'TYTUŁ jest wymagany',
		#     'content.required' => 'TREŚĆ jest wymagana',
		#   ];
		}
		

Więcej o walidacji formularzy

app\Http\Controllers\PagesController


		use App\Http\Request\PagesRequest;
		/*
		 * Store a newly created resource in storage.
		 *
		 * @param  \Illuminate\Http\Request $request
		 * @param  PagesRequest $request
		 * @return \Illuminate\Http\Response
		 */
	    public function store(PagesRequest Request $request) {
		  # $title = $request->input('title');
		  # $content = $request->input('content');
		  # return view('pages.create');
		  Pages::create($request->all());
		  return redirect()->route('pages.index');
		}
		...
	    public function update(PagesRequest Request $request, Pages $page) {
			$page->update($request->all());
			return redirect()->route('pages.index');
		}
		

App\Pages


		class Pages extends Model {
		  //tylko wskazane pola formularz będą tworzyły rekord w Pages::create($request->all())
		  protected $fillable = [ 'title', 'content' ];
		}
		

App\Http\Controllers\PagesController


		public function index() {
		  # $pages = Pages->paginate(10);
		  $pages = Pages::orderBy('id', 'DESC')->paginate(10);
		  return view('pages.index', compact('pages'));
		}
		

resources\views\pages\index.blade


		...
		@section('content')
		<a class="btn btn-primary" href="{{route('pages.create')}}">Dodaj stronę</a>
		...
		

routes\web


		...
		  Route::get('pages/edit/{page}', [
		    'uses' => 'PagesController@edit',
		    'as'   => 'pages.edit',
		  ]);

		  Route::put('pages/{page}', [
		    'uses' => 'PagesController@update',
		    'as'   => 'pages.update',
		  ]);
		...
		

App\Controllers\PagesController


		/**
		 * Show the form for editing the specified resource.
		 *
		 * param int $id
		 * @param \App\Pages $page
		 * @return \Illuminate\Http\Response
		 */
		public function edit(Pages $page $id) {
		 return view('pages.edit', compact('page'));
		}
		

App\resources\views\pages\edit.blade (zmieniona kopia create.blade)


		...
		  {!! Form::model($page, ['route' => ['pages.update', $page], 'method' => 'PUT']) !!}
		
		  @if ($errors->any())
		    <ul class="alert alert-danger">
		      @foreach ($errors->all() as $error)
		        
  • {{ $error }}
  •       @endforeach     </ul>   @endif     <div class="form-group">       {!! Form::label('title', 'Tytuł:') !!}       {!! Form::text('title', $page->title null, ['class' => 'form-control']) !!}     </div>     <div class="form-group">       {!! Form::label('content', 'Treść:') !!}       {!! Form::textarea('content', $page->content null, ['class' => 'form-control']) !!}     </div>     <div class="form-group">       {!! Form::submit('Zapisz', ['class' => 'btn btn-primary']) !!}       {!! link_to(URL::previous(), 'Powrót', ['class' => 'btn btn-default']) !!}     </div>   {!! Form::close() !!} ...

    Dokumentacja dla metod HTTP

    resources\views\pages\index.blade

    
    		      <a class="btn btn-danger" href="#">Delete</a>
    		      {!! Form::model($page, ['route' => ['pages.delete', $page], 'method' => 'DELETE']) !!}
    		        <button class="btn btn-danger">Delete</button>
    		      {!! Form::close() !!}