- PHP >= 5.4 + Mcrypt
- Mysql
- Composer
- Git (для установки готового приложения)
Вы можете настроить Homestead -- виртуальную машину, которая позволит вам на любой системе (Windows, Mac, Linux) развернуть девелоперское окружение, включающее Ubuntu 14.04, Nginx, MySQL, PostgreSQL, Redis, Memcached и многое другое. Причём вам даже не придётся самим настраивать сервер, а добавление новых сайтов происходит добавлением двух строчек в конфигурационном файле. Подробнее см. https://github.com/boxfrommars/ach/blob/master/docs/homestead.md
xu@calypso:~$ git clone https://github.com/boxfrommars/achievements-laravel.git
xu@calypso:~$ cd achievements-laravel/
xu@calypso:~$ composer update
xu@calypso:~$ chmod a+rw app/storage -R # папка для хранения логов, кеша и всего такого
# создаём бд (если изменили здесь параметры бд, то меняем их в кофигурации в файле app/config/database.php)
mysql> CREATE USER 'ach'@'localhost' IDENTIFIED BY 'ach';
mysql> CREATE DATABASE ach;
mysql> GRANT ALL PRIVILEGES ON ach . * TO 'ach'@'localhost';
mysql> FLUSH PRIVILEGES;
xu@calypso:~$ php artisan migrate
xu@calypso:~$ php artisan db:seed # тестовые данные, чтобы обновить миграции и данные: php artisan migrate:refresh --seed
xu@calypso:~$ php artisan serve --port 8444 # запускаем сервер
Для начала создадим с помощью composer
проект, и дадим серверу права на запись и чтение папки app/storage
xu@calypso:~$ composer create-project laravel/laravel ach --prefer-dist # создаём проект
xu@calypso:~$ cd ach # переходим в папку проекта
xu@calypso:~/ach$ composer update
xu@calypso:~/ach$ chmod a+rw app/storage -R # права на чтение и запись для сервера. можно (и для продакшна -- нужно) просто разрешить для группы вебсервера
Теперь для запуска сервера достаточно выполнить (если вы сами настроили apache или nginx или используете Homestead, запускать сервер не нужно)
xu@calypso:~/ach$ php artisan serve # дополнительный параметр --port для указания конкретного порта
php artisan -- это набор консольных комманд поставляющихся с laravel, облегчающих разработку на laravel, весь список доступных комманд можно посмотерть выполнив
php artisan list
или на странице документации
Настраиваем определение окружения разработчика http://laravel.com/docs/configuration#environment-configuration
По умолчанию окружение -- production
, а для разработки нам понадобится использовать откружение local
. из коробки единственная разница
между local
и production
только в том, что для локал выставлен параметр конфигурации debag => true
и вынесен отдельный конфиг для подключения к бд,
в котором сконфигурирован доступ к бд по умолчанию в homestead, но мы можем переписать для локального окружения любой параметр, также можно
добавлять собственные окружения. Подробнее см. документацию
Для работы в локальном окружении добавить в файле bootstrap/start.php
в массиве передаваемом в ->detectEnvironment
имя своего компьютера (в Linux/Mac определяется командой hostname
)
$env = $app->detectEnvironment(array(
'local' => array('your-machine-name'),
));
Настраиваем автодополнение https://github.com/barryvdh/laravel-ide-helper
Так как в laravel используются 'фасады' (подробнее), то в вашей ide не будет работать автодополнение, а это значит, что вас ждут вечные муки. Благо есть пакет, который решает эту проблему. установим его
xu@calypso:~/ach$ composer require barryvdh/laravel-ide-helper:1.* # добавляем пакет для генерации файлов для автодополнения
Теперь добавим в массив провайдеров в файле app/config/app.php
следующую строчку
'Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider'
Подробнее о сервис-провайдерах см. http://laravel.com/docs/ioc#service-providers
теперь мы можем генерировать файл-хелпер для автодополнения, с помощью команды artisan
: php artisan ide-helper:generate
xu@calypso:~/ach$ php artisan clear-compiled
xu@calypso:~/ach$ php artisan ide-helper:generate # т.к. мы не описали соединение с бд, то выскочит ошибка Could not determine driver/connection for DB -- это нормально
xu@calypso:~/ach$ php artisan optimize
Настраиваем debugbar https://github.com/barryvdh/laravel-debugbar
xu@calypso:~/ach$ composer require barryvdh/laravel-debugbar:dev-master
Теперь добавим в массив провайдеров в файле app/config/app.php
следующую строчку
'Barryvdh\Debugbar\ServiceProvider',
Добавим ресурсы этого пакета (стили, js)
xu@calypso:~/ach$ php artisan debugbar:publish
В документации к пакету автор отмечает, что ресурсы могут меняться и советует добавить в ваш composer.json
в post-update
следующую строчку:
"post-update-cmd": [
"php artisan debugbar:publish"
],
Создаём базу
mysql> CREATE USER 'ach'@'localhost' IDENTIFIED BY 'ach';
mysql> CREATE DATABASE ach;
mysql> GRANT ALL PRIVILEGES ON ach . * TO 'ach'@'localhost';
mysql> FLUSH PRIVILEGES;
Указываем в конфигурационном файле для нашего окружения app/config/local/database.php
настройки подключения к базе данных
'mysql' => array(
'driver' => 'mysql',
'host' => 'localhost',
'database' => 'ach',
'username' => 'ach',
'password' => 'ach',
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
),
Теперь можно перегенерировать хелпер для автодополнения, сейчас ошибок не будет, а заодно хелпер сгенерируется с поддержкой автодополнения для фасадов связанных с БД, таких как Schema, Blueprint.
xu@calypso:~/ach$ php artisan clear-compiled
xu@calypso:~/ach$ php artisan ide-helper:generate
xu@calypso:~/ach$ php artisan optimize
Для users
по умолчанию уже идёт модель User
, поэтому создавть вручную её не требуется
Создадим миграцию для создания таблицы users
xu@calypso:~/ach$ php artisan migrate:make create_users_table
создался файл ach/app/database/migrations/YYYY_MM_DD_SSZZZZ_create_users_table.php
, описываем в нём миграцию
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateUsersTable extends Migration {
// то что происходит при 'накатывании' миграции, в нашем случае создание таблицы
public function up()
{
Schema::create('users', function(Blueprint $table)
{
$table->string('name');
$table->string('image')->nullable();
$table->increments('id');
$table->string('email')->unique();
$table->string('password');
$table->string('remember_token', 100)->nullable();
$table->timestamps(); // стандартные timestamps: created_at, updated_at
$table->softDeletes(); // 'мягкое' удаление, колонка deleted_at
});
}
// то что происходит при 'откате' миграции, в нашем случае удаление таблицы
public function down()
{
Schema::table('users', function(Blueprint $table)
{
$table->drop();
});
}
}
отметим использование 'мягкого' удаления,
при удалении с помощью $user->delete()
сама запись из таблицы не удаляется, а лишь помечается, как удалённая, при этом в исключается
из результатов запросов вида User:all()
и тому подобных. подробнее см. http://laravel.com/docs/eloquent#soft-deleting
вообще говоря миграции нужны не только для создания таблиц, но и для любых других действий с ними: например, для добавления/удаления колонок, изменения колонок и даже для добавления изменения данных. Подробнее см. http://laravel.com/docs/migrations и https://laracasts.com/index/migration
Применяем миграцию
xu@calypso:~/ach$ php artisan migrate
Тут придётся создать модель вручную, то есть создать файл app/models/Achievement.php
со следующим содержимым
class Achievement extends Eloquent {
protected $table = 'achievements'; // в данном случае не обязательно указывать таблицу, так как её имя -- множественное число от имени класса модели и магия laravel всё сделала бы за вас
}
Теперь создаём миграцию точно так же как и для users
xu@calypso:~/ach$ php artisan migrate:make create_achievements_table
создался файл app/database/migrations/YYYY_MM_DD_SSZZZZ_create_achievements_table.php
, описываем в нём миграцию
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateAchievementsTable extends Migration {
public function up()
{
Schema::create('achievements', function(Blueprint $table)
{
$table->increments('id');
$table->integer('depth'); // глубина
$table->integer('outlook'); // кругозор
$table->integer('interaction'); // взаимодействие
$table->string('title');
$table->text('description')->nullable();
$table->string('image')->nullable();
$table->timestamps();
});
}
public function down()
{
Schema::table('achievements', function(Blueprint $table)
{
$table->drop();
});
}
}
Применяем миграцию
xu@calypso:~/ach$ php artisan migrate
для этого нам опять необходимо создать миграцию, которая создат нам таблицу для связей между нашими сущностями, заметим, что мы добавляем данные
в таблицу связи -- колонку is_approved
, которая показывает, было ли одобрено достижение администратором, о работе с этими данными будет рассказано несколько ниже
xu@calypso:~/ach$ php artisan migrate:make create_user_achievements_table
создался файл app/database/migrations/YYYY_MM_DD_SSZZZZ_create_user_achievements_table.php
, описываем в нём миграцию
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateUserAchievementsTable extends Migration {
public function up()
{
Schema::create('user_achievements', function(Blueprint $table)
{
$table->integer('id_user')->unsigned();
$table->integer('id_achievement')->unsigned();
$table->boolean('is_approved')->default(false);
$table->foreign('id_user')->references('id')->on('users');
$table->foreign('id_achievement')->references('id')->on('achievements');
});
}
public function down()
{
Schema::table('user_achievements', function(Blueprint $table)
{
$table->drop();
});
}
}
Применяем миграцию
xu@calypso:~/ach$ php artisan migrate
Добавляем в модель Achievement
метод ->users()
// User >-< Achievement many to many relationship
public function users()
{
return $this->belongsToMany('User', 'user_achievements', 'id_achievement', 'id_user');
}
А в модель User
метод ->achievements()
// User >-< Achievement many to many relationship
public function achievements()
{
return $this->belongsToMany('Achievement', 'user_achievements', 'id_user', 'id_achievement')->withPivot('is_approved');
}
Теперь мы можем получать связанные сущности, например:
$user = User::find($id);
$achievements = $user->achievements; // все достижения данного пользователя
$achievement = $achievements[0];
$isApproved = $achievement->pivot->is_approved; // получаем данные из таблицы связи
или
$achievement = Achievement::find($id);
$users = $achievements->users; // все пользователи с данным достижением
Подробнее http://laravel.com/docs/eloquent#relationships
xu@calypso:~/ach$ php artisan migrate:make create_groups_table
создался файл app/database/migrations/YYYY_MM_DD_SSZZZZ_create_groups_table.php
, описываем в нём миграцию
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateGroupsTable extends Migration {
public function up()
{
Schema::create('groups', function(Blueprint $table)
{
$table->increments('id');
$table->string('title');
$table->string('description');
$table->string('code');
$table->string('image')->nullable();
$table->timestamps();
});
}
public function down()
{
Schema::table('groups', function(Blueprint $table)
{
$table->drop();
});
}
}
создаём модель app/models/Group.php
class Group extends Eloquent {
}
Создаём связь многие ко многим между сущностями Group и User
xu@calypso:~/ach$ php artisan migrate:make create_user_groups_table
создался файл app/database/migrations/YYYY_MM_DD_SSZZZZ_create_user_groups_table.php
, описываем в нём миграцию
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateUserGroupsTable extends Migration {
public function up()
{
Schema::create('user_groups', function(Blueprint $table)
{
$table->integer('id_user')->unsigned();
$table->integer('id_group')->unsigned();
$table->foreign('id_user')->references('id')->on('users');
$table->foreign('id_group')->references('id')->on('groups');
});
}
public function down()
{
Schema::table('user_groups', function(Blueprint $table)
{
$table->drop();
});
}
}
Применяем миграции
xu@calypso:~/ach$ php artisan migrate
Добавляем в модель Group
метод ->users()
// User >-< Achievement many to many relationship
public function users()
{
return $this->belongsToMany('User', 'user_groups', 'id_group', 'id_user');
}
А в модель User
метод ->groups()
// User >-< Group many to many relationship
public function groups()
{
return $this->belongsToMany('Group', 'user_groups', 'id_user', 'id_group');
}
Создаём связь многие ко многим между сущностями Group и Achievement
xu@calypso:~/ach$ php artisan migrate:make create_achievement_groups_table
создался файл app/database/migrations/YYYY_MM_DD_SSZZZZ_create_achievement_groups_table.php
, описываем в нём миграцию
Добавляем в модель Achievement
метод ->groups()
// Achievement >-< Group many to many relationship
public function groups()
{
return $this->belongsToMany('Group', 'achievement_groups', 'id_achievement', 'id_group');
}
А в модель Group
метод ->achievements()
// Group >-< Achievement many to many relationship
public function achievements()
{
return $this->belongsToMany('Achievement', 'achievement_groups', 'id_group', 'id_achievement');
}
Теперь мы можем перегенерировать хелпер-файл для поддержки автодополнения в созданных моделей, для этого используется команда php artisan ide-helper:models
xu@calypso:~/ach$ php artisan clear-compiled
xu@calypso:~/ach$ php artisan ide-helper:generate
xu@calypso:~/ach$ php artisan optimize
генератор спросит, добавлять ли докблок с методами и свойствами в каждый файл модели или описать модели в стандартном файле _ide_helper.php
,
я предпочитаю добавлять в модели.
Добавляем папку publig/img/user
для хранения аватарок пользователей
Добавляем папку publig/img/achievement
для хранения картинок достижений
Кладём в каждую из этих папок файл .gitignore
(чтобы картинки не попадали в репозиторий, но сами папки создавались) со следующим содержимым
*
!.gitignore
Добавляем очень удобный пакет для генерации тестовых данных (документация)
xu@calypso:~/ach$ composer require fzaninotto/faker:1.4.*@dev
Создаём файл app/database/seeds/AchievementSeeder.php
<?php
class AchievementSeeder extends Seeder
{
/** @var \Faker\Generator */
protected $_faker;
public function __construct()
{
$this->_faker = Faker\Factory::create('ru_RU');
}
public function run()
{
$usersCount = 16;
$achievementsCount = 10;
$beardFrequency = 4; // на сколько мальчишек один бородач
$defaultPassword = '123123';
// а вот и все группы
$groupsData = array(
array('title' => 'мальчишки', 'code' => 'male'),
array('title' => 'девчонки', 'code' => 'female'),
array('title' => 'разработчики', 'code' => 'developer'),
array('title' => 'дизайнеры', 'code' => 'designer'),
array('title' => 'менеджеры', 'code' => 'manager'),
array('title' => 'бородачи', 'code' => 'beard'),
);
$userImageDirectory = 'public/img/user/';
$achievementImageDirectory = 'public/img/achievement/';
// Удаляем предыдущие данные
DB::table('user_groups')->delete();
DB::table('achievement_groups')->delete();
DB::table('user_achievements')->delete();
DB::table('groups')->delete();
DB::table('achievements')->delete();
DB::table('users')->delete();
$this->_cleanImageDirectory($userImageDirectory);
$this->_cleanImageDirectory($achievementImageDirectory);
/** @var Group[] $groups */
$groups = array();
foreach ($groupsData as $group) {
$groups[$group['code']] = Group::create(array(
'title' => $group['title'],
'description' => '',
'code' => $group['code'],
));
}
/** @var Achievement[] $achievements */
$achievements = array();
for ($i = 0; $i < $achievementsCount; $i++) {
$achievement = Achievement::create(array(
'depth' => $this->_faker->numberBetween(0, 100),
'outlook' => $this->_faker->numberBetween(0, 100),
'interaction' => $this->_faker->numberBetween(0, 100),
'title' => $this->_faker->sentence(3),
'description' => $this->_faker->paragraph(),
'image' => $this->_faker->image($achievementImageDirectory, 100, 100, 'abstract', false),
));
$achievement->groups()->sync($this->_getRandomIds($groups));
$achievements[] = $achievement;
}
// Добавляем администратора
/** @var User $user */
$userData = array(
'name' => 'Dmitry Groza',
'email' => '[email protected]',
'password' => Hash::make($defaultPassword), // см. http://laravel.com/docs/security#storing-passwords
'image' => $this->_faker->image($userImageDirectory, 100, 100, 'people', false),
);
$userGroupIds = array($groups['developer']->id, $groups['male']->id);
$userAchievementIds = $this->_getRandomIds($achievements, 4);
$this->_createUser($userData, $userGroupIds, $userAchievementIds);
// Добавляем остальных тестовых пользователей
for ($i = 0; $i < $usersCount; $i++) {
$gender = $this->_faker->randomElement(array('male', 'female'));
/** @var User $user */
$userData = array(
'name' => mb_convert_case($this->_faker->name($gender), MB_CASE_TITLE), // у фейкера нехорошие имена/фамилии, то с большой буквы, то с маленькой. приводим к нормальному виду
'email' => $this->_faker->email,
'password' => Hash::make($defaultPassword),
'image' => $this->_faker->image($userImageDirectory, 100, 100, 'people', false),
);
$position = $this->_faker->randomElement(array('developer', 'manager', 'designer'));
$userGroupIds = array($groups[$gender]->id, $groups[$position]->id);
$userAchievementIds = $this->_getRandomIds($achievements, 4);
// добавляем немного бородачей
if ($gender === 'male' && rand(1, $beardFrequency) === 1) {
array_push($userGroupIds, $groups['beard']->id);
}
$this->_createUser($userData, $userGroupIds, $userAchievementIds);
}
}
/**
* @param array $data данные, которые прямиком отправляются в User::create($data)
* @param array $groupIds массив id групп
* @param array $achievementIds массив id достижений
*/
protected function _createUser($data, $groupIds, $achievementIds)
{
$user = User::create($data);
$user->groups()->sync($groupIds);
if (!empty($achievementIds)) { // тут, в отличии от groups нужна проверка, т.к. array_fill вторым параметром принимает только integer > 0
$user->achievements()->sync(
array_combine($achievementIds, array_fill(0, count($achievementIds), array('is_approved' => true)))
);
}
}
/**
* @param string $directory директория для очищения
*
* очищаем директорию, при этом не удалянм в ней файл .gitignore
*/
protected function _cleanImageDirectory($directory)
{
if (File::isDirectory($directory)) {
$items = new FilesystemIterator($directory);
foreach ($items as $item) {
if (!$item->isDir() && $item->getFilename() !== '.gitignore') {
File::delete($item->getRealPath());
}
}
}
}
/**
* @param Eloquent[] $entities массив объектов со свойством id
* @param integer $maxCount максимальное число возвращаемых id
* @return array массив id случайно выбранных объектов из списка
*/
protected function _getRandomIds($entities, $maxCount = null)
{
if ($maxCount === null) {
$maxCount = count($entities);
}
return array_map(
function ($item) {
return $item->id;
},
$this->_faker->randomElements($entities, rand(1, $maxCount))
);
}
}
а в файле app/database/seeds/AchievementSeeder.php
добавляем вызов генерации тестовых данных
$this->call('AchievementSeeder');
На данный момент нужно реализовать следующие пути
/
список достижений/users
список пользователей/users/{id}
страница пользователя, где id -- идентификатор пользователя/achievements
тоже список достижений (?)/achievements/{id}
страница достижения
в файле app/routes.php
удаляем текущий роут для пути /
и прописываем наши роуты
Route::get('/', 'AchievementController@getMain');
Route::get('users', 'AchievementController@getUsers');
Route::get('users/{id}', 'AchievementController@getUser');
Route::get('achievements', 'AchievementController@getAchievements');
Route::get('achievements/{id}', 'AchievementController@getAchievement');
Так как страниц не очень много, то все их заносим в один контроллер AchievementController
Создаём файл app/controllers/AchievementController.php
со следующим содержимым
(если действие контроллера возвращает массив, то приложение возвращает ответ в формате json
с соответствующим
хедером application/json
, при это eloquent модели тоже корректно преобразовываются, с исключенными hidden
полями,
которые мы установили в соответствующей модели, как,например, поле password
)
<?php
class AchievementController extends BaseController
{
public function getMain()
{
return ['url' => '/'];
}
public function getUsers()
{
$users = User::all();
return $users;
}
public function getUser($id)
{
$user = User::find($id);
if ($user === null) {
App::abort(404, 'Page not found');
}
return $user;
}
public function getAchievements()
{
$achievements = Achievement::all();
return $achievements;
}
public function getAchievement($id)
{
$achievement = Achievement::find($id);
if ($achievement === null) {
App::abort(404, 'Page not found');
}
return $achievement;
}
}
Теперь можно открыть соответствующие страницы в браузере и убедиться, что всё работает.
Сначала создадим общий лайаут app/views/layout.blade.php
, в котором в месте, где будет выводиться контент вставляем @yield('content')
(см. документацию к шаблонизотру blade). Заодно удалим ненужные нам app/views/hello.php
и app/views/email
.
...
<!-- Begin page content -->
<div class="container">
@yield('content')
</div>
...
Теперь создадим отдельные страницы (и соответствующие папки) для каждого действия
app/views/user/user_list.blade.php
@extends('layout')
@section('content')
<h1>Пользователи</h1>
@foreach ($users as $user)
<div class="media">
<div class="pull-left">
<img class="img-thumbnail" src="/img/user/{{{ $user->image }}}" />
</div>
<div class="media-body">
<h4 class="media-heading"><a href="/users/{{{ $user->id }}}">{{{ $user->name }}}</a></h4>
<ul class="list-inline">
@foreach ($user->achievements as $achievement)
<li><a href="/achievements/{{{ $achievement->id }}}" title="{{{ $achievement->title }}}"><img class="img-thumbnail achievement-image-icon" src="/img/achievement/{{{ $achievement->image }}}" /></a></li>
@endforeach
</ul>
<ul class="list-inline">
@foreach ($user->groups as $group)
<li><a class="label label-group label-{{{ $group->code }}}" href="/groups/{{{ $group->id }}}">{{{ $group->title }}}</a></li>
@endforeach
</ul>
</div>
</div>
@endforeach
@stop
По аналогии создадим виды app/views/user/user_show.blade.php
, app/views/achievement/achievement_show.blade.php
, app/views/achievement/achievement_list.blade.php
Изменим контроллер app/controllers/AchievementController.php
, чтобы он начал работать с созданными видами:
<?php
class AchievementController extends BaseController
{
public function getMain()
{
return View::make('layout');
}
public function getUsers()
{
$users = User::all();
return View::make('user.user_list', array('users' => $users));
}
public function getUser($id)
{
$user = User::find($id);
if ($user === null) {
App::abort(404, 'Page not found');
}
return View::make('user.user_show', array('user' => $user));
}
public function getAchievements()
{
$achievements = Achievement::all();
return View::make('achievement.achievement_list', array('achievements' => $achievements));
}
public function getAchievement($id)
{
$achievement = Achievement::find($id);
if ($achievement === null) {
App::abort(404, 'Page not found');
}
return View::make('achievement.achievement_show', array('achievement' => $achievement));
}
}
Теперь настало время заглянуть в наш debugbar.
А там мы увидим, что страница /users
делает 35 (жуть) запросов (при 16 пользователях) к базе данных.
Дело в том, что каждый раз, когда мы обращаемся к связанным сущностям eloquent-модели, выполняется запрос к базе, получающий эти связанные сущности.
Но это легко исправить, достаточно заменить:
User::all()
наUser::with('achievements', 'groups')->get()
Achievement::all()
наAchievement::with('users', 'groups')->get()
Achievement::find($id)
наAchievement::with('users', 'groups')->find($id)
(при запросе одной моделей, нет плюсов в использовании with -- и так и так выполняются три запроса, но это понадобится нам чуть ниже)User::find($id)
наUser::with('achievements', 'groups')->find($id)
и мы получим всего три запроса (для users: выборка всех пользователей, выборка всех групп этих пользователей и выборка всех достижений этих пользователей)
Теперь открываем страницу /achievements/{id}
и видим, что даже после замены выполняются десятки запросов. Это понятно, мы подгрузили только
связанных пользователей, но не их связанные группы и достижения, поэтому для каждого пользователя в списке, будет выполняться ещё по два запроса.
И кажется, что вот тут-то всё кончено и придётся писать что-то громоздкое собственными руками. Но это не так :) В laravel и для этого есть немного магии,
а именно вот такая конструкция:
Achievement::with('users.groups', 'user.achievements', 'groups')->find($id)
Итого пять запросов независимо от количества пользователей, привязанных к данному достижению.
По умолчанию с laravel уже идёт модель User
(app/models/User.php
), которая довольно просто используется для аутентификации с помощью
Eloquent драйвера аутентификации. Также мы выше уже создали таблицу users
со всеми необходимыми полями. Теперь для аутентификации пользователя,
нам достаточно в контроллере, в котором будет происходить логин, проверить (предварительно, конечно, полчив $email и $password из формы):
if (Auth::attempt(array('email' => $email, 'password' => $password)))
{
// успех
} else {
// неудача
}
мы используем колонку email
для аутентификации, но вы может использовать и другую колонку, например, username
Создадим контроллер, в котором опишем три действия:
class AuthController extends BaseController
{
public function getLogin()
{
return View::make('auth.login');
}
public function postLogin()
{
// валидатор проверяющий заполнены ли поля формы
$validator = Validator::make(Input::all(), array(
'email' => 'required',
'password' => 'required'
));
$credentials = array(
'email' => Input::get('email'),
'password' => Input::get('password'),
);
// если Auth::attempt вторым параметром принимает true, то приложение запоминает пользователя на неопределённое время
// подробнее см. http://laravel.com/docs/security#authenticating-users
$isRemember = Input::get('is_remember');
if ($validator->passes() && Auth::attempt($credentials, $isRemember)) {
// в случае успешной аутентификации редиректим на главную
return Redirect::intended($path);
} else {
// в случае неуспешной -- редиректим назад на форму, заполняя поля введёнными данными, также записываем в flash-сообщение ошибки
return Redirect::back()
->withInput()
->with('errors', array('Неправильный логин или пароль'));
}
}
public function logout()
{
Auth::logout();
return Redirect::to('/');
}
}
Input::all()
, Input::get($fieldName)
введённые пользователем данные, см. http://laravel.com/docs/requests#basic-input
Validator::make
-- валидатор для данных, см. http://laravel.com/docs/validation#basic-usage
Redirect::back()
, Redirect::to($path)
, Redirect::intended($path)
-- редиректы, см. http://laravel.com/docs/responses#redirects,
о Redirect::intended($path)
будет написано чуть ниже
Теперь создадим вид для страницы логина auth/login.blade.php
(точно так же, как и раньше наследуемся от общего лайаута и переписываем
секцию content), подробнее о работе с формами см. http://laravel.com/docs/html
@extends("layout")
@section("content")
<h3>Вход</h3>
{{ Form::open(array('role' => 'form', 'class' => 'form-horizontal')) }}
<div class="form-group">
{{ Form::label("email", "Email", array('class' => 'col-sm-2 control-label')) }}
<div class="col-sm-4">
{{ Form::text("email", Input::old("email"), array('class' => 'form-control')) }}
</div>
</div>
<div class="form-group">
{{ Form::label("password", "Пароль", array('class' => 'col-sm-2 control-label')) }}
<div class="col-sm-4">
{{ Form::password("password", array('class' => 'form-control')) }}
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-4">
<div class="checkbox">
<label>
<input type="checkbox" name="is_remember"> Запомнить меня
</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-4">
{{ Form::submit("Войти", array('class' => 'btn btn-default')) }}
</div>
</div>
{{ Form::close() }}
@stop
И добавим в app/routes.php
:
Route::get('login', 'AuthController@getLogin');
Route::post('login', 'AuthController@postLogin');
Route::get('logout', 'AuthController@logout');
Также изменим наш общий лайаут, добавив функциональность для вывода любых ошибок переданных во флеш-сообщениях (см. http://laravel.com/docs/session#flash-data и http://laravel.com/docs/responses#redirects). Для этого добавим
@if (Session::has('errors'))
@foreach (Session::get('errors') as $error)
<div class="alert alert-danger">{{ $error }} <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button></div>
@endforeach
@endif
Теперь можно проверять работу формы входа и страницы выхода. Добавим в лайаут ссылки на вход для гостей и на страницу профиля и на выход для вошедших пользователей
<ul class="nav navbar-nav navbar-right">
@if (Auth::check())
<li><a href="/my">Мои успехи</a></li>
<li><a href="/logout">Выйти</a></li>
@else
<li><a href="/login">Войти</a></li>
@endif
</ul>
Auth::check()
-- проверяет, прошёл ли человек аутентификацию
Теперь можно устроить страницу /my
пользователя, воспользовавшись методом Auth::user()
. Добавим соответствующий метод в AchievementController
public function getMy()
{
/** @var User $user */
$user = Auth::user();
if (is_null($user)) App::abort(404, 'Page not found');
// воспользуемся тем же видом, что и для страницы пользователя
return View::make('user.user_show', array('user' => $user));
}
И добавим новый роут
Route::get('my', array('before' => 'auth', 'uses' => 'AchievementController@getMy'));
Тут мы воспользовались встроенным фильтром auth
(находится в файле app/filters.php
), который проверяет выполнил ли пользователь вход
и, если нет, запишет в сессию, адрес текущей страницы и перенаправит пользователя на страницу входа. После входа пользователя перенаправит обратно на данный адрес.
Для этого используется метод Redirect::intended($path)
(см. AuthController@postLogin
), который в случае существования в сессии intended страницы,
перенаправит на неё или на $path в другом случае.