composer create-project laravel/laravel example-app
cd example-app php artisan serve
php artisan make:controller MyFirstController
php artisan list
Глава 1. Прогулки по пути запроса
require __DIR__.'/../vendor/autoload.php';
$app = require_once __DIR__.'/../bootstrap/app.php';
$app = require_once __DIR__.'/../bootstrap/app.php';
$app->singleton( Illuminate\Contracts\Http\Kernel::class, App\Http\Kernel::class ); $app->singleton( Illuminate\Contracts\Console\Kernel::class, App\Console\Kernel::class );
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
$response = $kernel->handle( $request = Request::capture() )->send();
$request = Request::capture()
$kernel->handle
public function handle($request) { try { $request->enableHttpMethodParameterOverride(); $response = $this->sendRequestThroughRouter($request); } catch (Throwable $e) { $this->reportException($e); $response = $this->renderException($request, $e); } $this->app['events']->dispatch( new RequestHandled($request, $response) ); return $response; }
$response = $this->sendRequestThroughRouter($request);
protected function sendRequestThroughRouter($request) { $this->app->instance('request', $request); Facade::clearResolvedInstance('request'); $this->bootstrap(); return (new Pipeline($this->app)) ->send($request) ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) ->then($this->dispatchToRouter()); }
return (new Pipeline($this->app)) ->send($request) ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) ->then($this->dispatchToRouter()); }
###Глава 2. Middleware
public function handle($request, Closure $next) { // return $next($request); }
return $next($request);
public function handle($request, Closure $next) { if ($request->ip() !== ‘190.90.90.90’) { abort(404); } return $next($request); }
protected $middleware = [ // \App\Http\Middleware\TrustHosts::class, \App\Http\Middleware\TrustProxies::class, \Illuminate\Http\Middleware\HandleCors::class, \App\Http\Middleware\PreventRequestsDuringMaintenance::class, \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \App\Http\Middleware\TrimStrings::class, \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, ];
protected $middlewareGroups = [ 'web' => [ \App\Http\Middleware\EncryptCookies::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Session\Middleware\StartSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class, \App\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, ], 'api' => [ // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, 'throttle:api', \Illuminate\Routing\Middleware\SubstituteBindings::class, ], ];
###Глава 3. Route
public function dispatchToRoute(Request $request) { return $this->runRoute($request, $this->findRoute($request)); }
public function boot() { $this->routes(function () { Route::middleware('web') ->group(base_path('routes/web.php')); }); }
'web' => [ \App\Http\Middleware\EncryptCookies::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Session\Middleware\StartSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class, \App\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, ],
Route::get('/', function () { return view('welcome'); });
###Глава 4. Структура
DB_CONNECTION=mysql DB_HOST=localhost DB_PORT=3306 DB_DATABASE=laravel DB_USERNAME=root DB_PASSWORD=
###Глава 5. Конвенция наименований
Глава 6. Миграции
php artisan make:model User -m
php artisan make:migration create_users_table
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateUsersTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('users', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); $table->rememberToken(); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('users'); } }
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::table('news', function (Blueprint $table) { $table->boolean('active')->default(true); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('news', function (Blueprint $table) { $table->dropColumn('active') }); } };
php artisan migrate
php artisan migrate:rollback
php artisan migrate:rollback –step=2 Глава 7. Модели
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Service extends Model { }
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Service extends Model { protected $table = ‘custom_table’; }
class Service extends Model { protected $fillable = [ 'title', 'code' ]; }
$user->create(request()->all());
Глава 7.1. QueryBuilder
$users = User::query()->where(‘active’, true)->where(‘banned’, false)
SELECT * FROM users WHERE active = 1 AND banned = 0;
$user = User::query()->where(‘active’, true)->where(‘banned’, false)->first()
$users = User::query()->where(‘active’, true)->where(‘banned’, false)->get()
Глава 7.2. Collections
$users->sortBy(‘name’)->filter(fn($user) => $user->id > 1)
Глава 8. Отношения
php artisan make:model Phone
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Phone extends Model { public function user() { return $this->belongsTo(User::class); } }
return $this->belongsTo(User::class, 'foreign_key', 'owner_key');
return $this->belongsTo(User::class, 'telefon_uuid', 'uuid');
$phone->user->value
$phone->user_id = 1; $phone->save();
$user = User::find(1); $phone->user()->associate($user); $phone->save();
$phone->user()->save(new User([name => ‘CutCode’]))
$phone->user()->create([‘name’ => ‘CutCode’])
$phone->user()->saveMany([ new User([‘name’ => ‘CutCode’]), new User([‘name’ => ‘Ivan’]) ])
$phone->user()->createMany([ [name => ‘CutCode’], [name => ‘Ivan’] ])
$phone->user()->update([‘name’ => ‘Oleg’])
$phone->user()->save($user)
$phone->user()->delete()
8.2. HasMany - отношение "Один ко многим"
public function comments() { return $this->hasMany(Comment::class); }
public function post() { return $this->belongsTo(Post::class); }
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model { public function phone() { return $this->hasOne(Phone::class); } }
$user->phone->value
public function comments() { return $this->hasMany(Comment::class); } public function comment() { return $this->hasOne(Comment::class); }
SELECT * FROM comments WHERE post_id = 1;
SELECT * FROM comments WHERE post_id = 1 LIMIT 1;
8.4. Расширенное использование HasOne
public function lastComment(): HasOne { return $this->hasOne(Comment::class)->latestOfMany(); }
public function firstComment(): HasOne { return $this->hasOne(Comment::class)->oldestOfMany(); }
public function firstComment(): HasOne { return $this->hasOne(Comment::class)->oldestOfMany(‘identity’); }
public function currentPricing() { return $this->hasOne(Price::class)->ofMany([ 'published_at' => 'max', 'id' => 'max', ], function ($query) { $query->where('published_at', '<', now()); }); }
public function firstComment(): HasOne { return $this->comments()->one()->oldestOfMany(); }
8.5. BelongsToMany - отношение многие ко многим
users id - integer name - string roles id - integer name - string role_user user_id - integer role_id - integer
Schema::create('role_user', function (Blueprint $table) { $table->id(); $table->foreignId('user_id') ->constrained() ->cascadeOnDelete() ->cascadeOnUpdate(); $table->foreignId('role_id') ->constrained() ->cascadeOnDelete() ->cascadeOnUpdate(); $table->timestamps(); });
public function roles() { return $this->belongsToMany(Role::class); }
Schema::create('product_property', function (Blueprint $table) { $table->id(); $table->foreignId(‘product_id’) ->constrained() ->cascadeOnDelete() ->cascadeOnUpdate(); $table->foreignId('property_id') ->constrained() ->cascadeOnDelete() ->cascadeOnUpdate(); $table->string(‘value’); $table->timestamps(); });
public function properties() { return $this->belongsToMany(Property::class) ->withPivot(‘value’); }
foreach ($products->properties as $property) { echo $property->pivot->value; }
$product = Product::find(1); $products->properties()->attach(1);
$products->properties()->attach(1, ['value' => ‘128 mb’]);
$product = Product::find(1); $products->properties()->detach(1);
$products->properties()->sync([1,2,3]);
$products->properties()->toggle([1, 2, 3]);
// начальное состояние Характеристик товара [] Характеристики айди 1, 10, 20 //добавляем характеристику id 10 tovar -> toggle (10) Характеристик товара [10] //переключаем характеристику id 10 и добавляем характеристику id 20 tovar -> toggle (10, 20) Характеристик товара [20]
$products->properties()->sync([1 => ['value' => ‘128 mb’], 2, 3]);
8.6. HasOneThrough - отношение один к одному через таблицу
mechanics id - integer name - string cars id - integer model - string mechanic_id - integer owners id - integer name - string car_id - integer
class Mechanic extends Model { /** * Get the car's owner. */ public function carOwner() { return $this->hasOneThrough(Owner::class, Car::class); } }
public function carOwner() { return $this->hasOneThrough( Owner::class, Car::class, 'mechanic_id', // Ключ в таблице cars - связь с текущей таблицей mechanics 'car_id', // Ключ в таблице owners связь с таблицей cars через которую мы двигаемся 'id', // Primary key в mechanics 'id' // Primary key в cars ); }
8.7. HasManyThrough - отношение один ко многим через таблицу
class Mechanic extends Model { /** * Get the car's owner. */ public function carOwners() { return $this->hasManyThrough(Owner::class, Car::class); } }
class Mechanic extends Model { public function carOwners() { return $this->through(‘cars’)->has(‘owners’); } }
class Mechanic extends Model { public function cars() { return $this->hasMany(Car::class); } }
class Car extends Model { public function owners() { return $this->hasMany(Owner::class); } }
class Car extends Model { public function owner() { return $this->hasOne(Owner::class); } }
class Mechanic extends Model { /** * Get the car's owner. */ public function carOwner() { return $this->through(‘cars’)->has(‘owners’); } }
8.8.Полиморфные отношения
posts id - integer title - string blogs id - integer title - string news id - integer title - string comments id - integer text - text commentable_id - integer commentable_type - string
class Post extends Model { /** * Get all of the post's comments. */ public function comments() { return $this->morphMany(Comment::class, 'commentable'); } }
class Comment extends Model { /** * Get the parent commentable model (post or video). */ public function commentable() { return $this->morphTo(); } }
$post->comments $blog->comments $new->comments
posts id - integer blogs id - integer news id - integer images id - integer url - string imageable_id - integer imageable_type - string
class Post extends Model { /** * Get the post's image. */ public function image() { return $this->morphOne(Image::class, 'imageable'); } }
posts id - integer title - string blogs id - integer title - string news id - integer title - string tags id - integer name - string taggables tag_id - integer taggable_id - integer taggable_type - string
class Tag extends Model { /** * Get all of the posts that are assigned this tag. */ public function posts() { return $this->morphedByMany(Post::class, 'taggable'); } /** * Get all of the videos that are assigned this tag. */ public function blogs() { return $this->morphedByMany(Blog::class, 'taggable'); } }
class Post extends Model { /** * Get all of the tags for the post. */ public function tags() { return $this->morphToMany(Tag::class, 'taggable'); } }
8.9. Eager load
foreach($post->comments as $comment) { echo $comment->author->name; }
$comments = Comment::query()->with(‘author’)->get();
$post = Post::query()->with(‘comments.author’)->where(‘id’, 1)->first();
$post->load(‘comments.author’);
8.10. QueryBuilder для отношений
$posts = Post::has('comments')->get();
$posts = Post::whereHas('comments', function (Builder $query) { $query->where('content', 'like', 'code%'); })->get();
$posts = Post::whereRelation('comments', 'is_approved', false)->get();
8.11. Агрегатные функции для отношений
$posts = Post::withCount('comments')->get();
foreach ($posts as $post) { echo $post->comments_count; }
$posts = Post::withMax('comments', 'likes')->get(); foreach ($posts as $post) { echo $post->comments_max_likes; }
$posts = Post::withAvg('comments', 'votes')->get(); foreach ($posts as $post) { echo $post->comments_avg_votes; } $posts = Post::withSum('comments', 'votes')->get(); foreach ($posts as $post) { echo $post->comments_sum_votes; }
9.1. Mutators/Accessors
class User extends Model { /** * Get the user's first name. * * @return \Illuminate\Database\Eloquent\Casts\Attribute */ protected function phone(): Attribute { return Attribute::make( get: fn ($value) => ‘+’ . $value, ); } }
class User extends Model { /** * Get the user's first name. * * @return \Illuminate\Database\Eloquent\Casts\Attribute */ protected function name(): Attribute { return Attribute::make( get: fn () => $this->first_name . " " . $this->last_name, ); } }
class User extends Model { /** * Get the user's first name. * * @return \Illuminate\Database\Eloquent\Casts\Attribute */ protected function phone(): Attribute { return Attribute::make( set: fn ($value) => trim(preg_replace('/^1|\D/', "", $value)), ); } }
return Attribute::make( get: fn ($value) => ‘+’ . $value, set: fn ($value) => trim(preg_replace('/^1|\D/', "", $value)), );
9.2. Casts
class User extends Model { /** * The attributes that should be cast. * * @var array */ protected $casts = [ 'is_admin' => 'boolean', ]; }
class User extends Model { /** * The attributes that should be cast. * * @var array */ protected $casts = [ 'images' => 'collection', ]; }
php artisan make:cast PhoneCast
namespace App\Casts; use Illuminate\Contracts\Database\Eloquent\CastsAttributes; class PhoneCast implements CastsAttributes { /** * Cast the given value. * * @param \Illuminate\Database\Eloquent\Model $model * @param string $key * @param mixed $value * @param array $attributes * @return array */ public function get($model, $key, $value, $attributes) { return ‘+’ . $value; } /** * Prepare the given value for storage. * * @param \Illuminate\Database\Eloquent\Model $model * @param string $key * @param array $value * @param array $attributes * @return string */ public function set($model, $key, $value, $attributes) { return trim(preg_replace('/^1|\D/', "", $value)); } }
use App\Casts\PhoneCast; class User extends Model { /** * The attributes that should be cast. * * @var array */ protected $casts = [ 'phone' => PhoneCast::class, ]; }
Глава 10. Scopes
$posts = Post::query()->where(‘active’, true)->where(‘is_moderated’, true)->where(‘banned’, false)->get()
$posts = Post::query()->active()->get()
public function activeScope(Builder $query) { $query->where(‘active’, true)->where(‘is_moderated’, true)->where(‘banned’, false); }
11.1 View
Route::get('/’', function(){ return view('home’'); });
Route::get('/’', function(){ return view('pages.home’'); });
return view('home’', ['posts' => Post::query()->active()->get()]);
<html> <head> <title>Cutcode here!</title> </head> <body> <ul> @foreach($posts as $post) <li>{{ $post->name }}</li> @endforeach </ul> </body> </html>
@if(true) @endif @auth @endauth
Глава 12. Контроллер
php artisan make:controller HomeController
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class HomeController extends Controller { // }
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class HomeController extends Controller { public function index() { return view(‘home’, [‘posts’ => Post::query()->active()->get()]) } }
Route::get('/’', [HomeController:class, ‘index’]);
Глава 13. Service Container
public function indexPage(Request $request) { }
public function indexPage(User $user) { }
class Car { public function __construct( protected string $color ) {} }
public function indexPage(Car $car) { }
public function boot(): void { $this->app->instance(Car::class, new Car(‘white’)); }
$app->singleton( Illuminate\Contracts\Http\Kernel::class, App\Http\Kernel::class );
public function indexPage(Illuminate\Contracts\Http\Kernel $kernel) { }
public function boot(): void { $this->app->bind(MessengersInterface::class, Telegram::class); }
public function indexPage(MessengersInterface $messenger) { }
$this->app->bind(MessengersInterface::class, Slack::class);
public function indexPage() { $messenger = app(MessengersInterface::class); }
Глава 13. Два брата Request и Response
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class HomeController extends Controller { public function index(Request $request) { dump($request->all()); // все параметры реквеста // получим метод запроса GET или POST PUT DELETE и остальные dump($request->method()); // Читаем куку dump($request->cookie(‘name’)); // Берем файл dump($request->file(‘file’)); // Заголовок dump($request->header(‘name’)); return view(‘home’, [‘posts’ => Post::query()->active()->get()]) } }
return redirect(‘/’);
return response()->json([‘data’ => ‘’]);
Глава 14. Валидация
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class HomeController extends Controller { public function index(Request $request) { $validatedData = $request->validate([ 'title' => ['required', 'unique:posts', 'max:255'], 'body' => ['required'], ]); } }
<input type="text" name="title" /> @error(‘title’) {{ $message }} @enderror
php artisan make:request ContactForm
<?php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class ContactForm extends FormRequest { /** * Determine if the user is authorized to make this request. * * @return bool */ public function authorize() { return true; } /** * Get the validation rules that apply to the request. * * @return array<string, mixed> */ public function rules() { return [ 'title' => ['required', 'unique:posts', 'max:255'], 'body' => ['required'], ]; } }
<?php namespace App\Http\Controllers; use App\Http\Request\ContactForm; class HomeController extends Controller { public function index(ContactForm $request) {} }
Глава 15. Безопасность
$user[‘about’] = $_POST['about']; $query = UPDATE users SET about = ‘$user[‘about’]’;
"test" AND is_admin = "1’" $query = UPDATE users SET about = ‘$user[‘about’]’; // UPDATE users SET about = ‘test’ AND is_admin = 1
<script>alert('hello, I just hacked this page');</script>
{{ $user->about }}{!! $user->about !!} <form action="https://your-application.com/user/email" method="POST">
<input type="email" value="malicious-email@example.com">
</form><form method="POST" action="/profile"> <!-- Выведет hidden input с токеном как и пример ниже ... --> @csrf <!-- Тоже самое с помощью токена ... --> <input type="hidden" name="_token" value="{{ csrf_token() }}" /> </form>
session()->put(‘name’, ‘value’); // записали значение в сессию с ключом name session()->get(‘name’); // получили значение value if(session()->has(‘name’)) {} // проверили есть ли в сессиях такие данные
if(auth()->attempt([‘email’ => request(‘email’), ‘password’ => request(‘password’)])) { }
User::query()->create([ ‘email’ => request(‘email’), ‘password’ => Hash::make(‘password’) ])
auth()->logout()
auth()->login($user)
Auth::loginUsingId(1);
Route::get(‘/profile’, ProfileController::class)->middleware(‘auth’);
Route::get(‘/profile’, ProfileController::class)->middleware(‘guest’);
@auth
Я {{ auth()->user()->name }} и мой id - {{ auth()->id() }}
@endauth
@guest
А здесь будет кнопка "Войти"
@endguestphp artisan make:controller HomeController
php artisan make:cast PhoneCast php artisan make:request ContactFormRequest
php artisan serve
php artisan migrate
php artisan make:command CreateTestUse
<?php namespace App\Console\Commands; use Illuminate\Console\Command; class CreateTestUser extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'command:name'; /** * The console command description. * * @var string */ protected $description = 'Command description'; /** * Execute the console command. * * @return int */ public function handle() { return Command::SUCCESS; } }
php artisan command:name
<?php namespace App\Console\Commands; use Illuminate\Console\Command; class CreateTestUser extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'create:user'; /** * The console command description. * * @var string */ protected $description = 'Create test user'; /** * Execute the console command. * * @return int */ public function handle() { User::create([ ‘email’ => ‘test@example.com’ ]); return Command::SUCCESS; } }
php artisan create:user
Глава 19. Сид и нэнси? Почти! Сиды и Фабрики
namespace Database\Seeders; // use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { /** * Seed the application's database. * * @return void */ public function run() { // \App\Models\User::factory(10)->create(); } }
class DatabaseSeeder extends Seeder { /** * Seed the application's database. * * @return void */ public function run() { OrderStatus::query()->create([‘name’ => ‘new’]); } }
php artisan migrate –seed
php artisan db:seed
php artisan make:seeder OrderStatusSeeder
<?php namespace Database\Seeders; use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; class OrderStatusSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { DB::table(‘order_statuses’)->insert([‘id’ => 1, ‘name’ => ‘Новый’]); DB::table(‘order_statuses’)->insert([‘id’ => 2, ‘name’ => ‘В обработке’]); DB::table(‘order_statuses’)->insert([‘id’ => 3, ‘name’ => ‘Подтвержден’]); DB::table(‘order_statuses’)->insert([‘id’ => 4, ‘name’ => ‘Оплачен’]); } }
public function run() { $this->call([ OrderStatusSeeder::class, ]); }
php artisan db:seed --class=OrderStatusSeeder
php artisan migrate —-seed --seeder=OrderStatusSeeder
<?php namespace Database\Factories; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Str; /** * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User> */ class UserFactory extends Factory { /** * Define the model's default state. * * @return array<string, mixed> */ public function definition() { return [ 'name' => fake()->name(), 'email' => fake()->unique()->safeEmail(), 'email_verified_at' => now(), 'password' => '2ドルy10ドル92ドルIXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 'remember_token' => Str::random(10), ]; } /** * Indicate that the model's email address should be unverified. * * @return static */ public function unverified() { return $this->state(fn (array $attributes) => [ 'email_verified_at' => null, ]); } }
<?php namespace Database\Seeders; // use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { /** * Seed the application's database. * * @return void */ public function run() { \App\Models\User::factory(100)->create(); } }
php artisan db:seed
Глава 20. Тесты
php artisan make:test ArticlesTest
<?php namespace Tests\Feature; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; use Tests\TestCase; class ArticlesTest extends TestCase { /** * A basic feature test example. * * @return void */ public function testExample() { $response = $this->get('/'); $response->assertStatus(200); } } php artisan test --filter ArticlestTest
OK (1 test, 1 assertion)
public function testArticlesPage(){ $this->get('/articles) ->assertSee('All articles'); }