섹션

지침서 - 기초


개요

이 지침서 전체에 걸쳐 Phalcon 설계 상의 주요 측면에 대해 소개함과 동시에, 간단한 등록양식이 있는 어플리케이션을 만들어 보겠습니다.

이 지침서에서는 Phalcon 을 이용하면 얼마나 빠르고 쉽게 만들 수 있는지 보여주기 위해 간단한 MVC 어플리케이션의 구현을 다룹니다. 개발이 완료되면, 이 어플리케이션을 필요에 따라 확장시켜 사용하실 수있습니다. 이 지침서에 있는 코드는 다른 Phalcon 특유의 컨셉트와 아이디어를 배우는 놀이터로도 사용될 수 있습니다. <iframe width="560" height="315" src="https://www.youtube.com/embed/75W-emM4wNQ" frameborder="0" allowfullscreen mark="crwd-mark"></iframe>

바로 시작하기를 원하신다면 이 섹션은 건너뛰고 developer tools를 이용해서 자동으로 Phalcon 프로젝트를 생성하세요.

이 가이드를 사용하는 최고의 방법은 찬찬히 따라 오시면서 즐기려고 노력하시는 겁니다. 전체 코드는 여기 있습니다. 난관에 봉착했거나 궁금한게 있으시면, Discord포럼으로 오시면 됩니다.

파일 구조

Phalcon의 핵심기능 중 하나는 느슨하게 연결(loosely coupled) 되어 있다는 부분입니다. 그런 이유로, 어떤 형태든 본인이 편한대로 디렉토리 구조를 만드실 수 있습니다. 이 지침서에서는 MVC 어플리케이션에서 일반적으로 사용되는 표준 디렉토리 구조를 사용하겠습니다.

.
└── tutorial
    ├── app
    │   ├── controllers
    │   │   ├── IndexController.php
    │   │   └── SignupController.php
    │   ├── models
    │   │   └── Users.php
    │   └── views
    └── public
        ├── css
        ├── img
        ├── index.php
        └── js

주의: Phalcon이 노출하는 모든 코드는 (웹서버에서 로드한) 익스텐션 내에 캡슐화 되어있으므로, Phalcon 코드를 포함하고 있는 vendor 디렉토리는 여기에 없습니다. 필요한 모든 것이 메모리에 로드되어 있습니다. 아직 어플리케이션 설치 전이시라면, 이 자습서를 더 진행하시기 전에 설치 페이지로 가셔서 설치과정을 완료해주시기 바랍니다

모든 것이 처음이시라면 Phalcon Devtools 또한 설치하시기를 권장합니다. DevTools는 어플리케이션을 즉시 실행해 보실 수 있도록, PHP에 내장되어 있는 웹서버를 활용합니다. 이 옵션을 선택하신다면, 프로젝트 루트폴더에 .htrouter.php 파일을 아래와 같은 내용으로 생성해주세요:

<?php

$uri = urldecode(
    parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)
);

if ($uri !== '/' && file_exists(__DIR__ . '/public' . $uri)) {
    return false;
}

$_GET['_url'] = $_SERVER['REQUEST_URI'];

require_once __DIR__ . '/public/index.php';

이 자습서의 경우에, 이 파일은 tutorial 디렉토리에 위치해야 합니다.

NginX, apache, cherokee 혹은 다른 웹서버를 사용하시는 것도 물론 가능합니다. 웹서버 준비 페이지게 가시면 자세한 설명을 확인하실 수 있습니다.

Bootstrap

제일 처음에 bootstrap 파일을 생성해야 합니다. 이 파일은 어플리케이션의 실행시 최초 진입점과 어플리케이션 설정의 역할을 합니다. 이 파일에서, 컴포넌트 초기화를 적용하고 어플리케이션의 동작(behavior) 을 정의할 수 있습니다.

이 파일은 세가지를 처리합니다:

  • 컴포넌트 오토로더의 등록
  • 서비스 설정 및 의존성 주입 컨텍스트와 함께 서비스 등록
  • 어플리케이션의 HTTP 요청을 처리

Autoloader

우리는 PSR-4 규칙을 따르는 Phalcon\Loader 파일로더를 사용합니다. 일반적으로 오토로더에 추가해야 할 것들은 컨트롤러와 모델 입니다. 어플리케이션이 요청하는 파일을 스캔할 디렉토리들도 등록할 수 있습니다.

자 그럼, Phalcon\Loader를 이용해서 컨트롤러모델 디렉토리를 등록해 봅시다:

public/index.php

<?php

use Phalcon\Loader;

define('BASE_PATH', dirname(__DIR__));
define('APP_PATH', BASE_PATH . '/app');
// ...

$loader = new Loader();

$loader->registerDirs(
    [
        APP_PATH . '/controllers/',
        APP_PATH . '/models/',
    ]
);

$loader->register();

의존성 관리

Phalcon은 느슨히 결합된 형태이기 때문에, 서비스를 프레임워크 의존성관리자에 등록해서 IoC 컨테이너 내부에 있는 컴포넌트와 서비스에 자동으로 주입(inject) 시킬 수 있도록 합니다. 앞으로 DI (Dependency Injection - 의존성 주입) 라는 단어를 자주 접하게 될것입니다. 의존성 주입과 역제어(IoC: Inversion of Control) 는 뭔가 복잡하게 들릴 수 있겠지만, Phalcon에서 이들의 사용은 간단하고 실질적이며 효과적입니다. Phalcon의 IoC 컨테이너는 다음과 같은 컨셉으로 이루어져 있습니다:

  • 서비스 컨테이너: 어플리케이션이 동작하기 위해 필요한 서비스를 전역참조로 저장하는 “가방”
  • 서비스/컴포넌트: 컴포넌트에 주입될 데이터처리 객체

컴포넌트나 서비스가 필요할 때마다 프레임워크는 컨테이너에게 해당 서비스를 미리 약속해둔 이름으로 요청을 하게 됩니다. 이 방법으로 로거, 데이터베이스 연결 등 어플리케이션에 필요한 객체를 쉽게 가져올 수 있게 됩니다.

주의: 자세한 내용이 궁금하시면 Martin Fowler 의 글을 참조해주세요. 그리고 많은 유스 케이스(use cases) 를 다루고 있는 멋진 자습서도 있습니다.

Factory Default

Phalcon\Di\FactoryDefaultPhalcon\Di의 변종입니다. 이 클래스는 보다 쉬운 사용을 위해 어플리케이션에서 필요로 하는 대부분의 컴포넌트를 자동으로 등록하며, 표준으로 Phalcon과 함께 제공됩니다. 서비스 설정은 수동으로 하기를 권하지만, 처음에는 Phalcon\Di\FactoryDefault 를 사용하고 이후 필요에 따라 맞춤 설정하실 수도 있습니다.

서비스를 등록하는 방법은 여러가지가 있지만, 이 자습서에서는 anonymous function을 이용하겠습니다:

public/index.php

<?php

use Phalcon\Di\FactoryDefault;

// Create a DI
$container = new FactoryDefault();

이제 프레임워크가 뷰 파일들을 찾을 디렉토리를 설정하여 view 서비스를 등록해야 합니다. 뷰와 클래스는 동일하지 않기 때문에, 오토로더가 자동으로 로드할 수 없기 때문입니다.

public/index.php

<?php

use Phalcon\Mvc\View;

// ...

$container->set(
    'view',
    function () {
        $view = new View();
        $view->setViewsDir(APP_PATH . '/views/');

        return $view;
    }
);

이제 Phalcon이 필요한 모든 URI 를 생성할 수 있는 기능을 제공해 줄 base URI를 등록할 차례입니다. 이 컴포넌트는 어플리케이션을 최상위 디렉토리에서 실행하던 하위 디렉토리에서 실행하던 상관없이 모든 URI가 정확하도록 보장해 줍니다. 이 자습서에서는 / 가 base 경로입니다. 자습서의 뒷부분에서 하이퍼링크를 생성하기 위해 Phalcon\Tag 를 사용하게 되면 이 base경로가 중요해질것입니다.

public/index.php

<?php

use Phalcon\Url;

// ...

$container->set(
    'url',
    function () {
        $url = new Url();
        $url->setBaseUri('/');

        return $url;
    }
);

어플리케이션 요청 처리하기

요청을 처리하기 위해 필요한 모든 힘든 작업을 Phalcon\Mvc\Application 객체가 대신해 줍니다. 이 컴포넌트는 사용자로부터의 요청 수락, 경로(route) 감지, 컨트롤러에 전달하고, 결과를 반환하여 뷰를 렌더링합니다.

public/index.php

<?php

use Phalcon\Mvc\Application;

// ...

$application = new Application($container);

$response = $application->handle(
    $_SERVER["REQUEST_URI"]
);

$response->send();

지금까지 내용을 다 합쳐보면

tutorial/public/index.php 파일은 다음과 같습니다:

public/index.php

<?php

use Phalcon\Di\FactoryDefault;
use Phalcon\Loader;
use Phalcon\Mvc\View;
use Phalcon\Mvc\Application;
use Phalcon\Url;

// Define some absolute path constants to aid in locating resources
define('BASE_PATH', dirname(__DIR__));
define('APP_PATH', BASE_PATH . '/app');

// Register an autoloader
$loader = new Loader();

$loader->registerDirs(
    [
        APP_PATH . '/controllers/',
        APP_PATH . '/models/',
    ]
);

$loader->register();

$container = new FactoryDefault();

$container->set(
    'view',
    function () {
        $view = new View();
        $view->setViewsDir(APP_PATH . '/views/');
        return $view;
    }
);

$container->set(
    'url',
    function () {
        $url = new Url();
        $url->setBaseUri('/');
        return $url;
    }
);

$application = new Application($container);

try {
    // Handle the request
    $response = $application->handle(
        $_SERVER["REQUEST_URI"]
    );

    $response->send();
} catch (\Exception $e) {
    echo 'Exception: ', $e->getMessage();
}

보시는 바와 같이, 이 부트스트랩파일은 아주 짧고 추가적인 파일을 include 할 필요가 없습니다. 이제 여러분은 30줄 미만의 코드로 유연한 MVC 어플리케이션을 잘 만들 수 있게 되었습니다.

컨트롤러 생성

Phalcon 은 기본적으로 IndexController 라는 컨트롤러를 찾습니다. 이곳은 요청에 컨트롤러나 액션이 없을 경우의 시작점이 됩니다 (예를들어 https://localhost/). IndexControllerIndexAction 는 다음의 예제코드와 비슷해야 합니다:

app/controllers/IndexController.php

<?php

use Phalcon\Mvc\Controller;

class IndexController extends Controller
{
    public function indexAction()
    {
        return '<h1>Hello!</h1>';
    }
}

컨트롤러 클래스이름은 Controller로 끝나야 하며 컨트롤러 액션은 Action으로 끝나야 합니다. 더 자세한 내용은 컨트롤러 문서를 확인해 주세요. 브라우저에서 어플리케이션에 접근하시는 경우, 아래와 같은 화면이 보여야 합니다:

축하합니다, 이제 Phalcon과 함께 Phlying 을 성공하셨습니다!

뷰로 출력 보내기

컨트롤러에서 화면으로 직접 출력하는 것이 가끔 필요한 경우도 있지만 MVC커뮤니티에 있는 대부분의 순수주의자들이 증언하는 바와 같이, 썩 바람직하지는 앖습니다. 모든 결과는 화면에 데이터를 출력하는 책임을 가진, 뷰에 전달되어야 합니다. Phalcon은 가장 나중에 실행된 컨트롤러의 이름과 같은 디렉토리 내에 있는 가장 나중에 실행된 액션과 동일한 이름의 뷰를 찾습니다.

그러므로 우리의 경우 URL이 다음과 같다면:

http://localhost/

IndexController 컨트롤러와 indexAction 액션을 실행하고, 다음과 같은 뷰를 찾게 됩니다:

/views/index/index.phtml

찾는데 성공하면 해당파일을 파싱해서결과물을 화면으로 보냅니다. 뷰의 내용은 다음과 같습니다:

app/views/index/index.phtml

<?php echo "<h1>Hello!</h1>";

컨트롤러 액션에 있던 echo 부분을 뷰로 이동시켰기 때문에, 현재 액션은 비어있겠지요:

app/controllers/IndexController.php

<?php

use Phalcon\Mvc\Controller;

class IndexController extends Controller
{
    public function indexAction()
    {

    }
}

하지만 브라우저에 출력되는 결과물은 이전과 동일합니다. Phalcon\Mvc\View 컴포넌트는 액션의 실행이 종료되면 자동으로 생성됩니다.. Phalcon의 뷰에 관해 좀 더 자세한 내용을 보시려면 여기를 참조해 주세요.

회원가입 Form 설계하기

이제 signup 이라는 이름의 새로운 컨트롤러에 링크를 추가하기 위해, index.phtml 뷰 파일을 변경해 보겠습니다.. 사용자들이 어플리케이션에서 회원가입 할 수 있도록 만드는 것이 우리의 목표입니다.

app/views/index/index.phtml

<?php

echo "<h1>Hello!</h1>";

echo PHP_EOL;

echo PHP_EOL;

echo $this->tag->linkTo(
    'signup',
    'Sign Up Here!'
);

생성된 HTML코드는 새로운 컨트롤러에 링크시키는 앵커(<a>) HTML 태그를 표시합니다.

app/views/index/index.phtml (실제 렌더링결과)

<h1>Hello!</h1>

<a href="/signup">Sign Up Here!</a>

<a> 태그로 링크를 만들기 위해, Phalcon\Tag 컴포넌트를 사용합니다. 이것은 프레임워크에서 정한 규약에 맞춰 HTML태그를 쉽게 만드는 방법을 제공하는 유틸리티 클래스입니다. 이 클래스는 또한 의존성 주입기(Dependency Injector)에 등록된 서비스이기도 하므로, $this->tag 를 사용해서 기능에 접근할 수 있습니다.

주의: 우리는 Phalcon\Di\FactoryDefault 컨테이너를 사용하므로 Phalcon\Tag 는 DI 컨테이너에 이미 등록되어 있습니다. 모든 서비스를 직접 수동으로 등록하신 경우라면, 이 컴포넌트를 컨테이너에 등록하셔야 어플리케이션에서 사용하실 수 있습니다.

Phalcon\Tag 컴포넌트는 정확한 URI를 생성하기 위해 Phalcon\Uri 컴포넌트도 또한 사용합니다. HTML 생성에 관한 자세한 내용은 이 문서를 참조해 주세요.

다음으로 회원가입 컨트롤러는 (app/controllers/SignupController.php):

app/controllers/SignupController.php

<?php

use Phalcon\Mvc\Controller;

class SignupController extends Controller
{
    public function indexAction()
    {

    }
}

Index 액션에 아무 내용이 없으면 파라미터 없이 바로 form이 정의되어있는 뷰를 호출합니다. (app/views/signup/index.phtml):

app/views/signup/index.phtml

<h2>Sign up using this form</h2>

<?php echo $this->tag->form("signup/register"); ?>

    <p>
        <label for="name">Name</label>
        <?php echo $this->tag->textField("name"); ?>
    </p>

    <p>
        <label for="email">E-Mail</label>
        <?php echo $this->tag->textField("email"); ?>
    </p>

    <p>
        <?php echo $this->tag->submitButton("Register"); ?>
    </p>

</form>

브라우저에서 이 form을 확인하면 아래와 같이 표시됩니다:

위에서 언급한것 처럼, Phalcon\Tag 유틸리티 클래스는 form HTML 요소를 쉽게 만들 수 있는 유용한 메서드들을 제공합니다. 예를 들어 Phalcon\Tag::form() 메서드는 어플리케이션의 컨트롤러/액션에 대한 상대적 URI 값 하나만 파라미터로 받습니다. Phalcon\Tag::textField() 는 넘겨받은 파라미터 값을 이름으로 하는 텍스트 HTML요소를 생성하고, Phalcon\Tag::submitButton() 은 submit HTML 버튼을 생성합니다.

Register 버튼을 클릭하면, signup 컨트롤러에 register액션이 없다는 메시지의 예외가 발생할 것입니다. public/index.php 파일은 다음과 같은 예외를 발생시킵니다:

Exception: Action "register" was not found on handler "signup"

메서드를 구현해주면 이 예외는 없어집니다:

app/controllers/SignupController.php

<?php

use Phalcon\Mvc\Controller;

class SignupController extends Controller
{
    public function indexAction()
    {

    }

    public function registerAction()
    {

    }
}

If you click the Register button again, you will see a blank page. We will be adding a view a little later that provides useful feedback. But first, we should work on the code to store the user’s inputs in a database.

According to MVC guidelines, database interactions must be done through models to ensure clean, object-oriented code.

모델 생성

Phalcon은 100% C 언어로 만들어진 최초의 PHP용 ORM을 제공합니다. 개발의 복잡성을 증가시키지 않고, 오히려 단순화 시킵니다.

첫번째 모델을 만들기 전에 우선, 데이터베이스 관리 도구나 커맨드라인 유틸리티를 이용해서 데이터베이스 테이블을 만들어야 합니다. 이 자습서에서는 MYSQL 데이터베이스를 사용해서, 가입한 사용자정보를 저장할 간단한 테이블을 다음과 같이 생성합니다:

create_users_table.sql

CREATE TABLE `users` (
    `id`    int(10)     unsigned NOT NULL AUTO_INCREMENT,
    `name`  varchar(70)          NOT NULL,
    `email` varchar(70)          NOT NULL,

    PRIMARY KEY (`id`)
);

모델은 app/models 디렉토리에 위치해야 합니다 (app/models/Users.php). 이 모델파일은 users 테이블 구조와 매칭됩니다:

app/models/Users.php

<?php

use Phalcon\Mvc\Model;

class Users extends Model
{
    public $id;
    public $name;
    public $email;
}

주의: 모델의 public 속성들은 만들어진 테이블의 필드명과 동일하다는 부분을 주의해주세요.

데이터베이스 연결 설정

데이터베이스 연결을 사용하고 이후 모델을 통해서 데이터에 접근하기 위해서는 부트스트랩 과정에서 이 부분을 정의해줘야 합니다. 데이터베이스 연결은 어플리케이션 전체에서 사용할 수 있는 많은 서비스 중에 하나입니다.

public/index.php

<?php

use Phalcon\Db\Adapter\Pdo\Mysql;

$container->set(
    'db',
    function () {
        return new Mysql(
            [
                'host'     => '127.0.0.1',
                'username' => 'root',
                'password' => 'secret',
                'dbname'   => 'tutorial',
            ]
        );
    }
);

Adjust the code snippet above as appropriate for your database.

With the correct database parameters, our model is ready to interact with the rest of the application so we can save the user’s input. First, let’s take a moment and create a view for SignupController::registerAction() that will display a message letting the user know the outcome of the save operation.

app/views/signup/register.phtml

<div class="alert alert-<?php echo $success === true ? 'success' : 'danger'; ?>">
    <?php echo $message; ?>
</div>

<?php echo $this->tag->linkTo(['/', 'Go back', 'class' => 'btn btn-primary']); ?>

Note that we have added some css styling in the code above. We will cover including the stylesheet in the Styling section below.

모델을 이용해서 데이터 저장하기

app/controllers/SignupController.php

<?php

use Phalcon\Mvc\Controller;

class SignupController extends Controller
{
    public function indexAction()
    {

    }

    public function registerAction()
    {
        $user = new Users();

        //assign value from the form to $user
        $user->assign(
            $this->request->getPost(),
            [
                'name',
                'email'
            ]
        );

        // Store and check for errors
        $success = $user->save();

        // passing the result to the view
        $this->view->success = $success;

        if ($success) {
            $message = "Thanks for registering!";
        } else {
            $message = "Sorry, the following problems were generated:<br>"
                     . implode('<br>', $user->getMessages());
        }

        // passing a message to the view
        $this->view->message = $message;
    }
}

At the beginning of the registerAction we create an empty user object using the Users class we created earlier. We will use this class to manage the record of a user. As mentioned above, the class’s public properties map to the fields of the users table in our database. Setting the relevant values in the new record and calling save() will store the data in the database for that record. The save() method returns a boolean value which indicates whether the save was successful or not.

The ORM will automatically escape the input preventing SQL injections so we only need to pass the request to the save() method.

Additional validation happens automatically on fields that are defined as not null (required). If we do not enter any of the required fields in the sign-up form our screen will look like this:

등록된 사용자 목록표시

Now we will need to get and display all the registered users in our database

The first thing that we are going to do in our indexAction of theIndexController is to show the result of the search of all the users, which is done simply by calling the static method find() on our model (Users::find()).

indexAction would change as follows:

app/controllers/IndexController.php

<?php

use Phalcon\Mvc\Controller;

class IndexController extends Controller
{
    /**
     * Welcome and user list
     */
    public function indexAction()
    {
        $this->view->users = Users::find();
    }
}

주의: view 객체의 magic 속성에 find 의 결과값을 할당합니다. 이렇게 하면 이 변수에 주어진 데이터 값이 설정되어 뷰에서 사용할 수 있게 되는 것입니다

In our view file views/index/index.phtml we can use the $users variable as follows:

The view will look like this:

views/index/index.phtml

<?php

echo "<h1>Hello!</h1>";

echo $this->tag->linkTo(["signup", "Sign Up Here!", 'class' => 'btn btn-primary']);

if ($users->count() > 0) {
    ?>
    <table class="table table-bordered table-hover">
        <thead class="thead-light">
        <tr>
            <th>#</th>
            <th>Name</th>
            <th>Email</th>
        </tr>
        </thead>
        <tfoot>
        <tr>
            <td colspan="3">Users quantity: <?php echo $users->count(); ?></td>
        </tr>
        </tfoot>
        <tbody>
        <?php foreach ($users as $user) { ?>
            <tr>
                <td><?php echo $user->id; ?></td>
                <td><?php echo $user->name; ?></td>
                <td><?php echo $user->email; ?></td>
            </tr>
        <?php } ?>
        </tbody>
    </table>
    <?php
}

As you can see our variables $users can be iterated and counted. You can get more information on how models operate in our document about models.

스타일 입히기

We can now add small design touches to our application. We can add the Bootstrap CSS in our code so that it is used throughout our views. We will add an index.phtml file in theviews folder, with the following content:

app/views/index.phtml

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Phalcon Tutorial</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
</head>
<body>
<div class="container">
    <?php echo $this->getContent(); ?>
</div>
</body>
</html>

In the above template, the most important line is the call to the getContent() method. This method returns all the content that has been generated from our view. Our application will now show:

결론

As you can see, it is easy to start building an application using Phalcon. Because Phalcon is an extension loaded in memory, the footprint of your project will be minimal, while at the same time you will enjoy a nice performance boost.

If you are ready to learn more check out the Vökuró Tutorial next.