Laravel 로 웹싸이트 개발하기 #1

devil
Standard

Laravel 로 웹싸이트 개발하기

2014년 12월 구로동 사무실에서

현재 모바일부터 웹애플리케이션에 이르는 풀스택 개발 프로젝트를 진행하고 있기에, 최신 기술 트랜드를 적용한 부분들 중 일부 내용을 블로그에 써보기로 했다. 웹개발 플랫폼은 Laravel 4.2 프레임웍을 근간으로하여, 온갖 오픈 소스 패키지들과, 유료 3rd party API 들을 최대한 가져다 쓰고 있다. 금번 프로젝트 개발의 백엔드 부분 개발은 물론이며, 프론트엔드 부분까지도 나혼자 스스로 결정해가면서 진행하고 있어서, 그간 안해봤던 내용들을 두루 경험해 볼 수 있는 좋은 시간이 되고 있다. 개를 보기만 하다가 먹어도 보는 그런 느낌? 암튼 그런 묘한… 그런게 있다. 이번 글에서 이야기할 내용은 사용자가 설정 페이지에서 다수의 사진들을 올리고 관리하기 위한, 사용자의 사진을 유지/관리하는데 필요한 내용들이다. 웹 애플리케이션에서 흔히들 많이 구현하는 그런 내용이 되겠다.

우선, 우연히 봤던 이 블로그 (http://revoltvisual.com/journal/5-must-have-laravel-4-packages) 에서 Laravel 5대 패키지 중 하나라고 언급된 Stapler 패키지를 사용한다. 딴 놈이 추천한 패키지를 사용한다는 것만으로도 시간 헛낭비하는게 아닌듯 싶어 뿌듯하다. 이 패키지를 이용해서 User 모델에 Photo 모델을 붙이는 과정을 정리해 보겠다. 특이한 점은, 이미지 파일 저장을 위해서 서버의 파일 시스템을 사용하지 않고, 아마존 S3 를 이용하려는 점이다. 부담스런 서버 호스팅 관리, 효율적인 파일 접근을 위한 디렉토리 구조 생성, 모델간의 연계성 상호 유지 등 관련된 여러가지 고민거리들을 한방에 해결하는 것이 목표다.

한방에 훅

우선 프론트엔드 부에서, 다수의 파일 업로드를 위해 선택한 라이브러리는 mailru/FileAPI 이다. 초기에는 (그래봤자 하루 전 이야기지만 ㅎㅎ) blueimp/jQuery-File-Upload 또는 enyo/dropzone 를 사용할까도 생각해 봤지만, 내가 선호하는 스타일로 커스터마이징에 보다 플렉서블한 FileAPI 데모 페이지를 보고 단숨에 미련없이 결정했다. 존나 멋지다. 근데, 예제의 HTML view 를 보니, 템플릿 엔진도 들어가 있고, 이거 뭐냐 다른 디펜던시가 또 있는건가? 생각하다 한참 헤맸다. 만든 사람도 있는데 셋업을 못한다면 얼마나 쪽팔린 일이냐… 근데 또 다른 디펜던시 그런건 없다. 그냥 예제처럼 쓰면 된다. 늘 그렇듯 오버하면 안된다.

우선 아래 bower 명령으로 jquery 용 FileAPI 를 가져온다.

bower install jquery.fileapi

이제 FileAPI 라이브러리를 이용하는 뷰가 필요하다. 아래는 현재 제작중인 프로젝트에서 사용하는 뷰에서 설명에 필요한 내용 일부를 발췌한 것이다. FileAPI 예제와 비교해 보면, 이 라이브러리의 사용법을 익히는데 많은 도움이 되리라본다.

@extends('layouts.default')

@section('stylesheets')
    <style>
        #uploader .btn {
            cursor: pointer;
            display: inline-block;
            position: relative;
            overflow: hidden;
        }
        #uploader .btn input
        {
            top: -10px;
            right: -40px;
            z-index: 2;
            position: absolute;
            cursor: pointer;
            opacity: 0;
            filter: alpha(opacity=0);
            font-size: 50px;
        }
        #uploader .b-thumb__name {
            font-size: 0.8em;
        }
    </style>
@stop

@section('content')
    <div class="canvas">
        <div class="page-header">
            <div>Title Header</div>
        </div>

        <div class="panel panel-default">
            <div class="panel-heading clearfix">
                <div class="panel-title pull-left">사진</div>
                <button type="button" class="btn btn-default btn-xs pull-right"><span class="glyphicon glyphicon-pencil"></span> 수정</button>
            </div>
            <div class="panel-body">

                <div class="row centered-form">
                    <div class="col-xs-12 col-sm-8 col-md-4 col-sm-offset-2 col-md-offset-4">
                        <div class="panel panel-warning">
                            <div class="panel-heading">
                                <h3 class="panel-title">더많은 사진 올리기</h3>
                            </div>
                            <div class="panel-body">
                                <div id="uploader">

                                  <div class="b-upload__hint">사진을 올리면 주목받을 수 있습니다.</div>
                                  <div class="js-files b-upload__files">
                                     <div class="js-file-tpl b-thumb" data-id="<%=uid%>" title="<%-name%>, <%-sizeText%>">
                                        <div class="b-thumb__preview">
                                           <div class="b-thumb__preview__pic"></div>
                                        </div>
                                        <div class="b-thumb__name"><%-name%></div>
                                        <div class="b-thumb__progress progress progress-striped progress-small active">
                                            <div class="js-progress progress-bar progress-bar-warning"></div>
                                        </div>
                                     </div>
                                  </div>
                                  <hr>
                                  <div class="btn btn-warning btn-small js-fileapi-wrapper">
                                     <span>불러오기</span>
                                     <input type="file" name="filedata">
                                  </div>

                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>

    </div>
@stop

@section('scripts')

    <script>
    window.FileAPI = {
          debug: true,
          staticPath: '/js/jquery.fileapi/FileAPI/' // path to *.swf
    };
    </script>
    <script src="/js/jquery.fileapi/FileAPI/FileAPI.min.js"></script>
    <script src="/js/jquery.fileapi/FileAPI/FileAPI.exif.js"></script>
    <script src="/js/jquery.fileapi/jquery.fileapi.js"></script>

    <script>
        $(function() {
            $('#uploader').fileapi({
                url: '{{ route("users.upload") }}',
                accept: 'image/*',
                autoUpload: true,
                multiple: true,
                imageTransform: { // resize by max side
                    maxWidth: 800,
                    maxHeight: 600
                },
                elements: {
                    empty: { show: '.b-upload__hint' },
                    emptyQueue: { hide: '.js-upload' },
                    list: '.js-files',
                    file: {
                        tpl: '.js-file-tpl',
                        preview: { el: '.b-thumb__preview', width: 64, height: 64 },
                        upload: { show: '.progress' },
                        complete: { hide: '.progress' },
                        progress: '.js-progress'
                    }
                }
            });
        });
    </script>

@stop

일단, 이번 회에는 서버 파일 시스템의 /upload 폴더에 파일 이름을 난수명으로 변경하여 저장하는 가장 일반적인 이미지 파일 처리 방법에 대해서 설명을 하고, 이후에 Stapler 패키지를 이용해서 /system 폴더에 저장하는 방법, 계속해서 서버의 파일 시스템이 아닌 아마존 S3를 사용하는 방법으로 진화시켜 보겠다. 서버로 올라간 파일에 대한 처리는 아래 컨트롤러의 upload 메서드에서 처리한다. 현재는, 단순히 /uploads 폴더에 난수명을 갖는 파일로 옮겨 저장한다.

<?php

use Teeshot\Users\UserRepository;

class UsersController extends \BaseController {

    /**
     * @var UserRepository
     */
    protected $userRepository;

    /**
     * @param UserRepository $userRepository
     */
    function __construct(UserRepository $userRepository)
    {
        $this->userRepository = $userRepository;
    }

    public function upload()
    {
        $file = Input::file('filedata');

        $destinationPath = 'uploads';
        $filename  = str_random(16);
        $extension = $file->getClientOriginalExtension();
        $size      = $file->getSize();
        $fullName  = $filename.'.'.$extension;
        $upload_success = $file->move($destinationPath, $fullName);

        if( $upload_success ) {
            return Response::json(['name' => $fullName, 'size' => $size], 200);
        } else {
            return Response::json('error', 400);
        }
    }
}

이 방법이 이미지 파일 업로드를 위해서 가장 많이 사용하는 기초적인 방법이 되겠다. 물론 이렇게 사용해도 된다. 하지만, 아까도 말했듯이 고민해야할 문제들을 한방에 훅가도록 처리하기 위해서 Stapler 를 사용해 보자. 당장에 아마존S3 를 이용할 것은 아니지만, 궁극적인 목적이 아마존S3 를 이용하는 것이니, 필요한 내용을 미리 미리 셋업하면서 진행해 가겠다. Stapler 메뉴얼에 미안할 정도로 설명이 잘 나와있다. composer.json 에 다음 2줄을 추가하고 업데이트를 시켜서 해당파일 및 디펜던시들을 설치하면 일단, 백엔드쪽 준비는 끝이다.

"codesleeve/laravel-stapler": "1.0.*",
"aws/aws-sdk-php": "2.7.*"

아마존S3 가입하지 않은 사용자라면, 브라우저를 열어 가입하기로 한다. 아마존 AWS 에 가입 후, S3 메뉴를 선택하고 새로운 bucket을 생성한다. 다음은 IAM 메뉴를 선택하여 새로운 사용자 그룹을 만든다. 이 그룹이 갖을 퍼미션은 S3 에 full access 권한이다. 그 다음은 새로운 사용자를 만들어서 access credentials 를 받는다. php artisan config:publish codesleeve/laravel-stapler 명령으로 생성한 config 파일들 중 s3.php 에 필요한 user credentials 값들이므로 카피해 놓는다. region 값은 역시 Stapler 설명서에 잘 나와 있듯이 http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region 에서 찾아보면 된다. 사용자를 생성한 뒤에는 반드시 그 사용자 그룹을 바로 전단계에서 생성한 사용자 그룹으로 설정시킨다. 이로써 이 아마존 S3 에 full access 권한을 갖는 사용자가 탄생하게 되었다.

다음 회에서는 Stapler 를 사용하여, 지정된 모델과 연동하는 이미지 파일들을 자동으로 생성처리하는 법을 살펴보기로 하겠다.