해리의 데브로그

Laravel 5.7 From Scratch 06 - Routing Conventions / Faking PATCH & DELETE request

|

1. Routing Conventions Worth Following

Laravel에서 권장하는 Routing Conventions에 따라 routing을 아래와 같이 RESTful하게 설정 할 수 있음.

Method URI Controller Method
GET /projects index
GET /projects/create create
GET /projects/1 show
POST /projects store
GET /projects/1/edit edit
PATCH /projects/1 update
DELETE /projects/1 destory

이에 따라 routing 코드를 web.php에 작성하면 다음과 같을 수 있음. 또한, 아래 코드는 resource 라는 스태틱 메서드를 이용하여 shortcut으로 작성 할 수도 있다. php artisan route:list 로 등록된 routes를 비교해보면 똑같음을 알 수 있다.

Route::get('/projects', 'ProjectsController@index');
Route::get('/projects/create', 'ProjectsController@create');
Route::get('/projects/{project}', 'ProjectsController@show');
Route::post('/projects', 'ProjectsController@store');
Route::get('/projects/{project}/edit', 'ProjectsController@edit');
Route::patch('/projects/{project}', 'ProjectsController@update');
Route::delete('/projects/{project}', 'ProjectsController@destroy');

//shortcut - resource. first parameter shall be thing we are manipulating
Route::resource('projects','ProjectsController')

2. resource 자동 생성

php artisan make:controller PostController -r : create all resourceable method for specific controller

php artisan make:controller PostController -r -m Post : create Model & allocate model object to the parameter of each method properly

// example of php artisan make:controller PostController -r
<?php

namespace App\Http\Controllers;
use Illuminate\Http\Request;
class PostController extends Controller

{
    public function show($id)
    {
        //
    }
}

// example of php artisan make:controller PostController -r -m Post
<?php

namespace App\Http\Controllers;
// added Model Post
use App\Post;
use Illuminate\Http\Request;
class PostController extends Controller

{
    public function show(Post $post)
    {
        //
    }
}

3. Faking PATCH and DELETE Requests

routes(Web.php)에 명시된 Restful하게 설계된 URI에 따라, 수정, 삭제에 대한 로직을 MVC 패턴으로 구현 할 수 있음.

Route::get('/projects/{project}/edit', 'ProjectsController@edit');
Route::patch('projects/{project}', 'ProjectsController@update');
Route::delete('projects/{project}', 'ProjectsController@destroy');

ProjectsController.php 내 edit에 대한 메서드 구현. 특정 값에 대하여 수정하는 것이 일반적이므로 기준이 되는 $id 를 인자로 받아 ::find 를 통해 해당되는 오브젝트를 찾아줌. 이후 오브젝트를 compact() 로 감싸, 전체 데이터를 템플릿으로 넘김.

update 메서드는 edit 템플릿에서 수정된 내용을 받아와 데이터베이스에 저장 & redirect 등을 설정하는 함수.

public function edit($id)
{
  $project = Project::find($id);

  return view('projects.edit', compact('project'))
}

public function update($id)
{
  $project = Project::find($id);
  $project->title = request('title');
  $project->description = request('description');

  $project->save();

  return redirect('/projects');
}

4. base template 설정

edit.blade.php 작성에 앞서 베이스 템플릿의 역할을 하는 layout.blade.php를 먼저 작성 한 후, 공통되는 영역을 상속 받음.

  • @yield('content') : 상속 받은 템플릿의 코드가 삽입되는 부분 in layout.blade.php
  • @extends('layout') : 상속받을 템플릿 명 입력(최상단에)
  • @section('content') @endsection : 코드 입력
<!doctype html>
<html lang="en">
<head>
    <title>Document</title>
</head>
<body>
    <div class="container">
        @yield('content')
    </div>
</body>
</html>

5. method_field('PATCH')

수정 페이지에서는 당연히 기존의 데이터가 보여야하므로, input 태그의 value 값과, textarea 태그 안에 각각의 값을 {{ }} 으로 표현.

수정에 대한 HTTP Method는 PATCH이나, 지원되지 않으므로 폼태그에서는 POST 방식으로 데이터를 보내되, 라라벨이 실제로는 PATCH 라는 것을 알 수 있도록 {{ method_field('PATCH')}} 를 입력해 줌. 또한 CSRF 공격을 막기 위해 {{ csrf_field() }} 입력.

@extends('layout')

@section('content')
<h1 class=""title>Edit Project</h1>

<form method="POST" action="/projects/{{ $project->id }}">
  {{ method_field('PATCH') }}
  {{ csrf_field() }}
  <div>
    <label class="label" for="title">Title</label>
    <input type="text" class="input" name="title" placeholder="Title" value="{{ $project->title }}">
  </div>
  <div>
    <label class="label" for="description">Description</label>
    <textarea name="description" class="textarea" >{{ $project->description }}</textarea>
  </div>

  <div>
    <button type="submit" class="button is-link">Update Project</button>
  </div>
</form>
@endsection

6. Delete Requests

마찬가지로 Delete Requests도 로직 구현이 가능함. Edit.blade.php 내 delete 요청을 보내는 코드 구현. 이후 controller 내에서 destroy 메서드 구현. findOrFailfind 의 응용 버젼으로, 존재하지 않는 url으로 요청이 왔을 경우, 404에러를 날려줌.


<form method="POST" action="/projects/{{ $project->id }}">
  {{ method_field('DELETE') }}
  {{ csrf_Field() }}

  <div>
    <button type="submit">Delete Project</button>
  </div>
</form>

public function destroy($id)
{
  $project = Project::findOrFail($id)->delete();

  return redirect('/projects');
}

Laravel 5.7 From Scratch 05 - Directory Structure / Form Handling / CSRF

|

1. Directory Structure Review

editorconfig : configuration for editor

.env : store important configration items (db password)

artisan : when php artisan is typed, this file is triggered

composer.json : configuration file that specifies all of dependencies (maybe only for development purposes)

composer.lock : taks all of dependencies you pulled in, locks them to specific versions - ensure the consistency

package.json : help with front-end compilation. dependencies are installed via Node.js and represented in javascript.

webpack.mix.js : wrapper on webpack. compiling javascript, scss, less. compile output to go.

yarn.lock : similar to composer.lock

\vender : where all dependencies are installed.

\tests : execture test.

\storage: storage files.

\routes : store routes.

  • console.php : possible to make custom artisan command.
  • channels.php: broadcasting channels. how will server side communicate with front end.
  • api.php : for the client routes specified for api.

\resources : views, js, sass etc that could be compiled down are stored.

\public : where compiled file goes. CSS, JS are coming from other files in \resources that are complied down using web pack.mix.js

\database : store all of migrations, database seeding, factories.

\config : various configuration settings are stored.

\bootsrap : framework bootstrap itself.

\app : where your app lives. all of your model/controller/more complicated artisan command(\console directory) go,

  • Http\middleware : layers of onion… when request comes in, user visits page. it is gonna go throw all of these layer(middleware). literally loop. loop throw all of these layers and trigger necessary layer. each of layers in these onion has the opportunity to respond to request before hit controller. in kerne file, you can find $middleware array that all of middleware are run during every single request to your application.
  • Providers

2. Form Handling and CSRF Protection

create.blade.php에서 폼 태그 내용을 POST 방식으로 지정된 URI(/projects)로 보내는 로직 구현

Routes\web.php 내에 routing 코드 입력

//post 방식으로 /projects라는 url로 요청이 들어왔을 경우, ProjectsController@store메서드 호출
Route::post('/projects', 'ProjectsController@store');
//마찬가지로, get방식으로 하기 주소로 요청이 들어왔을 경우.
Route::get('/projects/create', 'ProjectsController@create');

Resources\views\projects\create.blade.php 내 템플릿 코드 작성

  • POST 방식으로 /projects에 대한 경로로 요청을 보낼 경우, web.php에 따라 정의된 url패턴에 따라 ProjectsController@store 가 호출 됨.
  • 이때, `` 을 통해 CSRF TOKEN을 생성하여 세션에 저장된 토큰값과 일치하는지 검증. 이를 통해 CSRF 공격을 막을 수 있음.
<h1>Create New Project</h1>

<form method="POST" action="/projects">
  
  <div>
    <input type="text" name="title" placeholder="Project title">
  </div>
  <div>
    <textarea name="description" placeholder="Project description"></textarea>
  </div>
  <div>
    <button type="submit">Create Project</button>
  </div>
</form>

ProjectsController.php 내 메서드 구현

  • return redirect('/projects') : return to this url
  • return request()->all() : return all data
  • return request('title') : return the value of this specific key(title)
public function create()
{
  return view('projects.create');
}

public function store()
{
  $project = new Project();

  $project->title = request('title');
  $project->description = request('description');

  $project->save();

  return redirect('/projects');
  //        return request()->all();
  //        return request('title');
}

Laravel 5.7 From Scratch 04 - Eloquent / Namespacing / MVC

|

1. Eloquent

php artisan make:model Project : 모델 생성

php artisan tinker : 플레이 그라운드 (laraval shell의 개념)

php artisan tinker
Psy Shell v0.9.11 (PHP 7.2.25  cli) by Justin Hileman
>>> App\Project::all();
=> Illuminate\Database\Eloquent\Collection {#3006
     all: [],
	}
>>> App\Project::first();
=> null
>>> App\Project::latest()->first();
=> null
>>> $project = new App\Project;  // Project 클래스의 인스턴스 생성
=> App\Project {#2997}

// Migration 에서 생성한 create_projects_table을 적용시킴
>>> $project->title = 'My First Project';
=> "My First Project"
>>> $project->description = 'Lorem ipsum';
=> "Lorem ipsum"
>>> $project
=> App\Project {#2997
     title: "My First Project",
     description: "Lorem ipsum",
   }
>>> $project->save(); // DB에 저장
=> true

>>> App\Project::first();
=> App\Project {#3019
     id: 1,
     title: "My First Project",
     description: "Lorem ipsum",
     created_at: "2019-12-05 12:43:28",
     updated_at: "2019-12-05 12:43:28",
   }
>>> App\Project::first()->title;
=> "My First Project"
>>> App\Project::first()->description;
=> "Lorem ipsum"
>>> App\Project::all();
=> Illuminate\Database\Eloquent\Collection {#3003
     all: [
       App\Project {#3022
         id: 1,
         title: "My First Project",
         description: "Lorem ipsum",
         created_at: "2019-12-05 12:43:28",
         updated_at: "2019-12-05 12:43:28",
       },
     ],
   }

두번째 Project 인스턴스 생성 후 데이터베이스에 저장

>>> $project = new App\Project;
=> App\Project {#3006}
>>> $project->title = 'My Second Project';
=> "My Second Project"
>>> $project->description = 'Lorem ipsum';
=> "Lorem ipsum"
>>> $project->save();
=> true

App\Project 데이터를 갖고오면 collection이 생성되어 있음을 알 수 있음.

  • Collections are like arrays on steroids
>>> App\Project::all();
=> Illuminate\Database\Eloquent\Collection {#3025
     all: [
       App\Project {#3026
         id: 1,
         title: "My First Project",
         description: "Lorem ipsum",
         created_at: "2019-12-05 12:43:28",
         updated_at: "2019-12-05 12:43:28",
       },
       App\Project {#3027
         id: 2,
         title: "My Second Project",
         description: "Lorem ipsum",
         created_at: "2019-12-05 12:48:01",
         updated_at: "2019-12-05 12:48:01",
       },
     ],
   }

모든 배열 메서드를 동일하게 적용 시킬 수 도 있음.

>>> App\Project::all()[0];
=> App\Project {#3030
     id: 1,
     title: "My First Project",
     description: "Lorem ipsum",
     created_at: "2019-12-05 12:43:28",
     updated_at: "2019-12-05 12:43:28",
   }
>>> App\Project::all()[1];
=> App\Project {#3016
     id: 2,
     title: "My Second Project",
     description: "Lorem ipsum",
     created_at: "2019-12-05 12:48:01",
     updated_at: "2019-12-05 12:48:01",
   }
>>> App\Project::all()[1]->title;
=> "My Second Project"
>>> App\Project::all()->map->title;
=> Illuminate\Support\Collection {#3002
     all: [
       "My First Project",
       "My Second Project",
     ],
   }

2. MVC Pattern

MVC 패턴에 따라 어플리케이션을 사용할 수 있으며 각 컴포넌트들의 위치는 다음과 같다.

  • Model: app\Project.php
  • View: resources\views\welcome.blade.php
  • Controller: app\Http\Controllers\Controller.php
  • +Route

3. MVC 패턴 적용

ProjectsController를 생성 한 후, 라우팅 연결 & view 파일을 만들어 주도록 하자.

// web.php
Route::get('/projects', 'ProjectsController@index');

//ProjectsController.php
class ProjectsController extends Controller
{
    public function index()
    {
        return view('projects.index');
    }
}

// views\projects\index.blade.php
<!doctype html>
<html lang="en">
<head>
    <title>Document</title>
</head>
<body>
    <h1>projects</h1>
</body>
</html>

4. 모델 연결 & namespaces

라라벨은 PSR-4(an autoloading specification)이라는 컨벤션 룰에 따라 네임스페이스를 설정해줘야함.

  • $projects = \App\Project::all();

model 은 app 디렉토리 내에 있으므로 App 으로 시작해야함. 이때, controller는 App\Http\Controllers 디렉토리 내에 있기 때문에 루트 디렉토리에서 시작하기 위해서는 네임스페이스 시작을 \으로 해야함. 그렇게 하지 않을 시, 현재 디렉토리를 기준으로 인식해버림.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ProjectsController extends Controller
{
    public function index()
    {
        $projects = \App\Project::all();

        return view('projects.index');
    }
}

다른 방법으로는 아래와 같이 또한 사용 가능 함.

<?php

namespace App\Http\Controllers;

use App\Project;

use Illuminate\Http\Request;

class ProjectsController extends Controller
{
    public function index()
    {
        $projects = Project::all();

        return view('projects.index');
    }
}

모델에 따른 데이터를 view로 넘겨줌

class ProjectsController extends Controller
{
    public function index()
    {
        $projects = Project::all();

//        return view('projects.index', ['projects' => $projects]);
        return view('projects.index', compact('projects'));

    }
}
<!doctype html>
<html lang="en">
<head>
    <title>Document</title>
</head>
<body>
    <h1>projects</h1>
    @foreach ($projects as $project)
        <li></li>
    @endforeach
</body>
</html>

Laravel 5.7 From Scratch 03 - Databases / Migrations

|

1. GET STARTED

laravel new project : project라는 새로운 라라벨 프로젝트 생성 .env : configuration & private detail을 나타냄. (private api key, DB password 등)

Sequel_pro 설치 후 DB 연결, tutorial이라는 DB 생성 후 그에 맞게 .env 파일 수정. 각 변수를 .env에 저장하였지만, 어떻게 이 변수들이 어플리케이션에서 참조가 가능할까? => config/database.php 확인

// env/DB_CONNECTION을 우선적으로 읽음. 실패할 경우 두번째 인자를 읽음.
'default' => env('DB_CONNECTION', 'mysql'),

2. Migrations

php artisn migrate : 데이터베이스에 마이그레이션 적용 php artisan migrate:rollback : 마이그레이션 롤백

mysql> show tables;
+--------------------+
| Tables_in_tutorial |
+--------------------+
| failed_jobs        |
| migrations         |
| password_resets    |
| users              |
+--------------------+
4 rows in set (0.00 sec)

mysql> describe users;
+-------------------+---------------------+------+-----+---------+----------------+
| Field             | Type                | Null | Key | Default | Extra          |
+-------------------+---------------------+------+-----+---------+----------------+
| id                | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| name              | varchar(255)        | NO   |     | NULL    |                |
| email             | varchar(255)        | NO   | UNI | NULL    |                |
| email_verified_at | timestamp           | YES  |     | NULL    |                |
| password          | varchar(255)        | NO   |     | NULL    |                |
| remember_token    | varchar(100)        | YES  |     | NULL    |                |
| created_at        | timestamp           | YES  |     | NULL    |                |
| updated_at        | timestamp           | YES  |     | NULL    |                |
+-------------------+---------------------+------+-----+---------+----------------+
8 rows in set (0.00 sec)

3. Database 컬럼명 변경

users 테이블의 name을 변경하고 싶다면 config/migrations 디렉토리 내에 저장된 2014_10_12_000000_create_users_table.php 파일에서 내용을 변경하면됨.

이렇게 변경된 컬럼명을 적용시키는데는 크게 2가지 방법이 있을 수 있음.

  1. 마이그레이션 롤백 후 재 적용
  2. php artisan migrate:fresh
class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->bigIncrements('id');
						$table->string('username');
          	//$table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

4. Database 테이블 생성

php artisan make:migration : 새로운 마이그레이션 파일 생성 => php artisan make:migration create_projects_table

  • up() : 마이그레이션 시 사용
  • down() : 마이그레이션 롤백 시 사용. 함수 코드를 주석 처리할 경우, migration:rollback을 하더라도 마이그레이션 롤백이 적용되지 않음. 이런 경우 migration:fresh 를 사용함 (마이그레이션을 완전 새로고치므로…)
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateProjectsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('projects', function (Blueprint $table) {
            $table->bigIncrements('id');
          	$table->string('title');
            $table->text('description');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('projects');
    }
}

Laravel 5.7 From Scratch 02 - Views / Controllers

|

1. Sending Data to your Views

Route:get() 내에서 배열을 선언한 후, 연관배열을 return하는 것도 가능하다. 이때 <?php : ?> & <?= ?> 와 같이 traditional한 PHP 문법을 사용해도 되지만, Laracasts에서 지원하는 shorthands를 사용 할 수도 있음.

  • <?php : ?> => @
  • <?= ?> => ``
Route::get('/', function() {
    $tasks = [
    'Go to the store',
    'Go to the market',
    'Go to the work',
];

    return view('welcome', [
        'tasks' => $tasks
    ]);
});
@extends('layout')

@section('content')
    <h1>My First Website!!!!!!</h1>

    // 기존 방식
    <ul>
      	<?php foreach ($tasks as $task) : ?>
          	<li><?= $task; ?></li>
      	<?php endforeach; ?>
    </ul>

    // Laravel shorthand: laravel이 코드를 읽으면서 @를 <?php 형태로 대체시킴.
    <ul>
        @foreach ($tasks as $task)
            <li></li>
        @endforeach
    </ul>

@endsection

2. HTML 엘리먼트 route로 넘기기

routes의 return 값으로 url에서 들어오는 쿼리스트링을 넘길 수도 있다. 이때 html의 태그를 같이 넣을 경우 그대로 스트링으로 인식되되어 엘리먼트로 적용되지는 않는다. 만약 엘리먼트로 사용하고 싶을 경우, `` 대신 {!! !!} 으로 묶어줘야 한다.

Route::get('/', function() {
    $tasks = [
    'Go to the store',
    'Go to the market',
    'Go to the work',
    'Go to concert',
];

    return view('welcome', [
        'tasks' => $tasks,
        'foo' => request('title'),
        'foo' => '<script>alert("foobar")</script>'
    ]);
});
@section('content')
    <h1>My {!! $foo !!} Website</h1>

    <ul>
        @foreach ($tasks as $task)
            <li> </li>
        @endforeach
    </ul>
@endsection

3. 응용

Laravel에서 지원하는 custom method 중 하나인 with 를 사용하여 좀 더 코드를 단축시킬 수 있음.

  • With"view에서사용하는변수명"->("정의된 변수명")
Route::get('/', function() {
    $tasks = [
    'Go to the store',
    'Go to the market',
    'Go to the work',
    'Go to concert',
];
    return view('welcome')->withTasks($tasks)->withFoo('foo');

//    return view('welcome', [
//        'tasks' => $tasks,
//        'foo' => 'foobar',
//    ]);
});

하나의 인자만 넘길 경우, 변형 가능

Route::get('/', function() {
    return view('welcome')->withTasks([
        'Go to the store',
        'Go to the market',
        'Go to the work',
        'Go to concert',
    ]);
});

또다른 응용

Route::get('/', function() {
    return view('welcome')->with([
        'foo' => 'bar',
        'tasks' => [
            'Go to the store',
            'Go to the market',
            'Go to the work',
            'Go to concert',
        ]
    ]);
});

4. Controllers 101

php artisan make:controller 컨트롤러이름 으로 컨트롤러를 간단히 만들 수 있음. routes 코드를 다시 한번 다듬어, web.php에서는 컨트롤러를 호출하고, 엔드 포인트 설정을 컨트롤러 내 메서드를 통해 정의

  • PagesController 생성 후, 각각 home, about, contact 페이지로 이동하는 메서드를 만듦.
  • web.php에서는 컨트롤러와 대응되는 메서드를 호출 PagesController@home
Route::get('/', 'PagesController@home');
Route::get('about', 'PagesController@about');
Route::get('contact', 'PagesController@contact');


namespace App\Http\Controllers;

use Illuminate\Http\Request;

class PagesController extends Controller
{
    public function home()
    {
        return view('welcome', [
            'foo' => 'bar',
        ]);
    }

    public function about()
    {
        return view('about');
    }

    public function contact()
    {
        return view('contact');
    }
}