laravel 5.7 from Scratch.
Лекции по laravel от Scratch. Конспект. Эпизоды 8-. Всё по англицки - ни хера не понимаю.. буду излагать лапидарно.
конспектировал Капустин Яков
оглавление
- 01 Создание нового приложения scratch (laravel v5.7.19)
- 02 Создание новой миграции
- 03 Создание модели
- 04 Laravel Tinker Shell
- 05 Внесение изменений в базу данных через создание объекта
- 06 Чтение данных средствами tinker
- 07 MVC
- 08 Вызов artisan inspire
- 09 Вызов factory в tinker shell
- 10 Создание страницы добавления проектов
- 11 Изменение маршрутизации
- 12 Создание контроллера с типичными методами и моделью
- 13 Описание автоматически созданных методов контроллера
- 14 Создание уровня отображения
- 15 Cleaner Controllers and Mass Assign
- 16 Два уровня валидации формы
- 17 Eloquent отношения
- 18 Передача в контроллер объектов по id
- 19 Описание метода ProjectTasksController@store
- 20 RRR
- 21 RRR
- 22 RRR
- 23 RRR
- 24 RRR
- 25 RRR
- 26 RRR
- 27 RRR
01Создание нового приложения scratch (laravel v5.7.19)
bash:vagrant@homestead:~/projects$ composer create-project --prefer-dist laravel/laravel scratch Installing laravel/laravel (v5.7.19) - Installing laravel/laravel (v5.7.19): Loading from cache Created project in test > @php -r "file_exists('.env') || copy('.env.example', '.env');" Loading composer repositories with package information Updating dependencies (including require-dev) Package operations: 86 installs, 0 updates, 0 removals - Installing symfony/polyfill-ctype (v1.10.0): Loading from cache - Installing vlucas/phpdotenv (v2.6.1): Loading from cache - Installing symfony/css-selector (v4.2.3): Loading from cache - Installing tijsverkoyen/css-to-inline-styles (2.2.1): Loading from cache - Installing symfony/polyfill-php72 (v1.10.0): Loading from cache - Installing symfony/polyfill-mbstring (v1.10.0): Loading from cache - Installing symfony/var-dumper (v4.2.3): Loading from cache - Installing symfony/routing (v4.2.3): Loading from cache - Installing symfony/process (v4.2.3): Loading from cache - Installing symfony/http-foundation (v4.2.3): Loading from cache - Installing symfony/contracts (v1.0.2): Loading from cache - Installing symfony/event-dispatcher (v4.2.3): Loading from cache - Installing psr/log (1.1.0): Loading from cache - Installing symfony/debug (v4.2.3): Loading from cache - Installing symfony/http-kernel (v4.2.3): Loading from cache - Installing symfony/finder (v4.2.3): Loading from cache - Installing symfony/console (v4.2.3): Loading from cache - Installing doctrine/lexer (v1.0.1): Loading from cache - Installing egulias/email-validator (2.1.7): Loading from cache - Installing swiftmailer/swiftmailer (v6.1.3): Loading from cache - Installing paragonie/random_compat (v9.99.99): Loading from cache - Installing ramsey/uuid (3.8.0): Loading from cache - Installing psr/simple-cache (1.0.1): Loading from cache - Installing psr/container (1.0.0): Loading from cache - Installing opis/closure (3.1.5): Loading from cache - Installing symfony/translation (v4.2.3): Loading from cache - Installing nesbot/carbon (1.36.2): Loading from cache - Installing monolog/monolog (1.24.0): Loading from cache - Installing league/flysystem (1.0.50): Loading from cache - Installing erusev/parsedown (1.7.1): Loading from cache - Installing dragonmantank/cron-expression (v2.2.0): Loading from cache - Installing doctrine/inflector (v1.3.0): Loading from cache - Installing ralouphie/getallheaders (2.0.5): Loading from cache - Installing psr/http-message (1.0.1): Loading from cache - Installing guzzlehttp/psr7 (1.5.2): Loading from cache - Installing guzzlehttp/promises (v1.3.1): Loading from cache - Installing guzzlehttp/guzzle (6.3.3): Loading from cache - Installing laravel/slack-notification-channel (v1.0.3): Loading from cache - Installing laravel/framework (v5.7.26): Downloading (100%) - Installing lcobucci/jwt (3.2.5): Loading from cache - Installing php-http/promise (v1.0.0): Loading from cache - Installing php-http/httplug (v1.1.0): Loading from cache - Installing php-http/guzzle6-adapter (v1.1.1): Loading from cache - Installing zendframework/zend-diactoros (1.8.6): Loading from cache - Installing nexmo/client (1.6.2): Loading from cache - Installing laravel/nexmo-notification-channel (v1.0.1): Loading from cache - Installing fideloper/proxy (4.1.0): Loading from cache - Installing nikic/php-parser (v4.2.0): Loading from cache - Installing jakub-onderka/php-console-color (v0.2): Loading from cache - Installing jakub-onderka/php-console-highlighter (v0.4): Loading from cache - Installing dnoegel/php-xdg-base-dir (0.1): Loading from cache - Installing psy/psysh (v0.9.9): Loading from cache - Installing laravel/tinker (v1.0.8): Loading from cache - Installing beyondcode/laravel-dump-server (1.2.2): Loading from cache - Installing fzaninotto/faker (v1.8.0): Loading from cache - Installing hamcrest/hamcrest-php (v2.0.0): Loading from cache - Installing mockery/mockery (1.2.2): Downloading (100%) - Installing filp/whoops (2.3.1): Loading from cache - Installing nunomaduro/collision (v2.1.1): Loading from cache - Installing sebastian/version (2.0.1): Loading from cache - Installing sebastian/resource-operations (2.0.1): Loading from cache - Installing sebastian/object-reflector (1.1.1): Loading from cache - Installing sebastian/recursion-context (3.0.0): Loading from cache - Installing sebastian/object-enumerator (3.0.3): Loading from cache - Installing sebastian/global-state (2.0.0): Loading from cache - Installing sebastian/exporter (3.1.0): Loading from cache - Installing sebastian/environment (4.1.0): Loading from cache - Installing sebastian/diff (3.0.2): Loading from cache - Installing sebastian/comparator (3.0.2): Loading from cache - Installing phpunit/php-timer (2.0.0): Loading from cache - Installing phpunit/php-text-template (1.2.1): Loading from cache - Installing phpunit/php-file-iterator (2.0.2): Loading from cache - Installing theseer/tokenizer (1.1.0): Loading from cache - Installing sebastian/code-unit-reverse-lookup (1.0.1): Loading from cache - Installing phpunit/php-token-stream (3.0.1): Loading from cache - Installing phpunit/php-code-coverage (6.1.4): Loading from cache - Installing doctrine/instantiator (1.1.0): Loading from cache - Installing webmozart/assert (1.4.0): Loading from cache - Installing phpdocumentor/reflection-common (1.0.1): Loading from cache - Installing phpdocumentor/type-resolver (0.4.0): Loading from cache - Installing phpdocumentor/reflection-docblock (4.3.0): Loading from cache - Installing phpspec/prophecy (1.8.0): Loading from cache - Installing phar-io/version (2.0.1): Loading from cache - Installing phar-io/manifest (1.0.3): Loading from cache - Installing myclabs/deep-copy (1.8.1): Loading from cache - Installing phpunit/phpunit (7.5.5): Downloading (100%) symfony/routing suggests installing doctrine/annotations (For using the annotation loader) symfony/routing suggests installing symfony/config (For using the all-in-one router or any loader) symfony/routing suggests installing symfony/dependency-injection (For loading routes from a service) symfony/routing suggests installing symfony/expression-language (For using expression matching) symfony/routing suggests installing symfony/yaml (For using the YAML loader) symfony/contracts suggests installing psr/cache (When using the Cache contracts) symfony/contracts suggests installing symfony/cache-contracts-implementation symfony/contracts suggests installing symfony/service-contracts-implementation symfony/event-dispatcher suggests installing symfony/dependency-injection symfony/http-kernel suggests installing symfony/browser-kit symfony/http-kernel suggests installing symfony/config symfony/http-kernel suggests installing symfony/dependency-injection symfony/console suggests installing symfony/lock swiftmailer/swiftmailer suggests installing true/punycode (Needed to support internationalized email addresses, if ext-intl is not installed) paragonie/random_compat suggests installing ext-libsodium (Provides a modern crypto API that can be used to generate random bytes.) ramsey/uuid suggests installing ircmaxell/random-lib (Provides RandomLib for use with the RandomLibAdapter) ramsey/uuid suggests installing ext-libsodium (Provides the PECL libsodium extension for use with the SodiumRandomGenerator) ramsey/uuid suggests installing ext-uuid (Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator) ramsey/uuid suggests installing moontoast/math (Provides support for converting UUID to 128-bit integer (in string form).) ramsey/uuid suggests installing ramsey/uuid-doctrine (Allows the use of Ramsey\Uuid\Uuid as Doctrine field type.) ramsey/uuid suggests installing ramsey/uuid-console (A console application for generating UUIDs with ramsey/uuid) symfony/translation suggests installing symfony/config symfony/translation suggests installing symfony/yaml nesbot/carbon suggests installing friendsofphp/php-cs-fixer (Needed for the `composer phpcs` command. Allow to automatically fix code style.) nesbot/carbon suggests installing phpstan/phpstan (Needed for the `composer phpstan` command. Allow to detect potential errors.) monolog/monolog suggests installing aws/aws-sdk-php (Allow sending log messages to AWS services like DynamoDB) monolog/monolog suggests installing doctrine/couchdb (Allow sending log messages to a CouchDB server) monolog/monolog suggests installing ext-amqp (Allow sending log messages to an AMQP server (1.0+ required)) monolog/monolog suggests installing ext-mongo (Allow sending log messages to a MongoDB server) monolog/monolog suggests installing graylog2/gelf-php (Allow sending log messages to a GrayLog2 server) monolog/monolog suggests installing mongodb/mongodb (Allow sending log messages to a MongoDB server via PHP Driver) monolog/monolog suggests installing php-amqplib/php-amqplib (Allow sending log messages to an AMQP server using php-amqplib) monolog/monolog suggests installing php-console/php-console (Allow sending log messages to Google Chrome) monolog/monolog suggests installing rollbar/rollbar (Allow sending log messages to Rollbar) monolog/monolog suggests installing ruflin/elastica (Allow sending log messages to an Elastic Search server) monolog/monolog suggests installing sentry/sentry (Allow sending log messages to a Sentry server) league/flysystem suggests installing league/flysystem-aws-s3-v2 (Allows you to use S3 storage with AWS SDK v2) league/flysystem suggests installing league/flysystem-aws-s3-v3 (Allows you to use S3 storage with AWS SDK v3) league/flysystem suggests installing league/flysystem-azure (Allows you to use Windows Azure Blob storage) league/flysystem suggests installing league/flysystem-cached-adapter (Flysystem adapter decorator for metadata caching) league/flysystem suggests installing league/flysystem-eventable-filesystem (Allows you to use EventableFilesystem) league/flysystem suggests installing league/flysystem-rackspace (Allows you to use Rackspace Cloud Files) league/flysystem suggests installing league/flysystem-sftp (Allows you to use SFTP server storage via phpseclib) league/flysystem suggests installing league/flysystem-webdav (Allows you to use WebDAV storage) league/flysystem suggests installing league/flysystem-ziparchive (Allows you to use ZipArchive adapter) league/flysystem suggests installing spatie/flysystem-dropbox (Allows you to use Dropbox storage) league/flysystem suggests installing srmklive/flysystem-dropbox-v2 (Allows you to use Dropbox storage for PHP 5 applications) laravel/framework suggests installing aws/aws-sdk-php (Required to use the SQS queue driver and SES mail driver (^3.0).) laravel/framework suggests installing doctrine/dbal (Required to rename columns and drop SQLite columns (^2.6).) laravel/framework suggests installing league/flysystem-aws-s3-v3 (Required to use the Flysystem S3 driver (^1.0).) laravel/framework suggests installing league/flysystem-cached-adapter (Required to use the Flysystem cache (^1.0).) laravel/framework suggests installing league/flysystem-rackspace (Required to use the Flysystem Rackspace driver (^1.0).) laravel/framework suggests installing league/flysystem-sftp (Required to use the Flysystem SFTP driver (^1.0).) laravel/framework suggests installing moontoast/math (Required to use ordered UUIDs (^1.1).) laravel/framework suggests installing pda/pheanstalk (Required to use the beanstalk queue driver (^3.0).) laravel/framework suggests installing predis/predis (Required to use the redis cache and queue drivers (^1.0).) laravel/framework suggests installing pusher/pusher-php-server (Required to use the Pusher broadcast driver (^3.0).) laravel/framework suggests installing symfony/dom-crawler (Required to use most of the crawler integration testing tools (^4.1).) laravel/framework suggests installing symfony/psr-http-message-bridge (Required to psr7 bridging features (^1.0).) lcobucci/jwt suggests installing mdanter/ecc (Required to use Elliptic Curves based algorithms.) psy/psysh suggests installing ext-pdo-sqlite (The doc command requires SQLite to work.) psy/psysh suggests installing hoa/console (A pure PHP readline implementation. You'll want this if your PHP install doesn't already support readline or libedit.) filp/whoops suggests installing whoops/soap (Formats errors as SOAP responses) sebastian/global-state suggests installing ext-uopz (*) phpunit/php-code-coverage suggests installing ext-xdebug (^2.6.0) phpunit/phpunit suggests installing phpunit/php-invoker (^2.0) phpunit/phpunit suggests installing ext-xdebug (*) Writing lock file Generating optimized autoload files > Illuminate\Foundation\ComposerScripts::postAutoloadDump > @php artisan package:discover --ansi Discovered Package: beyondcode/laravel-dump-server Discovered Package: fideloper/proxy Discovered Package: laravel/nexmo-notification-channel Discovered Package: laravel/slack-notification-channel Discovered Package: laravel/tinker Discovered Package: nesbot/carbon Discovered Package: nunomaduro/collision Package manifest generated successfully. > @php artisan key:generate --ansi Application key set successfully. vagrant@homestead:~/projects$
02Создание новой миграции
В новом приложении 'scratch' (автор создаёт его с помощью команды 'laravel new project' на 16 секунде 7 эпизода) меняем базу данных на 'scratch_db' и создаём миграцию.
bash:vagrant@homestead:~/projects/scratch$ php artisan help make:migration Description: Create a new migration file Usage: make:migration [options] [--]
Arguments: name The name of the migration Options: --create[=CREATE] The table to be created --table[=TABLE] The table to migrate --path[=PATH] The location where the migration file should be created --realpath Indicate any provided migration file paths are pre-resolved absolute paths -h, --help Display this help message -q, --quiet Do not output any message -V, --version Display this application version --ansi Force ANSI output --no-ansi Disable ANSI output -n, --no-interaction Do not ask any interactive question --env[=ENV] The environment the command should run under -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug vagrant@homestead:~/projects/scratch$
Создаём миграцию 'create_projects_table':
bash:vagrant@homestead:~/projects/scratch$ php artisan make:migration create_projects_table Created Migration: 2019_02_08_235353_create_projects_table vagrant@homestead:~/projects/scratch$
и добавляем в метод 'up' создание дополнительных полей 'title' и 'description'.
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateProjectsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('projects', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
$table->string('description');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('projects');
}
}
bash:vagrant@homestead:~/projects/scratch$ php artisan migrate Migrating: 2019_02_08_235353_create_projects_table Migrated: 2019_02_08_235353_create_projects_table vagrant@homestead:~/projects/scratch$
03Создание модели
Эпизод 8. Ахтунг! Имя модели связано с именем созданной ранее миграции. Смотреть предыдущую лекцию, в которой её создали.
bash:vagrant@homestead:~/projects/scratch$ php artisan make:model Project Model created successfully. vagrant@homestead:~/projects/scratch$
04Laravel Tinker Shell
Laravel включает в себя мощный REPL (Read—Eval—Print—Loop) инструмент - Tinker, работающий под управлением PsySH. Tinker позволяет вам взаимодействовать с вашим laravel приложением из командной строки.
bash:vagrant@homestead:~/projects/scratch$ php artisan tinker Psy Shell v0.9.9 (PHP 7.3.1-1+ubuntu18.04.1+deb.sury.org+1 — cli) by Justin Hileman >>> 5-3 => 2 >>> App\Project::all(); => Illuminate\Database\Eloquent\Collection {#2908 all: [], } >>> // просмотр всех записей >>> // пока здесь пусто >>> >>> App\Project::first(); => null >>> App\Project::latest()->first(); => null >>>
05Внесение изменений в базу данных через создание объекта
Создаём экземпляр класса App\Project и присваиваем ему 'title' и 'description'.
bash:>>> $project=new App\Project; => App\Project {#2906} >>> $project->title='My first Project' => "My first Project" >>> // забыл ';', но tinker почему-то не ругнулся.. >>> >>> $project->description='Lorem Ipsum'; => "Lorem Ipsum" >>> >>> // запускаем?? >>> $project => App\Project {#2906 title: "My first Project", description: "Lorem Ipsum", } >>> >>> $project->save(); => true >>> >>> exit Exit: Goodbye vagrant@homestead:~/projects/scratch$ mysql Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 157 Server version: 5.7.25-0ubuntu0.18.04.2 (Ubuntu) Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> use scratch_db; Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed mysql> select * from project; ERROR 1146 (42S02): Table 'scratch_db.project' doesn't exist mysql> show tables; +----------------------+ | Tables_in_scratch_db | +----------------------+ | migrations | | password_resets | | projects | | users | +----------------------+ 4 rows in set (0.00 sec) mysql> select * from projects; +----+------------------+-------------+---------------------+---------------------+ | id | title | description | created_at | updated_at | +----+------------------+-------------+---------------------+---------------------+ | 1 | My first Project | Lorem Ipsum | 2019-02-09 01:01:29 | 2019-02-09 01:01:29 | +----+------------------+-------------+---------------------+---------------------+ 1 row in set (0.00 sec) mysql> Bye vagrant@homestead:~/projects/scratch$
06Чтение данных средствами tinker
bash:vagrant@homestead:~/projects/scratch$ php artisan tinker Psy Shell v0.9.9 (PHP 7.3.1-1+ubuntu18.04.1+deb.sury.org+1 — cli) by Justin Hileman >>> App\Project::first(); => App\Project {#2911 id: 1, title: "My first Project", description: "Lorem Ipsum", created_at: "2019-02-09 01:01:29", updated_at: "2019-02-09 01:01:29", } >>> >>> App\Project::first()->title; => "My first Project" >>> App\Project::first()->description; => "Lorem Ipsum" >>> >>> App\Project::all(); => Illuminate\Database\Eloquent\Collection {#2906 all: [ App\Project {#2905 id: 1, title: "My first Project", description: "Lorem Ipsum", created_at: "2019-02-09 01:01:29", updated_at: "2019-02-09 01:01:29", }, ], } >>> >>> >>> >>> $project=new App\Project; => App\Project {#2911} >>> $project->title='Second Project' => "Second Project" >>> $project->description='description second project' => "description second project" >>> App\Project::all(); => Illuminate\Database\Eloquent\Collection {#2896 all: [ App\Project {#2900 id: 1, title: "My first Project", description: "Lorem Ipsum", created_at: "2019-02-09 01:01:29", updated_at: "2019-02-09 01:01:29", }, ], } >>> $project->save(); => true >>> App\Project::all(); => Illuminate\Database\Eloquent\Collection {#2913 all: [ App\Project {#2902 id: 1, title: "My first Project", description: "Lorem Ipsum", created_at: "2019-02-09 01:01:29", updated_at: "2019-02-09 01:01:29", }, App\Project {#2914 id: 2, title: "Second Project", description: "description second project", created_at: "2019-02-09 01:17:54", updated_at: "2019-02-09 01:17:54", }, ], } >>> >>> >>> App\Project::all()[0]; => App\Project {#2916 id: 1, title: "My first Project", description: "Lorem Ipsum", created_at: "2019-02-09 01:01:29", updated_at: "2019-02-09 01:01:29", } >>> App\Project::all()[1]; => App\Project {#2906 id: 2, title: "Second Project", description: "description second project", created_at: "2019-02-09 01:17:54", updated_at: "2019-02-09 01:17:54", } >>> App\Project::all()[2]; PHP Notice: Undefined offset: 2 in /home/vagrant/projects/scratch/vendor/laravel/framework/src/Illuminate/Support/Collection.php on line 1924 >>> App\Project::all()[1]->title; => "Second Project" >>> App\Project::all()-map->title; PHP Parse error: Syntax error, unexpected T_OBJECT_OPERATOR on line 1 >>> App\Project::all()->map->title; => Illuminate\Support\Collection {#2916 all: [ "My first Project", "Second Project", ], } >>>
07MVC
Создадим новый маршрут:
2
3
4
5
...
Route::get('/projects', 'ProjectsController@index');
...
.. новый контроллер:
bash:vagrant@homestead:~/projects/scratch$ php artisan make:controller ProjectsController Controller created successfully. vagrant@homestead:~/projects/scratch$
02
03
04
05
06
07
08
09
10
11
12
13
14
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ProjectsController extends Controller
{
public function index(){
return view('projects.index');
}
}
и новый вид:
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>New Project</h1>
<?php
echo __FILE__;
?>
</body>
</html>
Перейдём по ссылке http://laravel.scratch/projects:
Добавим в метод созданного контроллера немного логики, вместо вида возвратив значение переменной $project:
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use \App\Project;
class ProjectsController extends Controller
{
public function index(){
// $project = \App\Project::all();
// после добавления пространства имен:
// use \App\Project;
// можно использовать запись ниже
$project = Project::all();
//return view('projects.index');
return $project;
}
}
На выходе мы получим json, для читабельного отображения которого установим в браузере расширение JSON Formatter (Offered by: Nik Rolls):
Теперь изменим код контроллера, вернув вид, передав в него во втором аргументе массив полученных объектов:
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use \App\Project;
class ProjectsController extends Controller
{
public function index(){
// $project = \App\Project::all();
// после добавления пространства имен:
// use \App\Project;
// можно использовать запись ниже
$projects = Project::all();
// return view('projects.index', ['projects'=>$projects]);
return view('projects.index', compact('projects'));
}
}
Добавим во view вызов переданного аргумента:
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>New Project</h1>
<?php
// echo __FILE__;
?>
@foreach($projects as $project)
<li>{{ $project->title }}</li>
@endforeach
</body>
</html>
08Вызов artisan inspire
bash:vagrant@homestead:~/projects/scratch$ php artisan inspire Simplicity is the ultimate sophistication. - Leonardo da Vinci vagrant@homestead:~/projects/scratch$ php artisan inspire Well begun is half done. - Aristotle vagrant@homestead:~/projects/scratch$ php artisan inspire It is quality rather than quantity that matters. - Lucius Annaeus Seneca vagrant@homestead:~/projects/scratch$
09Вызов factory в tinker shell
bash:vagrant@homestead:~/projects/scratch$ php artisan tinker Psy Shell v0.9.9 (PHP 7.3.1-1+ubuntu18.04.1+deb.sury.org+1 — cli) by Justin Hileman >>> >>> factory(App\User::class)->make(); => App\User {#2931 name: "Kayley Schuster", email: "meda18@example.com", email_verified_at: Illuminate\Support\Carbon @1549712891 {#2927 date: 2019-02-09 11:48:11.198086 UTC (+00:00), }, } >>> factory(App\User::class)->make(); => App\User {#2925 name: "Mr. Johnny Sporer Sr.", email: "tracey66@example.org", email_verified_at: Illuminate\Support\Carbon @1549712896 {#2926 date: 2019-02-09 11:48:16.349419 UTC (+00:00), }, } >>> factory(App\User::class)->make(); => App\User {#2932 name: "Alfonzo McKenzie", email: "ellsworth.carter@example.com", email_verified_at: Illuminate\Support\Carbon @1549712899 {#2924 date: 2019-02-09 11:48:19.087973 UTC (+00:00), }, } >>> factory(App\User::class)->create(); => App\User {#2930 name: "Lonie Baumbach", email: "brianne77@example.org", email_verified_at: Illuminate\Support\Carbon @1549713020 {#2922 date: 2019-02-09 11:50:20.175508 UTC (+00:00), }, updated_at: "2019-02-09 11:50:20", created_at: "2019-02-09 11:50:20", id: 1, } >>> exit Exit: Goodbye vagrant@homestead:~/projects/scratch$ mysql Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 168 Server version: 5.7.25-0ubuntu0.18.04.2 (Ubuntu) Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> use scratch_db; Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed mysql> select * from users; +----+----------------+-----------------------+---------------------+------------+----------------+---------------------+---------------------+ | id | name | email | email_verified_at | password | remember_token | created_at | updated_at | +----+----------------+-----------------------+---------------------+------------+----------------+---------------------+---------------------+ | 1 | Lonie Baumbach | brianne77@example.org | 2019-02-09 11:50:20 | $2y$10$T.. | pZb9HTRaWR | 2019-02-09 11:50:20 | 2019-02-09 11:50:20 | +----+----------------+-----------------------+---------------------+------------+----------------+---------------------+---------------------+ 1 row in set (0.00 sec) mysql>
10Создание страницы добавления проектов
Эпизод 10. Добавим маршруты на создаваемую страницу с формой и для сохранения данных в базу:
2
3
4
5
6
7
8
...
Route::get('/projects', 'ProjectsController@index');
Route::post('/projects', 'ProjectsController@save');
Route::get('/projects/create', 'ProjectsController@create');
...
В контроллере пропишем новый метод:
02
03
04
05
06
07
08
09
10
11
12
13
14
15
...
public function save(){
$project=new Project;
$project->title=request('title');
$project->description=request('description');
$project->save();
return redirect('/projects');
}
...
И создадим новый вид, в котором создадим форму с action='/project' и скрытыми полями для защиты от CSRF-атаки и передачи метода. Ниже представлены равноценные способы:
2
3
4
5
6
7
...
<form>
{{ csrf_field() }}
{{ method_field('DELETE') }}
...
2
3
4
5
6
7
...
<form>
@csrf
@method('DELETE')
...
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>Create Project</h1>
<form action='/projects' method='post'>
{{ csrf_field() }}
Title Project: <input type='text' name='title'><br>
Description Project: <input type='text' name='description'><br>
<input type='submit' value='Submit'>
</form>
</body>
</html>
11Изменение маршрутизации
Заменим объявление маршрута на следующую конструкцию, обеспечивающую создание типичных роутов:
2
3
4
5
6
...
// Route::get('/projects', 'ProjectsController@index');
Route::resource('projects', 'ProjectsController');
...
bash:vagrant@homestead:~/projects/scratch$ php artisan route:list +--------+-----------+-------------------------+------------------+-------------------------------------------------+--------------+ | Domain | Method | URI | Name | Action | Middleware | +--------+-----------+-------------------------+------------------+-------------------------------------------------+--------------+ | | GET|HEAD | / | | Closure | web | | | GET|HEAD | api/user | | Closure | api,auth:api | | | GET|HEAD | projects | projects.index | App\Http\Controllers\ProjectsController@index | web | | | POST | projects | projects.store | App\Http\Controllers\ProjectsController@store | web | | | GET|HEAD | projects/create | projects.create | App\Http\Controllers\ProjectsController@create | web | | | GET|HEAD | projects/{project} | projects.show | App\Http\Controllers\ProjectsController@show | web | | | PUT|PATCH | projects/{project} | projects.update | App\Http\Controllers\ProjectsController@update | web | | | DELETE | projects/{project} | projects.destroy | App\Http\Controllers\ProjectsController@destroy | web | | | GET|HEAD | projects/{project}/edit | projects.edit | App\Http\Controllers\ProjectsController@edit | web | +--------+-----------+-------------------------+------------------+-------------------------------------------------+--------------+ vagrant@homestead:~/projects/scratch$
12Создание контроллера с типичными методами и моделью
Если при создании контроллера воспользоваться ключами, то можно создать одной командой контроллер с предварительно объявленными методами и модель для него. Удаляем созданные ранее ProjectsController с моделью Project и создаём их заново:
bash:vagrant@homestead:~/projects/scratch$ php artisan make:controller ProjectsController -r -m Project A App\Project model does not exist. Do you want to generate it? (yes/no) [yes]: > Model created successfully. Controller created successfully. vagrant@homestead:~/projects/scratch$
Содержимое созданных файлов:
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
<?php
namespace App\Http\Controllers;
use App\Project;
use Illuminate\Http\Request;
class ProjectsController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
//
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*
* @param \App\Project $project
* @return \Illuminate\Http\Response
*/
public function show(Project $project)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Project $project
* @return \Illuminate\Http\Response
*/
public function edit(Project $project)
{
//
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\Project $project
* @return \Illuminate\Http\Response
*/
public function update(Request $request, Project $project)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param \App\Project $project
* @return \Illuminate\Http\Response
*/
public function destroy(Project $project)
{
//
}
}
02
03
04
05
06
07
08
09
10
11
12
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Project extends Model
{
//
}
13Описание автоматически созданных методов контроллера
13 эпизод. Опишем методы в классе ProjectsController:
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
<?php
namespace App\Http\Controllers;
use App\Project;
use Illuminate\Http\Request;
class ProjectsController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$projects = Project::all();
return view('projects.index', compact('projects'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('projects.create');
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$project = new Project;
$project->title = request('title');
$project->description = request('description');
$project->save();
return redirect('/projects');
}
/**
* Display the specified resource.
*
* @param \App\Project $project
* @return \Illuminate\Http\Response
*/
public function show(Project $project)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Project $project
* @return \Illuminate\Http\Response
*/
public function edit(Project $project)
{
// dd($project);
// $project = Project::findOrFail($id);
return view('projects.edit', compact('project'));
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\Project $project
* @return \Illuminate\Http\Response
*/
public function update(Request $request, Project $project)
{
$project->title = request('title');
$project->description = request('description');
$project->save();
return redirect('/projects');
}
/**
* Remove the specified resource from storage.
*
* @param \App\Project $project
* @return \Illuminate\Http\Response
*/
public function destroy(Project $project)
{
$project->delete();
return redirect('/projects');
}
}
14Создание уровня отображения
В директории resources/views создаем resources/views/layout.blade.php и поддиректорию resources/views/projects, в которой описываем подключаемые файлы. Привожу окончательный вариант. P.S.: bulma.css - гавно.
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.4/css/bulma.min.css">
<script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
</head>
<body>
<div class='container'>
@yield('content')
</div>
</body>
</html>
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
@extends('layout')
@section('content')
<h1 class='title' style='margin:2em 0;'>List projects (index.blade.php)</h1>
<ul>
@foreach($projects as $project)
<li><a href='/projects/{{ $project->id }}'>{{ $project->title }}</a></li>
@endforeach
</ul>
<a href='/projects/create'>
<button class="button is-info" form="update" style='margin:2em 0;'>Create new Project</button>
</a>
@endsection
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@extends('layout')
@section('content')
<h1 class='title' style='margin:2em 0;'>Create Project (create.blade.php)</h1>
<form action='/projects' method='POST' id='update' style='margin:2em 0;'>
{{ csrf_field() }}
<div class="field">
<label class="label">title</label>
<p class="control">
<input class="input" type="text" name='title' placeholder='Enter the title project' value='' required>
</p>
</div>
<div class="field">
<label class="label">Description</label>
<p class="control">
<textarea class="textarea" name='description' placeholder='Enter the description project' required></textarea>
</p>
</div>
</form>
<div class="field is-grouped">
<p class="control">
<button class="button is-success" form="update">Create Project</button>
</p>
<p class="control">
<button class="button is-info" form="update">Cancel</button>
</p>
</div>
@endsection
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
@extends('layout')
@section('content')
<h1 class='title' style='margin:2em 0;'>Show Project "{{ $project->title }} (show.blade.php)"</h1>
<div class='content'>
{{ $project->description }}
</div>
<a href='/projects/{{ $project->id }}/edit'>
<button class="button is-success" form="update">Update Project</button>
</a>
<a href='/projects'>
<button class="button is-info" form="update">back to list</button>
</a>
@endsection
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
@extends('layout')
@section('content')
<h1 class='title' style='margin:2em 0;'>Edit Project (edit.blade.php)</h1>
<form action='/projects/{{ $project->id }}' method='POST' id='update' style='margin:2em 0;'>
{{ csrf_field() }}
{{ method_field('PATCH') }}
<div class="field">
<label class="label">title</label>
<p class="control">
<input class="input" type="text" name='title' value='{{ $project->title }}' required>
</p>
</div>
<div class="field">
<label class="label">Description</label>
<p class="control">
<textarea class="textarea" name='description' required>{{ $project->description }}</textarea>
</p>
</div>
</form>
<form action='/projects/{{ $project->id }}' method='POST' id='delete'>
@csrf
@method('DELETE')
</form>
<div class="field is-grouped">
<p class="control">
<button class="button is-success" form="update">Update Project</button>
</p>
<p class="control">
<button class="button is-danger" form="delete">Delete Project</button>
</p>
<p class="control">
<button class="button is-info" form="update">Cancel</button>
</p>
</div>
@endsection
15Cleaner Controllers and Mass Assign
14 эпизод.
Очистка метода edit()
Как я понял, в коде выше в описании метода edit() уже чист. Ниже приведу пример нечистого, где аргументом передаётся id проекта и для получения экземпляра объекта необходимо вызвать findOrFail() (закомментированый участок кода) и эквивалентного ему чистого, где экземпляр объекта передаётся в качестве аргумента метода:
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
...
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
// public function edit($id)
// {
// $project = Project::findOrFail($id);
//
// return view('projects.edit', compact('project'));
// }
public function edit(Request $request)
{
return view('projects.edit', compact('project'));
}
...
Очистка метода store()
Также можно описать более чисто метод store(), но, в случае вызова create() со всеми пришедшими из формы данными необходимо обезопасить приложение, указав в модели перезаписываемые поля:
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
...
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
// Project::create([
// 'title' => request('title'),
// 'description' => request('description'),
// ]);
Project::create(request()->all());
return redirect('/projects');
}
...
02
03
04
05
06
07
08
09
10
11
12
13
14
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Project extends Model
{
protected $fillable = [
'title', 'description',
];
}
либо передав в request() в качестве аргумента массив с именами разрешенных к изменению полей (scratch остановился на этом варианте):
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
...
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
Project::create(request(['title', 'description',]));
return redirect('/projects');
}
...
02
03
04
05
06
07
08
09
10
11
12
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Project extends Model
{
protected $guarded = [];
}
Очистка метода update()
Аналогично поступим с методом update():
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
...
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\Project $project
* @return \Illuminate\Http\Response
*/
public function update(Request $request, Project $project)
{
// $project->title = request('title');
// $project->description = request('description');
// $project->save();
$project->update(request(['title', 'description',]));
return redirect('/projects');
}
...
Очищенный контроллер
Содержимое очищенного контроллера:
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
<?php
namespace App\Http\Controllers;
use App\Project;
use Illuminate\Http\Request;
class ProjectsController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$projects = Project::all();
return view('projects.index', compact('projects'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('projects.create');
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
Project::create(request(['title', 'description',]));
return redirect('/projects');
}
/**
* Display the specified resource.
*
* @param \App\Project $project
* @return \Illuminate\Http\Response
*/
public function show(Project $project)
{
return view('projects.show', compact('project'));
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Project $project
* @return \Illuminate\Http\Response
*/
public function edit(Project $project)
{
return view('projects.edit', compact('project'));
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\Project $project
* @return \Illuminate\Http\Response
*/
public function update(Request $request, Project $project)
{
$project->update(request(['title', 'description',]));
return redirect('/projects');
}
/**
* Remove the specified resource from storage.
*
* @param \App\Project $project
* @return \Illuminate\Http\Response
*/
public function destroy(Project $project)
{
$project->delete();
return redirect('/projects');
}
}
16Два уровня валидации формы
15 эпизод.
В уровне отображения для защиты от отправки пустых значений используется дополнительный атрибут required, но от особо умных пользователей необходима защита и на стороне контроллера. Она реализуется с помощью метода validate().
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
...
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
request()->validate([
'title' => 'require',
'description' => 'require',
]);
Project::create(request(['title', 'description',]));
return redirect('/projects');
}
...
При срабатывании метода validate() контроллер возвращает редирект на $_SERVER[referer] с передачей в качестве аргумента масива $errors (?). Опишем d файле create.blade.php элемент, в котором будут выводиться ошибки:
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
@extends('layout')
@section('content')
<h1 class='title' style='margin:2em 0;'>Create Project (create.blade.php)</h1>
<form action='/projects' method='POST' id='update' style='margin:2em 0;'>
{{ csrf_field() }}
<div class="field">
<label class="label">title</label>
<p class="control">
<input
class="input{{ $errors->has('title') ? ' is-danger':'' }}"
type="text"
name='title'
placeholder='Enter the title project'
value='{{ old('title') }}'>
</p>
</div>
<div class="field">
<label class="label">Description</label>
<p class="control">
<textarea
class="textarea{{ $errors->has('description') ? ' is-danger':'' }}"
name='description'
placeholder='Enter the description project'>{{ old('description') }}</textarea>
</p>
</div>
</form>
<div class="field is-grouped">
<p class="control">
<button class="button is-success" form="update">Create Project</button>
</p>
<p class="control">
<button class="button is-info" form="update">Cancel</button>
</p>
</div>
@if($errors->any())
<div class='notification is-danger'>
<ul>
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
@endsection
Временно удалим аттрибут required у элемента textarea и проверим корректность срабатывания validate(), отправив на сервер пустое значение description:
Полный список правил валидации представлен в официальной документации.
17Eloquent отношения
16 эпизод.
Воспользуемся ключами и создадим модель Task вместе с фабрикой и файлом миграции:
bash:vagrant@homestead:~/projects/scratch$ php artisan make:model Task -m -f Model created successfully. Factory created successfully. Created Migration: 2019_02_17_202511_create_tasks_table vagrant@homestead:~/projects/scratch$
Опишем и выполним миграцию:
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateTasksTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('tasks', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('project_id');
$table->text('description');
$table->boolean('completed')->default(false);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('tasks');
}
}
bash:vagrant@homestead:~/projects/scratch$ php artisan migrate Migrating: 2019_02_17_202511_create_tasks_table Migrated: 2019_02_17_202511_create_tasks_table vagrant@homestead:~/projects/scratch$
Опишем отношение в модели Project:
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Project extends Model
{
// protected $fillable = [
// 'title', 'description',
// ];
protected $guarded = [];
public function tasks()
{
return $this->hasMany(Task::class);
}
}
Создадим запись в таблице tasks с помощью tinker и вызовем описанный в модели Project метод tasks():
bash:vagrant@homestead:~/projects/scratch$ php artisan tinker Psy Shell v0.9.9 (PHP 7.3.1-1+ubuntu18.04.1+deb.sury.org+1 — cli) by Justin Hileman >>> App\Project::first(); => App\Project {#2918 id: 1, title: "First Project", description: "Description for First Project.", created_at: "2019-02-16 19:56:39", updated_at: "2019-02-17 14:53:53", } >>> // вызовем описанный выше метод task (почему метод без круглых скобок???): >>> App\Project::first()->tasks; => Illuminate\Database\Eloquent\Collection {#2912 all: [], } >>> // tinker вернул пустую коллекцию >>> // добавим запись в таблицу tasks: >>> $task = new App\Task; => App\Task {#2918} >>> $task->project_id = 2; => 2 >>> $task->description = 'description for task project with id=2'; => "description for task project with id=2" >>> $task->save(); => true >>> >>> // попробуем еще раз: >>> App\Project::first()->tasks; => Illuminate\Database\Eloquent\Collection {#2911 all: [], } >>> >>> // странно.. >>> App\Task::all(); => Illuminate\Database\Eloquent\Collection {#2912 all: [ App\Task {#2921 id: 1, project_id: 2, description: "description for task project with id=2", completed: 0, created_at: "2019-02-17 20:53:48", updated_at: "2019-02-17 20:53:48", }, ], } >>> // Семён Семёныч! мы же первый экземпляр вызываем! >>> $task = new App\Task; => App\Task {#2917} >>> $task->project_id = 1; => 1 >>> $task->save(); Illuminate/Database/QueryException with message 'SQLSTATE[HY000]: General error: 1364 Field 'description' doesn't have a default value (SQL: insert into `tasks` (`project_id`, `updated_at`, `created_at`) values (1, 2019-02-17 21:39:46, 2019-02-17 21:39:46))' >>> $task->description = 'description for task project with id=1'; => "description for task project with id=1" >>> $task->save(); => true >>> >>> // контрольный >>> App\Project::first()->tasks; => Illuminate\Database\Eloquent\Collection {#2926 all: [ App\Task {#2914 id: 2, project_id: 1, description: "description for task project with id=1", completed: 0, created_at: "2019-02-17 21:39:46", updated_at: "2019-02-17 21:39:46", }, ], } >>> >>> >>> >>> // создадим еще одну задачу для project_id = 1 >>> $task = new App\Task; => App\Task {#2912} >>> $task->description = 'description for another task project with id=1'; => "description for another task project with id=1" >>> $task->project_id = 1; => 1 >>> $task->save(); => true >>> // просмотрим возвращаемое значение: >>> App\Project::first()->tasks; => Illuminate\Database\Eloquent\Collection {#2920 all: [ App\Task {#2915 id: 2, project_id: 1, description: "description for task project with id=1", completed: 0, created_at: "2019-02-17 21:39:46", updated_at: "2019-02-17 21:39:46", }, App\Task {#2924 id: 3, project_id: 1, description: "description for another task project with id=1", completed: 0, created_at: "2019-02-17 22:34:12", updated_at: "2019-02-17 22:34:12", }, ], } >>>
Отобразим поставленные задачи:
02
03
04
05
06
07
08
09
10
11
12
13
...
@if($project->tasks->count())
<div class='content'>
@foreach($project->tasks as $task)
<ul>
<li>{{ $task->description }}</li>
</ul>
@endforeach
</div>
@endif
...
Опишем обратное отношение в модели Task:
02
03
04
05
06
07
08
09
10
11
12
13
14
15
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Task extends Model
{
public function project()
{
return $this->belongsTo(Project::class);
}
}
Ахтунг! Tinker увидел его только после перезагрузки:
bash:>>> App\Task::first()->projects; => null >>> App\Task::first(); => App\Task {#2922 id: 1, project_id: 2, description: "description for task project with id=2", completed: 0, created_at: "2019-02-17 20:53:48", updated_at: "2019-02-17 20:53:48", } >>> exit Exit: Goodbye vagrant@homestead:~/projects/scratch$ vagrant@homestead:~/projects/scratch$ php artisan tinker Psy Shell v0.9.9 (PHP 7.3.1-1+ubuntu18.04.1+deb.sury.org+1 — cli) by Justin Hileman >>> // можно вызвать непосредственно через (модель?) App\Task: >>> App\Task::first()->project; => App\Project {#2919 id: 2, title: "Second Project updated2", description: "Description for Second Project.", created_at: "2019-02-16 19:58:50", updated_at: "2019-02-17 14:54:04", } >>> // или через $task: >>> $task = App\Task::first(); => App\Task {#2923 id: 1, project_id: 2, description: "description for task project with id=2", completed: 0, created_at: "2019-02-17 20:53:48", updated_at: "2019-02-17 20:53:48", } >>> $task->project; => App\Project {#2922 id: 2, title: "Second Project updated2", description: "Description for Second Project.", created_at: "2019-02-16 19:58:50", updated_at: "2019-02-17 14:54:04", } >>>
18Передача в контроллер объектов по id
17 эпизод. Создаём контроллер ProjectTasksController.
bash:vagrant@homestead:~/projects/scratch$ php artisan make:controller ProjectTasksController -r Controller created successfully. vagrant@homestead:~/projects/scratch$
Так как при создании файла был использован ключ '-r', в описании класса уже присутствуют заготовки стандартных методов. В методе 'update' будет производиться перезапись объекта класса Task.
Для того, чтобы принять в метод не id (по-умолчанию), а сразу необходимый объект (экземпляр класса Task), необходимо импортировать (в пространство имен?) модель Task и изменить аргументы метода следующим образом:
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Task; // 1
class ProjectTasksController extends Controller
{
...
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
//public function update(Request $request, $id)
public function update(Task $task) // 2
{
// dd($task); // Task {#222 ▶}
// dd(request()->all()); // array:3 [▼
// // "_method" => "PATCH"
// // "_token" => "gOQD6zRrSpXwbDYK6ce7jy340WCDXDR7aV17t9Yj"
// // "completed" => "on"
// // ]
$task->update([
'completed' => request()->has('completed'),
]);
return back(); // or 'return redirect('/projects');'
}
...
}
Добавим поле 'completed' в свойство $fillable для снятия его защиты от массового заполнения:
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Task extends Model
{
public $fillable = ['completed'];
public function project()
{
return $this->belongsTo(Project::class);
}
}
Добавим роут:
2
3
4
...
Route::patch('task/{task}', 'ProjectTasksController@update');
Улучшим отображение, воспользовавшись так внезапно открывшимися возможностями:
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@extends('layout')
@section('content')
<h1 class='title' style='margin:2em 0;'>Show Project "{{ $project->title }} (show.blade.php)"</h1>
<div class='content'>
{{ $project->description }}
</div>
@if($project->tasks->count())
<!--div class='content'>
@foreach($project->tasks as $task)
<ul>
<li>{{ $task->description }}</li>
</ul>
@endforeach
</div-->
<div class='content'>
@foreach($project->tasks as $task)
<form method='POST' action='/tasks/{{ $task->id }}'>
@method('PATCH')
@csrf
<label class='checkbox' for='completed'{!!
$task->completed ? "style='text-decoration:line-through;'" : ''
!!}>
<input
type='checkbox'
name='completed'
onChange='this.form.submit()'{{ $task->completed ? 'checked' : ''}}>
{{ $task->description }}
</label>
</form>
@endforeach
</div>
@endif
<a href='/projects/{{ $project->id }}/edit'>
<button class="button is-success" form="update">Update Project</button>
</a>
<a href='/projects'>
<button class="button is-info" form="update">back to list</button>
</a>
@endsection
19Описание метода ProjectTasksController@store
18 эпизод.
Добавим роут post для контроллера ProjectTasksController:
2
3
4
5
...
Route::patch('/tasks/{task}', 'ProjectTasksController@update');
Route::post('/projects/{project}/tasks', 'ProjectTasksController@store');
Отредактируем метод store контроллера ProjectTasksController (пример из видео не сработал. разобраться почему. Выдаёт ошибку: SQLSTATE[HY000]: General error: 1364 Field 'project_id' doesn't have a default value (SQL: insert into `tasks` (`updated_at`, `created_at`) values (2019-02-26 00:09:11, 2019-02-26 00:09:11))):
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
...
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
// public function store(Request $request)
public function store(Project $project)
{
// dd(request('description'));
// dd($project->id);
// почему не работает пример из видео??? (https://laracasts.com/series/laravel-from-scratch-2018/episodes/18 03:20)
// Task::create([
// 'project_id' => $project->id,
// 'description' => request('description'),
// ]);
$task = new Task;
$task->project_id = $project->id;
$task->description = request('description');
$task->save();
return back();
}
...
Отредактируем вид, добавив форму для добавления задач проекта:
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div class='content'>{{-- add new task --}}
<form method='POST' action='/projects/{{ $project->id }}/tasks' class='box'>
<div class='field'>
@csrf
<label class='label' for='description'>New Task:</label>
<div class='control'>
<input type='text' class='input' name='description'>
</div>
</div>
<div class='field'>
<div class='control'>
<button type='submit' class='button is-link'>add Task</button>
</div>
</div>
</form>
</div>{{-- /add new task --}}
Капустин Яков (2019.02.09 14:12)