Learn

Flight에 대해 알아보기

Flight는 PHP를 위한 빠르고 간단하며 확장 가능한 프레임워크입니다. 매우 다재다능하며 모든 종류의 웹 애플리케이션을 구축하는 데 사용할 수 있습니다. 간결성을 염두에두고 작성되었으며 쉽게 이해하고 사용할 수 있는 방식으로 작성되었습니다.

중요한 프레임워크 개념

프레임워크의 필요성

프레임워크를 사용해야 하는 이유에 대한 짧은 글입니다. 프레임워크를 사용하기 전에 프레임워크의 이점을 이해하는 것이 좋습니다.

또한 @lubiana가 작성한 훌륭한 자습서가 있습니다. 특정한 Flight에 대해 자세히 다루지는 않지만, 이 안내서는 프레임워크 주변의 주요 개념과 사용하는 이유를 이해하는 데 도움이 될 것입니다. 자습서는 여기에서 찾을 수 있습니다.

핵심 주제

자동로드

응용 프로그램에서 자체 클래스를 자동으로 로드하는 방법에 대해 알아보세요.

라우팅

웹 애플리케이션의 라우트를 관리하는 방법에 대해 알아보세요. 이에는 라우트 그룹화, 라우트 매개변수 및 미들웨어도 포함됩니다.

미들웨어

요청 및 응답을 필터링하기 위해 미들웨어를 사용하는 방법에 대해 알아보세요.

요청

응용 프로그램에서 요청 및 응답 처리 방법에 대해 알아보세요.

응답

사용자에게 응답을 보내는 방법에 대해 알아보세요.

HTML 템플릿

HTML 템플릿을 렌더링하는 데 내장된 뷰 엔진을 사용하는 방법에 대해 알아보세요.

보안

일반적인 보안 위협에서 응용 프로그램을 보호하는 방법에 대해 알아보세요.

구성

응용 프로그램을 위해 프레임워크를 구성하는 방법에 대해 알아보세요.

Flight 확장

자체 메서드 및 클래스를 추가하여 프레임워크를 확장하는 방법에 대해 알아보세요.

이벤트 및 필터링

이벤트 시스템을 사용하여 방법에 후크를 추가하고 내부 프레임워크 메서드에 후크를 추가하는 방법에 대해 알아보세요.

의존성 주입 컨테이너

의존성 주입 컨테이너(DIC)를 사용하여 응용 프로그램의 종속성을 관리하는 방법에 대해 알아보세요.

프레임워크 API

프레임워크의 핵심 메서드에 대해 알아보세요.

v3로 이전

대부분의 경우 하위 호환성이 유지되었지만, v2에서 v3로 마이그레이션할 때 고려해야 할 사항이 있습니다.

Learn/stopping

중지

halt 메소드를 호출하여 언제든지 프레임워크를 중지할 수 있습니다:

Flight::halt();

선택적으로 HTTP 상태 코드와 메시지를 지정할 수도 있습니다:

Flight::halt(200, '잠시 후에 돌아오겠습니다...');

halt를 호출하면 해당 시점까지의 모든 응답 내용이 폐기됩니다. 프레임워크를 중지하고 현재 응답을 출력하려면 stop 메소드를 사용하세요:

Flight::stop();

Learn/errorhandling

오류 처리

오류 및 예외

모든 오류와 예외는 Flight에서 잡혀 error 메서드로 전달됩니다. 기본 동작은 일반적인 HTTP 500 내부 서버 오류 응답을 함께 일부 오류 정보를 보내는 것입니다.

사용자 정의로이 동작을 재정의 할 수 있습니다:

Flight::map('error', function (Throwable $error) {
  // 오류 처리
  echo $error->getTraceAsString();
});

기본적으로 오류는 웹 서버에 기록되지 않습니다. 이를 활성화하여 변경할 수 있습니다:

Flight::set('flight.log_errors', true);

찾을 수 없음

URL을 찾을 수 없을 때, Flight는 notFound 메서드를 호출합니다. 기본 동작은 간단한 메시지와 함께 HTTP 404 찾을 수 없음 응답을 보내는 것입니다.

사용자 정의로이 동작을 재정의 할 수 있습니다:

Flight::map('notFound', function () {
  // 찾을 수 없음 처리
});

Learn/migrating_to_v3

v3로 이전하기

거의 대부분 역 호환성이 유지되었지만, v2에서 v3로 이전할 때 알아야 할 몇 가지 변경 사항이 있습니다.

출력 버퍼링 동작 (3.5.0)

출력 버퍼링은 PHP 스크립트에서 생성된 출력물이 클라이언트에 전송되기 전에 버퍼(내부 PHP)에 저장되는 과정입니다. 이를 통해 클라이언트에 전송되기 전에 출력물을 수정할 수 있습니다.

MVC 애플리케이션에서 Controller가 "관리자"이며 뷰가 하는 일을 관리합니다. 컨트롤러 바깥에서(또는 Flight의 경우에는 때로는 익명 함수 안에서) 생성된 출력물은 MVC 패턴을 깨뜨립니다. 이 변경은 MVC 패턴과 더 일치하도록 하고 프레임워크를 예측 가능하고 사용하기 쉽게 만드는 것입니다.

v2에서는 출력 버퍼링이 자체 출력 버퍼를 일관되게 닫지 않는 방식으로 처리되어 유닛 테스트스트리밍이 더 어려워졌습니다. 대부분의 사용자에게는 이 변경이 실제로 영향을 미칠 수도 있고 그렇지 않을 수도 있습니다. 그러나 호출 가능한 함수 및 컨트롤러 바깥에서 콘텐츠를 echo하는 경우(예: 후크에서) 문제가 발생할 수 있습니다. 후크에서 콘텐츠를 echo하고 프레임워크가 실제로 실행되기 전에 이전에 작동했을 수도 있지만, 앞으로는 작동하지 않을 것입니다.

문제가 발생할 수 있는 경우

// index.php
require 'vendor/autoload.php';

// 예시일 뿐입니다
define('START_TIME', microtime(true));

function hello() {
    echo 'Hello World';
}

Flight::map('hello', 'hello');
Flight::after('hello', function(){
    // 이 부분은 실제로 괜찮습니다
    echo '<p>This Hello World phrase was brought to you by the letter "H"</p>';
});

Flight::before('start', function(){
    // 이런 것들은 오류를 발생시킵니다
    echo '<html><head><title>My Page</title></head><body>';
});

Flight::route('/', function(){
    // 이 부분은 실제로 괜찮습니다
    echo 'Hello World';

    // 이것도 아주 괜찮습니다
    Flight::hello();
});

Flight::after('start', function(){
    // 이것이 오류를 발생시킵니다
    echo '<div>Your page loaded in '.(microtime(true) - START_TIME).' seconds</div></body></html>';
});

v2 렌더링 동작 켜기

기존 코드를 재작성하지 않고 v3에서 작동하도록 만들기 위해 이전 방법으로 유지할 수 있습니까? 네, 가능합니다! flight.v2.output_buffering 구성 옵션을 true로 설정하여 v2 렌더링 동작을 켤 수 있습니다. 이를 통해 기존의 렌더링 동작을 계속 사용할 수 있지만, 앞으로 수정하는 것이 좋습니다. 프레임워크의 v4에서는 이 부분이 제거될 것입니다.

// index.php
require 'vendor/autoload.php';

Flight::set('flight.v2.output_buffering', true);

Flight::before('start', function(){
    // 이제 이 부분은 괜찮아질 것입니다
    echo '<html><head><title>My Page</title></head><body>';
});

// 더 많은 코드 

디스패처 변경 사항 (3.7.0)

Dispatcher::invokeMethod(), Dispatcher::execute() 등과 같이 Dispatcher의 정적 메서드를 직접 호출했다면 해당 메서드를 직접 호출하지 않도록 코드를 업데이트해야 합니다. Dispatcher는 더 객체 지향적으로 변환되어 DI(Dependency Injection) 컨테이너를 더 쉽게 사용할 수 있게 되었습니다. Dispatcher가 했던 것과 유사한 방식으로 메서드를 호출해야 하는 경우 $result = $class->$method(...$params); 또는 call_user_func_array()와 같은 것을 수동으로 사용할 수 있습니다.

Learn/configuration

구성

Flight의 특정 동작을 사용자 지정할 수 있습니다. 구성 값을 설정하여set 메소드를 통해 할 수 있습니다.

Flight::set('flight.log_errors', true);

사용 가능한 구성 설정

다음은 사용 가능한 모든 구성 설정 목록입니다:

변수

Flight를 사용하여 변수를 저장하여 응용 프로그램의 모든 곳에서 사용할 수 있습니다.

// 변수 저장
Flight::set('id', 123);

// 응용 프로그램 다른 곳에서
$id = Flight::get('id');

변수가 설정되었는지 확인하려면 다음을 수행할 수 있습니다:

if (Flight::has('id')) {
  // 작업 수행
}

변수를 지울 수 있습니다:

// id 변수 지우기
Flight::clear('id');

// 모든 변수 지우기
Flight::clear();

Flight는 구성 목적으로도 변수를 사용합니다.

Flight::set('flight.log_errors', true);

오류 처리

오류 및 예외

Flight에서 모든 오류와 예외가 잡히고 error 메소드로 전달됩니다. 기본 동작은 일반적인 HTTP 500 내부 서버 오류 응답을 보내어 일부 오류 정보를 표시하는 것입니다.

사용자 정의를 위해 이 동작을 재정의할 수 있습니다:

Flight::map('error', function (Throwable $error) {
  // 오류 처리
  echo $error->getTraceAsString();
});

기본적으로 오류는 웹 서버에 로깅되지 않습니다. 구성을 변경하여 이를 활성화할 수 있습니다:

Flight::set('flight.log_errors', true);

찾을 수 없음

URL을 찾을 수 없을 때, Flight는 notFound 메소드를 호출합니다. 기본 동작은 간단한 메시지와 함께 HTTP 404 찾을 수 없음 응답을 보내는 것입니다.

사용자 정의를 위해 이 동작을 재정의할 수 있습니다:

Flight::map('notFound', function () {
  // 찾을 수 없음 처리
});

Learn/security

보안

웹 응용 프로그램에 대한 보안은 중요합니다. 응용 프로그램이 안전하고 사용자 데이터가 안전한지 확인해야 합니다. Flight은 웹 응용 프로그램의 보안을 지원하기 위한 여러 기능을 제공합니다.

헤더

HTTP 헤더는 웹 응용 프로그램을 보호하는 가장 쉬운 방법 중 하나입니다. 헤더를 사용하여 클릭재킹, XSS 및 기타 공격을 방지할 수 있습니다. 이러한 헤더를 응용 프로그램에 추가하는 방법은 여러 가지가 있습니다.

보안 헤더를 확인할 수 있는 두 가지 좋은 웹 사이트는 securityheaders.comobservatory.mozilla.org입니다.

수동으로 추가

Flight\Response 객체의 header 메서드를 사용하여 이러한 헤더를 수동으로 추가할 수 있습니다.

// 클릭재킹을 방지하기 위해 X-Frame-Options 헤더를 설정합니다
Flight::response()->header('X-Frame-Options', 'SAMEORIGIN');

// XSS를 방지하기 위해 Content-Security-Policy 헤더를 설정합니다
// 참고: 이 헤더는 매우 복잡해질 수 있으므로
//  응용 프로그램에 대한 인터넷의 예제를 참고하시기 바랍니다
Flight::response()->header("Content-Security-Policy", "default-src 'self'");

// XSS를 방지하기 위해 X-XSS-Protection 헤더를 설정합니다
Flight::response()->header('X-XSS-Protection', '1; mode=block');

// MIME 스니핑을 방지하기 위해 X-Content-Type-Options 헤더를 설정합니다
Flight::response()->header('X-Content-Type-Options', 'nosniff');

// 리퍼러 정보를 전송하는 방식을 제어하기 위해 Referrer-Policy 헤더를 설정합니다
Flight::response()->header('Referrer-Policy', 'no-referrer-when-downgrade');

// HTTPS를 강제하기 위해 Strict-Transport-Security 헤더를 설정합니다
Flight::response()->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');

// 사용할 수 있는 기능 및 API를 제어하기 위해 Permissions-Policy 헤더를 설정합니다
Flight::response()->header('Permissions-Policy', 'geolocation=()');

이것들은 bootstrap.php 또는 index.php 파일의 맨 위에 추가할 수 있습니다.

필터로 추가

다음과 같이 필터/훅에 추가할 수도 있습니다:

// 필터에서 헤더 추가
Flight::before('start', function() {
    Flight::response()->header('X-Frame-Options', 'SAMEORIGIN');
    Flight::response()->header("Content-Security-Policy", "default-src 'self'");
    Flight::response()->header('X-XSS-Protection', '1; mode=block');
    Flight::response()->header('X-Content-Type-Options', 'nosniff');
    Flight::response()->header('Referrer-Policy', 'no-referrer-when-downgrade');
    Flight::response()->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
    Flight::response()->header('Permissions-Policy', 'geolocation=()');
});

미들웨어로 추가

미들웨어 클래스로도 추가할 수 있습니다. 코드를 깔끔하고 구성되어 있게 유지하는 좋은 방법입니다.

// app/middleware/SecurityHeadersMiddleware.php

namespace app\middleware;

class SecurityHeadersMiddleware
{
    public function before(array $params): void
    {
        Flight::response()->header('X-Frame-Options', 'SAMEORIGIN');
        Flight::response()->header("Content-Security-Policy", "default-src 'self'");
        Flight::response()->header('X-XSS-Protection', '1; mode=block');
        Flight::response()->header('X-Content-Type-Options', 'nosniff');
        Flight::response()->header('Referrer-Policy', 'no-referrer-when-downgrade');
        Flight::response()->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
        Flight::response()->header('Permissions-Policy', 'geolocation=()');
    }
}

// index.php 또는 라우트가 있는 곳
// FYI, 이 빈 문자열 그룹은 모든 라우트에 대한 전역 미들웨어로 작동합니다.
// 물론 동일한 작업을 수행하거나 특정 라우트에만 추가할 수도 있습니다.
Flight::group('', function(Router $router) {
    $router->get('/users', [ 'UserController', 'getUsers' ]);
    // 더 많은 라우트
}, [ new SecurityHeadersMiddleware() ]);

교차 사이트 요청 위조 (CSRF)

교차 사이트 요청 위조 (CSRF)는 악의적인 웹 사이트가 사용자 브라우저로 요청을 보내도록하는 공격 유형입니다. 이를 통해 사용자의 동의 없이 웹 사이트에서 작업을 수행할 수 있습니다. Flight에는 기본 CSRF 방어 메커니즘이 제공되지 않지만 미들웨어를 사용하여 쉽게 직접 구현할 수 있습니다.

설정

먼저 CSRF 토큰을 생성하고 사용자 세션에 저장해야 합니다. 그런 다음 이 토큰을 양식에서 사용하고 양식을 제출할 때 확인할 수 있습니다.

// CSRF 토큰을 생성하고 사용자 세션에 저장합니다
// (Flight에 세션 객체를 생성했다고 가정)
// 세션당 한 번의 토큰만 생성하면 됩니다 (같은 사용자의 여러 탭과 요청에서 작동합니다)
if(Flight::session()->get('csrf_token') === null) {
    Flight::session()->set('csrf_token', bin2hex(random_bytes(32)) );
}
<!-- 양식에서 CSRF 토큰 사용 -->
<form method="post">
    <input type="hidden" name="csrf_token" value="<?= Flight::session()->get('csrf_token') ?>">
    <!-- 다른 양식 필드 -->
</form>

Latte 사용

Latte 템플릿에서 CSRF 토큰을 출력하기 위해 사용자 지정 함수를 설정할 수도 있습니다.

// CSRF 토큰을 출력하는 사용자 지정 함수 설정
// 참고: 뷰는 뷰 엔진으로 Latte로 구성되어 있습니다
Flight::view()->addFunction('csrf', function() {
    $csrfToken = Flight::session()->get('csrf_token');
    return new \Latte\Runtime\Html('<input type="hidden" name="csrf_token" value="' . $csrfToken . '">');
});

이제 Latte 템플릿에서 csrf() 함수를 사용하여 CSRF 토큰을 출력할 수 있습니다.

<form method="post">
    {csrf()}
    <!-- 다른 양식 필드 -->
</form>

간단하고 명료하지요?

CSRF 토큰 확인

이벤트 필터를 사용하여 CSRF 토큰을 확인할 수 있습니다:

// 이 미들웨어는 요청이 POST 요청인지 확인하고 그렇다면 CSRF 토큰이 유효한지 확인합니다
Flight::before('start', function() {
    if(Flight::request()->method == 'POST') {

        // 양식 값에서 csrf 토큰을 가져옵니다
        $token = Flight::request()->data->csrf_token;
        if($token !== Flight::session()->get('csrf_token')) {
            Flight::halt(403, '잘못된 CSRF 토큰');
        }
    }
});

또는 미들웨어 클래스를 사용할 수 있습니다:

// app/middleware/CsrfMiddleware.php

namespace app\middleware;

class CsrfMiddleware
{
    public function before(array $params): void
    {
        if(Flight::request()->method == 'POST') {
            $token = Flight::request()->data->csrf_token;
            if($token !== Flight::session()->get('csrf_token')) {
                Flight::halt(403, '잘못된 CSRF 토큰');
            }
        }
    }
}

// index.php 또는 라우트가 있는 곳
Flight::group('', function(Router $router) {
    $router->get('/users', [ 'UserController', 'getUsers' ]);
    // 더 많은 라우트
}, [ new CsrfMiddleware() ]);

XSS (교차 사이트 스크립팅)

교차 사이트 스크립팅 (XSS)은 악성 웹 사이트가 코드를 웹 사이트에 주입할 수 있는 공격 유형입니다. 대부분은 사용자가 작성하는 양식 값에서 가능합니다. 사용자의 출력을 절대로 신뢰해서는 안 됩니다! 항상 사용자를 세계 최고의 해커로 간주하십시오. 악성 JavaScript 또는 HTML을 페이지에 주입하여 사용자의 정보를 도용하거나 웹 사이트에서 작업을 수행할 수 있습니다. Flight의 view 클래스를 사용하여 XSS 공격을 방지하기 위해 쉽게 출력을 이스케이프할 수 있습니다.

// 사용자가 이를 이름으로 사용하려고 시도하는 경우를 가정해 봅시다
$name = '<script>alert("XSS")</script>';

// 이렇게 출력이 이스케이프됩니다
Flight::view()->set('name', $name);
// 이렇게 출력됩니다: &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;

// 뷰 클래스로 Latte와 같은 것을 사용하면 이도 자동으로 이스케이프됩니다.
Flight::view()->render('template', ['name' => $name]);

SQL 인젝션

SQL 인젝션은 악성 사용자가 데이터베이스에 SQL 코드를 주입할 수 있는 공격 유형입니다. 이를 통해 데이터베이스에서 정보를 도용하거나 데이터베이스에서 작업을 수행할 수 있습니다. 다시 한 번 사용자로부터의 입력을 절대로 신뢰해서는 안 됩니다! 항상 사용자가 극악한 짓을 한다고 가정하십시오. PDO 객체에서 준비된 문을 사용하면 SQL 인젝션을 방지할 수 있습니다.

// Flight::db()를 PDO 객체로 등록 했다고 가정하면
$statement = Flight::db()->prepare('SELECT * FROM users WHERE username = :username');
$statement->execute([':username' => $username]);
$users = $statement->fetchAll();

// PdoWrapper 클래스를 사용하면 한 줄로 간단하게 수행할 수 있습니다
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE username = :username', [ 'username' => $username ]);

// ? 자리 표시자를 가진 PDO 객체로도 똑같이 할 수 있습니다
$statement = Flight::db()->fetchAll('SELECT * FROM users WHERE username = ?', [ $username ]);

// 이렇게 한다는 것을 절대로 약속해주세요...
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE username = '{$username}' LIMIT 5");
// 왜냐하면 $username = "' OR 1=1; -- "; 일 수 있으니까요
// 쿼리가 이렇게 구성됩니다
// SELECT * FROM users WHERE username = '' OR 1=1; -- LIMIT 5
// 이상하게 보일 수 있지만 동작하는 유효한 쿼리입니다. 실제로
// 이것은 모든 사용자를 반환하는 매우 흔한 SQL 인젝션 공격입니다.

CORS

교차 출처 리소스 공유 (CORS)는 웹 페이지의 많은 리소스 (예: 글꼴, JavaScript 등)를 자신과 다른 도메인에서 요청할 수 있는 메커니즘입니다. Flight에 기본 기능은 없지만 CSRF와 유사하게 미들웨어나 이벤트 필터를 사용하여 쉽게 처리할 수 있습니다.

// app/middleware/CorsMiddleware.php

namespace app\middleware;

class CorsMiddleware
{
    public function before(array $params): void
    {
        $response = Flight::response();
        if (isset($_SERVER['HTTP_ORIGIN'])) {
            $this->allowOrigins();
            $response->header('Access-Control-Allow-Credentials: true');
            $response->header('Access-Control-Max-Age: 86400');
        }

        if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
            if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) {
                $response->header(
                    'Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS'
                );
            }
            if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
                $response->header(
                    "Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}"
                );
            }
            $response->send();
            exit(0);
        }
    }

    private function allowOrigins(): void
    {
        // 여기서 허용된 호스트를 사용자 정의합니다.
        $allowed = [
            'capacitor://localhost',
            'ionic://localhost',
            'http://localhost',
            'http://localhost:4200',
            'http://localhost:8080',
            'http://localhost:8100',
        ];

        if (in_array($_SERVER['HTTP_ORIGIN'], $allowed)) {
            $response = Flight::response();
            $response->header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}");
        }
    }
}

// index.php 또는 라우트가 있는 곳
Flight::route('/users', function() {
    $users = Flight::db()->fetchAll('SELECT * FROM users');
    Flight::json($users);
})->addMiddleware(new CorsMiddleware());

결론

보안은 중요하며 웹 응용 프로그램을 안전하게 유지하는 것이 중요합니다. Flight은 웹 응용 프로그램을 안전하게 유지하는 데 도움이 되는 여러 가지 기능을 제공하지만 항상 주의해야 하며 사용자 데이터를 안전하게 유지할 수 있도록 최선을 다해야 합니다. 최악을 가정하고 사용자 입력을 신뢰해서는 안 됩니다. 출력을 이스케이프하고 SQL 인젝션을 방지하기 위해 준비된 문을 사용하십시오. CSRF 및 CORS 공격으로부터 라우트를 보호하기 위해 항상 미들웨어를 사용하십시오. 모든 이러한 것들을 수행한다면 안전한 웹 응용 프로그램을 구축하는 길에 올라설 수 있을 것입니다.

Learn/overriding

재정의

Flight는 코드를 수정하지 않고도 기본 기능을 사용자 정의하여 필요에 맞게 재정의할 수 있도록 합니다.

예를 들어, Flight가 URL을 경로에 매핑할 수 없는 경우, notFound 메서드를 호출하여 일반적인 HTTP 404 응답을 보냅니다. 이 동작은 map 메서드를 사용하여 재정의할 수 있습니다:

Flight::map('notFound', function() {
  // 사용자 정의 404 페이지 표시
  include 'errors/404.html';
});

또한 Flight는 프레임워크의 핵심 구성 요소를 교체할 수 있도록 합니다. 예를 들어 기본 Router 클래스를 사용자 정의 클래스로 교체할 수 있습니다:

// 사용자 정의 클래스를 등록
Flight::register('router', MyRouter::class);

// Flight가 Router 인스턴스를 로드할 때, 사용자 정의 클래스가 로드됩니다
$myrouter = Flight::router();

그러나 mapregister와 같은 프레임워크 메서드는 재정의할 수 없습니다. 이를 시도하면 오류가 발생합니다.

Learn/routing

라우팅

참고: 라우팅에 대해 더 알고 싶으신가요? 더 깊은 설명을 위해 "왜 프레임워크를 사용해야 할까요?" 페이지를 확인해보세요.

Flight에서의 기본적인 라우팅은 URL 패턴을 콜백 함수나 클래스와 메소드의 배열과 일치시킴으로써 이루어집니다.

Flight::route('/', function(){
    echo '안녕, 세상아!';
});

라우트는 정의된 순서대로 일치합니다. 요청과 일치하는 첫 번째 라우트가 호출됩니다.

콜백/함수

콜백은 호출 가능한 임의의 객체가 될 수 있습니다. 예를 들어 일반 함수를 사용할 수 있습니다.

function hello(){
    echo '안녕, 세상아!';
}

Flight::route('/', 'hello');

클래스

정적 클래스 메소드 또한 사용할 수 있습니다.

class Greeting {
    public static function hello() {
        echo '안녕, 세상아!';
    }
}

Flight::route('/', [ 'Greeting','hello' ]);

또한 객체를 먼저 생성한 다음 메소드를 호출할 수도 있습니다.


// Greeting.php
class Greeting
{
    public function __construct() {
        $this->name = '홍길동';
    }

    public function hello() {
        echo "{$this->name}님, 안녕하세요!";
    }
}

// index.php
$greeting = new Greeting();

Flight::route('/', [ $greeting, 'hello' ]);
// 객체를 먼저 생성하지 않고도 이 작업을 수행할 수도 있습니다.
// 참고: 생성자로 인수가 주입되지 않을 것임을 유념하세요
Flight::route('/', [ 'Greeting', 'hello' ]);

DIC (의존성 주입 컨테이너)를 통한 의존성 주입

컨테이너(PSR-11, PHP-DI, Dice 등)를 통해 의존성 주입을 사용하고 싶다면, 객체를 직접 생성하고 컨테이너를 사용하여 객체를 생성하는 라우트 유형이 유일합니다. 또는 클래스와 메소드를 호출하도록 문자열을 사용할 수도 있습니다. 더 많은 정보를 위해 의존성 주입 페이지를 방문해보세요.

다음은 간단한 예제입니다:


use flight\database\PdoWrapper;

// Greeting.php
class Greeting
{
    protected PdoWrapper $pdoWrapper;
    public function __construct(PdoWrapper $pdoWrapper) {
        $this->pdoWrapper = $pdoWrapper;
    }

    public function hello(int $id) {
        // $this->pdoWrapper로 무언가 수행
        $name = $this->pdoWrapper->fetchField("SELECT name FROM users WHERE id = ?", [ $id ]);
        echo "안녕하세요, 세상아! 제 이름은 {$name}입니다!";
    }
}

// index.php

// 필요한 매개변수로 컨테이너를 설정합니다
// PSR-11에 대한 추가 정보는 Dependency Injection 페이지를 참조하세요
$dice = new \Dice\Dice();

// 변수를 '$dice = '로 다시 할당하는 것을 잊지 마세요!!!!
$dice = $dice->addRule('flight\database\PdoWrapper', [
    'shared' => true,
    'constructParams' => [ 
        'mysql:host=localhost;dbname=test', 
        'root',
        'password'
    ]
]);

// 컨테이너 핸들러를 등록합니다
Flight::registerContainerHandler(function($class, $params) use ($dice) {
    return $dice->create($class, $params);
});

// 일반적인 라우트와 같이 사용합니다
Flight::route('/hello/@id', [ 'Greeting', 'hello' ]);
// 또는
Flight::route('/hello/@id', 'Greeting->hello');
// 또는
Flight::route('/hello/@id', 'Greeting::hello');

Flight::start();

메소드 라우팅

기본적으로, 라우트 패턴은 모든 요청 메소드와 일치합니다. 특정 메소드에 응답하기 위해 URL 앞에 식별자를 배치할 수 있습니다.

Flight::route('GET /', function () {
  echo 'GET 요청을 받았습니다.';
});

Flight::route('POST /', function () {
  echo 'POST 요청을 받았습니다.';
});

// 변수를 가져오기 위한 메소드로 Flight::get()을 사용할 수 없습니다.
// 라우트를 생성하는 데 사용되는 메소드가 아니기 때문입니다.
// Flight::post('/', function() { /* 코드 */ });
// Flight::patch('/', function() { /* 코드 */ });
// Flight::put('/', function() { /* 코드 */ });
// Flight::delete('/', function() { /* 코드 */ });

| 구분자를 사용하여 여러 메소드를 하나의 콜백에 매핑할 수도 있습니다:

Flight::route('GET|POST /', function () {
  echo 'GET 또는 POST 요청을 받았습니다.';
});

또한 일부 헬퍼 메소드를 사용할 수 있는 Router 객체를 가져올 수도 있습니다:


$router = Flight::router();

// 모든 메소드에 매핑
$router->map('/', function() {
    echo '안녕, 세상아!';
});

// GET 요청
$router->get('/users', function() {
    echo '사용자들';
});
// $router->post();
// $router->put();
// $router->delete();
// $router->patch();

정규 표현식

라우트에 정규 표현식을 사용할 수 있습니다:

Flight::route('/user/[0-9]+', function () {
  // 이는 /user/1234와 매치합니다.
});

이 방법은 사용할 수 있지만, 명명된 매개변수나 정규 표현식을 사용하는 것이 추천됩니다. 이러한 방법은 더 가독성이 높고 유지보수가 쉽기 때문입니다.

명명된 매개변수

라우트에 전달되는 콜백 함수에 명명된 매개변수를 지정할 수 있습니다.

Flight::route('/@name/@id', function (string $name, string $id) {
  echo "안녕하세요, $name ($id)님!";
});

: 구분자를 사용하여 명명된 매개변수와 함께 정규 표현식을 사용할 수도 있습니다:

Flight::route('/@name/@id:[0-9]{3}', function (string $name, string $id) {
  // 이는 /bob/123과 매치합니다.
  // 하지만 /bob/12345와는 매치하지 않습니다.
});

참고: 명명된 매개변수와 정규 표현식을 사용하여 매개변수와 함께 ()를 매치하는 것은 지원되지 않습니다. :'(

선택적 매개변수

매치되는 선택적 매개변수를 정의할 수 있습니다.

Flight::route(
  '/blog(/@year(/@month(/@day)))',
  function(?string $year, ?string $month, ?string $day) {
    // 다음 URL들과 매치됩니다:
    // /blog/2012/12/10
    // /blog/2012/12
    // /blog/2012
    // /blog
  }
);

매치되지 않은 선택적 매개변수들은 NULL로 전달됩니다.

와일드카드

매치는 개별 URL 세그먼트에 대해서만 이루어집니다. 여러 세그먼트를 매치하려면 * 와일드카드를 사용할 수 있습니다.

Flight::route('/blog/*', function () {
  // 이는 /blog/2000/02/01과 매치합니다.
});

모든 요청을 단일 콜백으로 라우트하려면 다음과 같이 할 수 있습니다:

Flight::route('*', function () {
  // 무언가 수행
});

패싱

콜백 함수에서 true를 반환함으로써 다음 일치하는 라우트로 실행을 전달할 수 있습니다.

Flight::route('/user/@name', function (string $name) {
  // 어떤 조건을 확인
  if ($name !== "Bob") {
    // 다음 라우트로 이동
    return true;
  }
});

Flight::route('/user/*', function () {
  // 이 코드가 실행됩니다
});

라우트 별칭

라우트에 별칭을 지정하여 URL을 동적으로 생성할 수 있습니다 (예: 템플릿과 같은 용도).

Flight::route('/users/@id', function($id) { echo '사용자:'.$id; }, false, 'user_view');

// 나중에 코드 어딘가에
Flight::getUrl('user_view', [ 'id' => 5 ]); // '/users/5'을 반환합니다

이것은 URL이 변경되어도 동작하며 특히 유용합니다. 예를 들어 위의 예에서 사용자가 /admin/users/@id로 이동했다고 가정해 봅시다. 별칭 사용을 하면 별칭을 참조하는 모든 곳을 수정할 필요가 없습니다. 별칭은 이제 /admin/users/5와 같이 반환될 것입니다.

그룹에서도 라우트 별칭을 사용할 수 있습니다:

Flight::group('/users', function() {
    Flight::route('/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
});

// 나중에 코드 어딘가에
Flight::getUrl('user_view', [ 'id' => 5 ]); // '/users/5'을 반환합니다

라우트 정보

일치하는 라우트 정보를 검사하려면 라우트 메소드의 세 번째 매개변수로 true를 전달하면 됩니다. 라우트 객체는 항상 콜백 함수에 전달되는 마지막 매개변수가 됩니다.

Flight::route('/', function(\flight\net\Route $route) {
  // 일치하는 HTTP 메소드 배열
  $route->methods;

  // 명명된 매개변수 배열
  $route->params;

  // 일치하는 정규 표현식
  $route->regex;

  // URL 패턴에 사용된 '*'의 내용
  $route->splat;

  // URL 경로 표시....정말 필요한 경우
  $route->pattern;

  // 이 라우트에 할당된 미들웨어 표시
  $route->middleware;

  // 이 라우트에 할당된 별칭 표시
  $route->alias;
}, true);

라우트 그룹화

때로는 관련된 라우트들을 그룹화하고 싶을 수 있습니다(예: /api/v1과 같이). 이를 위해 group 메소드를 사용할 수 있습니다:

Flight::group('/api/v1', function () {
  Flight::route('/users', function () {
    // /api/v1/users와 일치
  });

  Flight::route('/posts', function () {
    // /api/v1/posts와 일치
  });
});

그룹 중첩도 가능합니다:

Flight::group('/api', function () {
  Flight::group('/v1', function () {
    // Flight::get()은 변수를 가져오는 메소드이며, 라우트를 설정하지 않습니다! 아래 객체 컨텍스트를 참조하세요
    Flight::route('GET /users', function () {
      // GET /api/v1/users와 일치
    });

    Flight::post('/posts', function () {
      // POST /api/v1/posts와 일치
    });

    Flight::put('/posts/1', function () {
      // PUT /api/v1/posts와 일치
    });
  });
  Flight::group('/v2', function () {

    // Flight::get()은 변수를 가져오는 메소드이며, 라우트를 설정하지 않습니다! 아래 객체 컨텍스트를 참조하세요
    Flight::route('GET /users', function () {
      // GET /api/v2/users와 일치
    });
  });
});

객체 컨텍스트를 사용한 그룹화

Engine 객체와 함께 라우트 그룹화를 계속 사용할 수 있습니다:

$app = new \flight\Engine();
$app->group('/api/v1', function (Router $router) {

  // $router 변수를 사용합니다
  $router->get('/users', function () {
    // GET /api/v1/users와 일치
  });

  $router->post('/posts', function () {
    // POST /api/v1/posts와 일치
  });
});

스트리밍

streamWithHeaders() 메소드를 사용하여 이제 클라이언트에게 응답을 스트리밍할 수 있습니다. 큰 파일을 전송하거나 오래 실행되는 프로세스 또는 큰 응답을 생성하는 데 유용합니다. 라우트 스트리밍은 일반 라우트와는 약간 다르게 처리됩니다.

참고: 응답 스트리밍은 flight.v2.output_buffering이 false로 설정되어 있어야만 가능합니다.

Flight::route('/stream-users', function() {

    // 라우트가 실행된 이후 여기서 추가 헤더를 설정해야 한다면
    // echo 되기 전에 정의해야 합니다.
    // 모든 헤더는 header() 함수를 사용하거나
    // Flight::response()->setRealHeader() 함수를 사용해야 합니다.
    header('Content-Disposition: attachment; filename="users.json"');
    // 또는
    Flight::response()->setRealHeader('Content-Disposition', 'attachment; filename="users.json"');

    // 데이터를 가져오는 방식과 무관하게, 단순 예제로...
    $users_stmt = Flight::db()->query("SELECT id, first_name, last_name FROM users");

    echo '{';
    $user_count = count($users);
    while($user = $users_stmt->fetch(PDO::FETCH_ASSOC)) {
        echo json_encode($user);
        if(--$user_count > 0) {
            echo ',';
        }

        // 데이터를 클라이언트로 보내려면 여기가 필요합니다
        ob_flush();
    }
    echo '}';

// 스트리밍을 시작하기 전에 헤더를 설정하는 방법을 보여줍니다.
})->streamWithHeaders([
    'Content-Type' => 'application/json',
    // 옵션 상태 코드, 기본값은 200입니다
    'status' => 200
]);

Learn/variables

변수

Flight은 애플리케이션 내의 어디에서나 사용할 수 있도록 변수를 저장할 수 있게 해줍니다.

// 변수 저장하기
Flight::set('id', 123);

// 애플리케이션의 다른 곳에서
$id = Flight::get('id');

변수가 설정되었는지 확인하려면 다음을 수행할 수 있습니다:

if (Flight::has('id')) {
  // 무언가를 수행
}

다음을 수행하여 변수를 지울 수 있습니다:

// id 변수 지우기
Flight::clear('id');

// 모든 변수 지우기
Flight::clear();

Flight은 구성 목적으로도 변수를 사용합니다.

Flight::set('flight.log_errors', true);

Learn/dependency_injection_container

의존성 주입 컨테이너

소개

의존성 주입 컨테이너(DIC)는 응용 프로그램의 의존성을 관리하는 강력한 도구입니다. 이는 현대 PHP 프레임워크의 핵심 개념이며 객체의 인스턴스화 및 구성을 관리하는 데 사용됩니다. 일부 DIC 라이브러리 예시로는: Dice, Pimple, PHP-DI, 그리고 league/container이 있습니다.

DIC는 클래스를 중앙 위치에서 생성하고 관리할 수 있게 해준다는 멋진 방법입니다. 이것은 동일한 객체를 여러 클래스(예: 컨트롤러)에 전달해야 할 때 유용합니다. 간단한 예제가 이를 더 잘 이해하게 도와줄 수 있습니다.

기본 예제

과거의 방식은 다음과 같이 보일 수 있습니다:


require 'vendor/autoload.php';

// 데이터베이스에서 사용자를 관리하는 클래스
class UserController {

    protected PDO $pdo;

    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }

    public function view(int $id) {
        $stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = :id');
        $stmt->execute(['id' => $id]);

        print_r($stmt->fetch());
    }
}

$User = new UserController(new PDO('mysql:host=localhost;dbname=test', 'user', 'pass'));
Flight::route('/user/@id', [ $UserController, 'view' ]);

Flight::start();

위의 코드에서 새로운 PDO 객체를 만들고 UserController 클래스에 전달하고 있는 것을 볼 수 있습니다. 이는 작은 응용 프로그램에 적합하지만 응용 프로그램이 성장함에 따라 동일한 PDO 객체를 여러 곳에서 생성하게 되는 것을 발견할 것입니다. 이것이 DIC가 유용한 이유입니다.

다음은 동일한 예제를 DIC(Dice를 사용하여)를 사용하여 하는 방법입니다:


require 'vendor/autoload.php';

// 위와 동일한 클래스. 아무것도 변경되지 않았습니다
class UserController {

    protected PDO $pdo;

    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }

    public function view(int $id) {
        $stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = :id');
        $stmt->execute(['id' => $id]);

        print_r($stmt->fetch());
    }
}

// 새로운 컨테이너 생성
$container = new \Dice\Dice;
// 아래와 같이 자신에게 다시 할당하는 것을 잊지 마세요!
$container = $container->addRule('PDO', [
    // shared는 동일한 객체가 매번 반환됨을 의미합니다
    'shared' => true,
    'constructParams' => ['mysql:host=localhost;dbname=test', 'user', 'pass' ]
]);

// 이것은 Flight가 사용하도록 컨테이너 핸들러를 등록합니다.
Flight::registerContainerHandler(function($class, $params) use ($container) {
    return $container->create($class, $params);
});

// 이제 컨테이너를 사용하여 UserController를 생성할 수 있습니다
Flight::route('/user/@id', [ 'UserController', 'view' ]);
// 또는 대안적으로 아래처럼 라우트를 정의할 수 있습니다
Flight::route('/user/@id', 'UserController->view');
// 또는
Flight::route('/user/@id', 'UserController::view');

Flight::start();

아마도 예제에 많은 추가 코드가 추가되었다고 생각할 수 있습니다. 마법은 PDO 객체가 필요한 다른 컨트롤러가 있는 경우입니다.


// 모든 컨트롤러가 PDO 객체를 필요로 하는 생성자를 가지고 있다면
// 아래의 각 라우트는 자동으로 주입할 것입니다!!!
Flight::route('/company/@id', 'CompanyController->view');
Flight::route('/organization/@id', 'OrganizationController->view');
Flight::route('/category/@id', 'CategoryController->view');
Flight::route('/settings', 'SettingsController->view');

DIC를 활용하면 유닛 테스트가 훨씬 쉬워집니다. 모의 객체를 생성하고 클래스에 전달할 수 있습니다. 이것은 응용 프로그램의 테스트를 작성할 때 엄청난 이점이 됩니다!

PSR-11

Flight는 PSR-11 호환 컨테이너도 사용할 수 있습니다. 이는 PSR-11 인터페이스를 구현하는 모든 컨테이너를 사용할 수 있다는 것을 의미합니다. 다음은 League의 PSR-11 컨테이너를 사용하는 예제입니다:


require 'vendor/autoload.php';

// 위와 동일한 UserController 클래스

$container = new \League\Container\Container();
$container->add(UserController::class)->addArgument(PdoWrapper::class);
$container->add(PdoWrapper::class)
    ->addArgument('mysql:host=localhost;dbname=test')
    ->addArgument('user')
    ->addArgument('pass');
Flight::registerContainerHandler($container);

Flight::route('/user', [ 'UserController', 'view' ]);

Flight::start();

이는 이전의 Dice 예제보다 조금 더 많은 내용으로 되어 있을 수 있지만 동일한 이점을 유지합니다!

사용자 정의 DIC 핸들러

사용자 정의 DIC 핸들러를 만들 수도 있습니다. 이는 PSR-11이 아닌 사용자 지정 컨테이너를 사용하고 싶은 경우에 유용합니다. 기본 예제를 확인하면 어떻게 하는지 자세히 알 수 있습니다.

게다가, Flight를 사용할 때 일상적으로 사용하면 삶이 더욱 편리해지는 몇 가지 유용한 기본값이 있습니다.

Engine 인스턴스

컨트롤러/미들웨어에서 Engine 인스턴스를 사용 중이라면, 다음과 같이 구성할 수 있습니다:


// 부트스트랩 파일 어딘가에서
$engine = Flight::app();

$container = new \Dice\Dice;
$container = $container->addRule('*', [
    'substitutions' => [
        // 여기에 인스턴스를 전달합니다
        Engine::class => $engine
    ]
]);

$engine->registerContainerHandler(function($class, $params) use ($container) {
    return $container->create($class, $params);
});

// 이제 컨트롤러/미들웨어에서 Engine 인스턴스를 사용할 수 있습니다

class MyController {
    public function __construct(Engine $app) {
        $this->app = $app;
    }

    public function index() {
        $this->app->render('index');
    }
}

다른 클래스 추가

컨테이너에 추가하려는 다른 클래스가 있는 경우, Dice를 사용하면 자동으로 해결될 것입니다. 다음은 예시입니다:


$container = new \Dice\Dice;
// 클래스에 아무것도 주입할 필요가 없는 경우
// 아무것도 정의할 필요가 없습니다!
Flight::registerContainerHandler(function($class, $params) use ($container) {
    return $container->create($class, $params);
});

class MyCustomClass {
    public function parseThing() {
        return 'thing';
    }
}

class UserController {

    protected MyCustomClass $MyCustomClass;

    public function __construct(MyCustomClass $MyCustomClass) {
        $this->MyCustomClass = $MyCustomClass;
    }

    public function index() {
        echo $this->MyCustomClass->parseThing();
    }
}

Flight::route('/user', 'UserController->index');

Learn/middleware

라우트 미들웨어

Flight은 라우트 및 그룹 라우트 미들웨어를 지원합니다. 미들웨어는 라우트 콜백 앞(또는 뒤)에서 실행되는 함수입니다. 이는 코드에 API 인증 확인을 추가하거나 사용자가 경로에 액세스할 수 있는 권한이 있는지를 확인하는 좋은 방법입니다.

기본 미들웨어

다음은 기본 예제입니다:

// 익명 함수만 제공하는 경우, 라우트 콜백 전에 실행됩니다.
// 클래스를 제외하고는 "after" 미들웨어 함수는 없습니다(아래 참조)
Flight::route('/path', function() { echo '여기 있어요!'; })->addMiddleware(function() {
    echo '첫 번째 미들웨어!';
});

Flight::start();

// 이는 "첫 번째 미들웨어! 여기 있어요!"를 출력합니다.

미들웨어에 대해 사용하기 전에 알아두어야 할 몇 가지 매우 중요한 사항이 있습니다:

미들웨어 클래스

미들웨어는 클래스로 등록할 수도 있습니다. "after" 기능이 필요한 경우 반드시 클래스를 사용해야 합니다.

class MyMiddleware {
    public function before($params) {
        echo '첫 번째 미들웨어!';
    }

    public function after($params) {
        echo '마지막 미들웨어!';
    }
}

$MyMiddleware = new MyMiddleware();
Flight::route('/path', function() { echo '여기 있어요! '; })->addMiddleware($MyMiddleware); // 또는 ->addMiddleware([ $MyMiddleware, $MyMiddleware2 ]);

Flight::start();

// 이것은 "첫 번째 미들웨어! 여기 있어요! 마지막 미들웨어!"를 표시합니다.

미들웨어 그룹화

라우트 그룹을 추가한 다음에는 해당 그룹의 모든 라우트에 동일한 미들웨어가 적용됩니다. 이는 헤더의 API 키를 확인하는 Auth 미들웨어로 라우트를 그룹화해야 하는 경우 유용합니다.


// 그룹 메소드의 끝에 추가됩니다.
Flight::group('/api', function() {

    // "비어 보이는" 모습의 라우트는 실제로 /api와 일치합니다.
    Flight::route('', function() { echo 'api'; }, false, 'api');
    Flight::route('/users', function() { echo 'users'; }, false, 'users');
    Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
}, [ new ApiAuthMiddleware() ]);

모든 라우트에 전역 미들웨어를 적용하려면 "비어" 그룹을 추가할 수 있습니다:


// 그룹 메소드의 끝에 추가됩니다.
Flight::group('', function() {
    Flight::route('/users', function() { echo 'users'; }, false, 'users');
    Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
}, [ new ApiAuthMiddleware() ]);

Learn/filtering

필터링

Flight은 호출되기 전후에 메서드를 필터링할 수 있도록 합니다. 기억해야 할 미리 정의된 후크가 없습니다. 기본 프레임워크 메서드 및 매핑한 사용자 정의 메서드 중에서 원하는 메서드를 필터링할 수 있습니다.

필터 함수는 다음과 같이 보입니다:

function (array &$params, string &$output): bool {
  // 필터 코드
}

전달된 변수를 사용하여 입력 매개변수 및/또는 출력을 조작할 수 있습니다.

메서드가 실행되기 전에 필터를 실행하려면 다음과 같이 합니다:

Flight::before('start', function (array &$params, string &$output): bool {
  // 무언가 수행
});

메서드가 실행된 후에 필터를 실행하려면 다음과 같이 합니다:

Flight::after('start', function (array &$params, string &$output): bool {
  // 무언가 수행
});

원하는 메서드에 여러 필터를 추가할 수 있습니다. 선언된 순서대로 호출됩니다.

다음은 필터링 프로세스의 예시입니다:

// 사용자 정의 메서드 매핑
Flight::map('hello', function (string $name) {
  return "안녕, $name!";
});

// 전 필터 추가
Flight::before('hello', function (array &$params, string &$output): bool {
  // 매개변수 조작
  $params[0] = 'Fred';
  return true;
});

// 후 필터 추가
Flight::after('hello', function (array &$params, string &$output): bool {
  // 출력 조작
  $output .= " 좋은 하루 보내세요!";
  return true;
});

// 사용자 정의 메서드 호출
echo Flight::hello('Bob');

다음이 표시되어야 합니다:

안녕, Fred! 좋은 하루 보내세요!

여러 필터를 정의했는데 체인을 중단하려면 필터 함수 중에 false를 반환하여 체인을 중단할 수 있습니다:

Flight::before('start', function (array &$params, string &$output): bool {
  echo 'one';
  return true;
});

Flight::before('start', function (array &$params, string &$output): bool {
  echo 'two';

  // 이것은 체인을 종료시킵니다
  return false;
});

// 이 부분은 호출되지 않습니다
Flight::before('start', function (array &$params, string &$output): bool {
  echo 'three';
  return true;
});

mapregister와 같은 핵심 메서드는 직접 호출되므로 동적으로 호출되지 않기 때문에 필터링할 수 없습니다.

Learn/requests

요청

Flight은 HTTP 요청을 단일 객체로 캡슐화하며 다음을 통해 액세스할 수 있습니다:

$request = Flight::request();

요청 객체는 다음 속성을 제공합니다:

query, data, cookies, files 속성에는 배열이나 객체로 액세스할 수 있습니다.

따라서 쿼리 문자열 매개변수를 가져오려면 다음을 수행할 수 있습니다:

$id = Flight::request()->query['id'];

또는 다음을 수행할 수 있습니다:

$id = Flight::request()->query->id;

원시 요청 본문

예를 들어 PUT 요청을 처리할 때 원시 HTTP 요청 본문을 가져오려면 다음을 수행할 수 있습니다:

$body = Flight::request()->getBody();

JSON 입력

유형이 application/json이고 데이터가 {"id": 123} 인 요청을 보내면 data 속성에서 사용할 수 있습니다:

$id = Flight::request()->data->id;

$_SERVER 액세스

getVar() 메소드를 통해 $_SERVER 배열에 액세스하는 바로 가기가 있습니다:


$host = Flight::request()->getVar['HTTP_HOST'];

요청 해더 액세스

getHeader() 또는 getHeaders() 메소드를 사용하여 요청 헤더에 액세스할 수 있습니다:


// 인증 헤더가 필요한 경우
$host = Flight::request()->getHeader('Authorization');

// 모든 헤더를 가져와아 하는 경우
$headers = Flight::request()->getHeaders();

Learn/frameworkmethods

# 프레임워크 메소드

Flight은 사용하기 쉽고 이해하기 쉽게 설계되었습니다. 다음은 프레임워크의 완전한
메소드 세트입니다. 이는 일반적인 정적 메소드인 코어 메소드와 필터링하거나 재정의할 수 있는
매핑된 메소드인 확장 가능한 메소드로 구성되어 있습니다.

## 코어 메소드

```php
Flight::map(string $name, callable $callback, bool $pass_route = false) // 사용자 정의 프레임워크 메소드를 생성합니다.
Flight::register(string $name, string $class, array $params = [], ?callable $callback = null) // 클래스를 프레임워크 메소드에 등록합니다.
Flight::before(string $name, callable $callback) // 프레임워크 메소드 앞에 필터를 추가합니다.
Flight::after(string $name, callable $callback) // 프레임워크 메소드 뒤에 필터를 추가합니다.
Flight::path(string $path) // 클래스의 자동로딩을 위한 경로를 추가합니다.
Flight::get(string $key) // 변수를 가져옵니다.
Flight::set(string $key, mixed $value) // 변수를 설정합니다.
Flight::has(string $key) // 변수가 설정되어 있는지 확인합니다.
Flight::clear(array|string $key = []) // 변수를 지웁니다.
Flight::init() // 프레임워크를 기본 설정으로 초기화합니다.
Flight::app() // 어플리케이션 객체 인스턴스를 가져옵니다.

확장 가능한 메소드

Flight::start() // 프레임워크를 시작합니다.
Flight::stop() // 프레임워크를 중지하고 응답을 보냅니다.
Flight::halt(int $code = 200, string $message = '') // 선택적 상태 코드와 메시지로 프레임워크를 중지합니다.
Flight::route(string $pattern, callable $callback, bool $pass_route = false) // URL 패턴을 콜백에 매핑합니다.
Flight::group(string $pattern, callable $callback) // URL을 위한 그룹을 만듭니다. 패턴은 문자열이어야 합니다.
Flight::redirect(string $url, int $code) // 다른 URL로 리다이렉트합니다.
Flight::render(string $file, array $data, ?string $key = null) // 템플릿 파일을 렌더링합니다.
Flight::error(Throwable $error) // HTTP 500 응답을 보냅니다.
Flight::notFound() // HTTP 404 응답을 보냅니다.
Flight::etag(string $id, string $type = 'string') // ETag HTTP 캐싱을 수행합니다.
Flight::lastModified(int $time) // 마지막으로 수정된 HTTP 캐싱을 수행합니다.
Flight::json(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // JSON 응답을 보냅니다.
Flight::jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // JSONP 응답을 보냅니다.

mapregister로 추가된 사용자 정의 메소드는 필터링할 수도 있습니다.

Learn/api

프레임워크 API 메소드

Flight은 사용하기 쉽고 이해하기 쉽도록 설계되었습니다. 아래는 프레임워크를 위한 완전한 메소드 세트입니다. 일반 정적 메소드인 코어 메소드와 필터링이나 오버라이드할 수 있는 매핑된 메소드인 확장 가능한 메소드로 구성되어 있습니다.

코어 메소드

이러한 메소드는 프레임워크의 핵심이며 오버라이드할 수 없습니다.

Flight::map(string $name, callable $callback, bool $pass_route = false) // 사용자 정의 프레임워크 메소드를 생성합니다.
Flight::register(string $name, string $class, array $params = [], ?callable $callback = null) // 클래스를 프레임워크 메소드에 등록합니다.
Flight::unregister(string $name) // 클래스를 프레임워크 메소드에서 등록 해제합니다.
Flight::before(string $name, callable $callback) // 프레임워크 메소드 앞에 필터를 추가합니다.
Flight::after(string $name, callable $callback) // 프레임워크 메소드 뒤에 필터를 추가합니다.
Flight::path(string $path) // 클래스의 자동로드를 위한 경로를 추가합니다.
Flight::get(string $key) // 변수를 가져옵니다.
Flight::set(string $key, mixed $value) // 변수를 설정합니다.
Flight::has(string $key) // 변수가 설정되어 있는지 확인합니다.
Flight::clear(array|string $key = []) // 변수를 지웁니다.
Flight::init() // 프레임워크를 기본 설정으로 초기화합니다.
Flight::app() // 응용프로그램 객체의 인스턴스를 가져옵니다.
Flight::request() // 요청 객체의 인스턴스를 가져옵니다.
Flight::response() // 응답 객체의 인스턴스를 가져옵니다.
Flight::router() // 라우터 객체의 인스턴스를 가져옵니다.
Flight::view() // 뷰 객체의 인스턴스를 가져옵니다.

확장 가능한 메소드

Flight::start() // 프레임워크를 시작합니다.
Flight::stop() // 프레임워크를 중지하고 응답을 보냅니다.
Flight::halt(int $code = 200, string $message = '') // 옵션 상태 코드 및 메시지로 프레임워크를 중지합니다.
Flight::route(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // URL 패턴을 콜백에 매핑합니다.
Flight::post(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // POST 요청 URL 패턴을 콜백에 매핑합니다.
Flight::put(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // PUT 요청 URL 패턴을 콜백에 매핑합니다.
Flight::patch(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // PATCH 요청 URL 패턴을 콜백에 매핑합니다.
Flight::delete(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // DELETE 요청 URL 패턴을 콜백에 매핑합니다.
Flight::group(string $pattern, callable $callback) // URL을 위한 그룹을 생성합니다. 패턴은 문자열이어야 합니다.
Flight::getUrl(string $name, array $params = []) // 라우트 별칭을 기반으로 URL을 생성합니다.
Flight::redirect(string $url, int $code) // 다른 URL로 리다이렉트합니다.
Flight::render(string $file, array $data, ?string $key = null) // 템플릿 파일을 렌더링합니다.
Flight::error(Throwable $error) // HTTP 500 응답을 보냅니다.
Flight::notFound() // HTTP 404 응답을 보냅니다.
Flight::etag(string $id, string $type = 'string') // ETag HTTP 캐싱을 수행합니다.
Flight::lastModified(int $time) // 마지막 수정된 HTTP 캐싱을 수행합니다.
Flight::json(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // JSON 응답을 보냅니다.
Flight::jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // JSONP 응답을 보냅니다.

mapregister로 추가된 사용자 정의 메소드도 필터링할 수 있습니다.

Learn/why_frameworks

왜 프레임워크를 사용해야 하는가?

일부 프로그래머들은 프레임워크를 사용하는 데 강하게 반대합니다. 그들은 프레임워크가 비대해 느리고 배우기 어렵다고 주장합니다. 프레임워크는 필요 없고 그들 없이 더 나은 코드를 작성할 수 있다고 말합니다. 프레임워크를 사용하는 단점에 대해 언급할 수 있는 몇 가지 타당한 점이 있습니다. 그러나 프레임워크를 사용하는 것에는 많은 이점도 있습니다.

프레임워크 사용 이유

프레임워크를 사용해야 할 이유에는 다음과 같은 몇 가지가 있습니다:

Flight은 마이크로 프레임워크입니다. 이는 작고 가벼움을 의미합니다. Laravel이나 Symfony와 같은 더 큰 프레임워크만큼 많은 기능을 제공하지는 않습니다. 그러나 웹 애플리케이션을 구축하는 데 필요한 많은 기능을 제공합니다. 또한 학습하고 사용하기 쉽습니다. 이로 인해 웹 응용 프로그램을 빠르고 쉽게 구축하는 데 좋은 선택이 됩니다. 프레임워크에 대해 처음이라면 Flight는 시작하기에 좋은 초보자 프레임워크입니다. 프레임워크 사용의 장점에 대해 배울 수 있도록 도와줍니다. 복잡성을 너무 많이 겪지 않으면서. Flight를 경험한 후에 Laravel이나 Symfony와 같이 더 복잡한 프레임워크로 이동하는 것이 쉬워질 것이지만 게르약한 응용 프로그램을 만들 수있습니다.

라우팅이 무엇인가요?

라우팅은 Flight 프레임워크의 핵심이지만 정확히 무엇인가요? 라우팅은 URL을 가져와 코드 내의 특정 함수와 일치시키는 프로세스입니다. 이것은 사용자가 요청한 URL에 따라 웹 사이트가 다른 작업을 수행하는 방식입니다. 예를 들어, 사용자 프로파일을 표시하거나 /users를 방문할 때 모든 사용자 목록을 표시하고 싶을 수 있습니다. 이 모든 것은 라우팅을 통해 수행됩니다.

아마도 다음과 같이 작동할 수 있습니다:

그리고 왜 중요한가요?

적절한 중앙 집중화된 라우터를 가지고 있는 것은 실제로 귀하의 삶을 크게 편리하게 만들 수 있습니다! 처음에는 보기 어려울 수 있습니다. 여기 몇 가지 이유가 있습니다:

아마도 웹 사이트를 만드는 스크립트별 방식에 익숙할 것입니다. index.php라는 파일이 있고 URL을 확인하고 URL에 따라 특정 함수를 실행하는 if 문이 많이 있습니다. 이는 라우팅의 한 형태이지만 조직화되지 않았으며 빨리 문제가 발생할 수 있습니다. Flight의 라우팅 시스템은 라우팅을 처리하는 더욱 조직적이고 강력한 방법입니다.

이 것?


// /user/view_profile.php?id=1234
if ($_GET['id']) {
    $id = $_GET['id'];
    viewUserProfile($id);
}

// /user/edit_profile.php?id=1234
if ($_GET['id']) {
    $id = $_GET['id'];
    editUserProfile($id);
}

// 등등...

또는 이것?


// index.php
Flight::route('/user/@id', ['UserController', 'viewUserProfile']);
Flight::route('/user/@id/edit', ['UserController', 'editUserProfile']);

// 아마도 app/controllers/UserController.php에 있습니다.
class UserController {
    public function viewUserProfile($id) {
        // 무언가 수행
    }

    public function editUserProfile($id) {
        // 무언가 수행
    }
}

중앙 집중식 라우팅 시스템을 사용하면 장점을 시작할 수 있습니다. 나중에 이해하는 데 훨씬 쉬워지고 이해하기 쉬워집니다!

요청과 응답

Flight은 요청과 응답을 처리하는 간단하고 쉬운 방법을 제공합니다. 이것이 웹 프레임워크가 하는 핵심입니다. 사용자의 브라우저에서 요청을 수신하고 처리 한 다음 사용자에게 응답을 다시 보냅니다. 이를 통해 사용자의 프로필을 표시하거나 사용자를 로그인하게하거나 새 블로그 게시물을 게시 할 수있는 웹 응용 프로그램을 구축 할 수 있습니다.

요청

요청은 사용자의 브라우저가 웹 사이트를 방문할 때 서버로 보내는 것입니다. 이 요청에는 사용자가 무엇을하고 싶어 하는지에 대한 정보가 포함되어 있습니다. 예를 들어 사용자가 방문 할 URL, 서버로 보내려는 데이터 또는 서버로부터 받기를 원하는 데이터 유형에 대한 정보가 포함 될 수 있습니다. 요청은 읽기 전용이라는 것을 알아 두세요. 요청을 변경할 수 없지만 읽을 수는 있습니다.

Flight는 요청에 대한 정보에 액세스하는 간단한 방법을 제공합니다. Flight::request() 메서드를 사용하여 요청에 대한 정보에 액세스할 수 있습니다. 이 메서드는 요청에 대한 정보를 포함하는 Request 객체를 반환합니다. 이 객체를 사용하여 URL, 방법 또는 사용자가 서버로 보낸 데이터와 같은 요청에 대한 정보에 액세스 할 수 있습니다.

응답

응답은 사용자의 브라우저로 다시 보내는 서버의 응답입니다. 이 응답에는 서버가 수행하려는 작업에 대한 정보가 포함되어 있습니다. 예를 들어, 사용자에게 보내려는 데이터 유형, 사용자로부터 받기를 원하는 데이터 유형 또는 사용자의 컴퓨터에 저장하려는 데이터 유형과 같은 정보가 포함될 수 있습니다.

Flight는 사용자의 브라우저에 응답을 보내는 간단한 방법을 제공합니다. Flight::response() 메서드를 사용하여 응답을 보낼 수 있습니다. 이 메서드는 Response 객체를 인수로 사용하여 응답을 사용자의 브라우저로 보냅니다. 이 객체를 사용하여 HTML, JSON 또는 파일과 같은 응답을 사용자의 브라우저로 보낼 수 있습니다. Flight는 일부 응답의 일부를 자동으로 생성하여 작업을 수월하게합니다. 그러나 최종적으로는 사용자에게 돌려 보낼 내용에 대한 제어 권한을 가지고 있습니다.

Learn/httpcaching

HTTP 캐싱

Flight은 HTTP 레벨 캐싱을 내장 지원합니다. 캐싱 조건이 충족되면, Flight은 HTTP 304 Not Modified 응답을 반환합니다. 클라이언트가 동일한 리소스를 요청할 때, 그들은 로컬로 캐시된 버전을 사용하라는 메시지를 받게 됩니다.

Last-Modified

lastModified 메서드를 사용하여 UNIX 타임스탬프를 전달하여 페이지가 마지막으로 수정된 날짜와 시간을 설정할 수 있습니다. 클라이언트는 마지막 수정 값이 변경될 때까지 캐시를 계속 사용합니다.

Flight::route('/news', function () {
  Flight::lastModified(1234567890);
  echo '이 컨텐츠는 캐시될 것입니다.';
});

ETag

ETag 캐싱은 Last-Modified와 유사하지만 리소스에 대해 원하는 아이디를 지정할 수 있습니다:

Flight::route('/news', function () {
  Flight::etag('my-unique-id');
  echo '이 컨텐츠는 캐시될 것입니다.';
});

lastModified 또는 etag 중 하나를 호출하면 캐시 값을 설정하고 확인합니다. 요청 사이에 캐시 값이 동일하면, Flight은 즉시 HTTP 304 응답을 보내고 처리를 중지합니다.

Learn/responses

응답

Flight는 응답 헤더의 일부를 생성하는 데 도움을 주지만 사용자에게 다시 전송할 내용에 대한 대부분의 제어를 유지합니다. 때로는 Response 객체에 직접 액세스할 수 있지만, 대부분의 경우에는 응답을 보낼 때 Flight 인스턴스를 사용할 것입니다.

기본 응답 보내기

Flight는 출력을 버퍼링하는 데 ob_start()를 사용합니다. 이는 echo 또는 print를 사용하여 사용자에게 응답을 보낼 수 있고 Flight가 해당 내용을 캡처하여 적절한 헤더와 함께 사용자에게 다시 전송합니다.


// 이것은 "Hello, World!"를 사용자 브라우저로 보냅니다
Flight::route('/', function() {
    echo "Hello, World!";
});

// HTTP/1.1 200 OK
// Content-Type: text/html
//
// Hello, World!

대안으로 write() 메소드를 호출하여 본문에 추가할 수도 있습니다.


// 이것은 "Hello, World!"를 사용자 브라우저로 보냅니다
Flight::route('/', function() {
    // 필요할 때 사용 가능하지만 조금 많은 코드
    Flight::response()->write("Hello, World!");

    // 이 시점에서 설정한 본문을 검색하려면
    // 다음과 같이 수행할 수 있습니다
    $body = Flight::response()->getBody();
});

상태 코드

status 메소드를 사용하여 응답의 상태 코드를 설정할 수 있습니다:

Flight::route('/@id', function($id) {
    if($id == 123) {
        Flight::response()->status(200);
        echo "Hello, World!";
    } else {
        Flight::response()->status(403);
        echo "Forbidden";
    }
});

현재 상태 코드를 가져오려면 인수없이 status 메소드를 사용할 수 있습니다:

Flight::response()->status(); // 200

응답 헤더 설정

header 메소드를 사용하여 응답의 콘텐츠 유형과 같은 헤더를 설정할 수 있습니다:


// 이것은 "Hello, World!"를 사용자 브라우저에 평문으로 보냅니다
Flight::route('/', function() {
    Flight::response()->header('Content-Type', 'text/plain');
    echo "Hello, World!";
});

JSON

Flight는 JSON 및 JSONP 응답을 보내는 데 지원을 제공합니다. JSON 응답을 보내려면 JSON으로 인코딩할 데이터를 전달하면 됩니다:

Flight::json(['id' => 123]);

JSONP

JSONP 요청의 경우, 콜백 함수를 정의하는 데 사용하는 쿼리 매개변수 이름을 선택적으로 전달할 수 있습니다:

Flight::jsonp(['id' => 123], 'q');

따라서 ?q=my_func를 사용하여 GET 요청을 보낼 때 출력을 받아야합니다:

my_func({"id":123});

쿼리 매개변수 이름을 전달하지 않으면 기본값은 jsonp로 설정됩니다.

다른 URL로 리디렉션

redirect() 메소드를 사용하여 현재 요청을 새 URL로 리디렉션할 수 있습니다.

Flight::redirect('/new/location');

Flight는 기본적으로 HTTP 303 ("다른 곳 보기") 상태 코드를 보냅니다. 옵션으로 사용자 지정 코드를 설정할 수도 있습니다:

Flight::redirect('/new/location', 401);

중지

halt 메소드를 호출하여 언제든지 프레임워크를 중지할 수 있습니다:

Flight::halt();

선택적으로 HTTP 상태 코드 및 메시지를 지정할 수도 있습니다:

Flight::halt(200, '곧 돌아올게요...');

halt를 호출하면 해당 지점까지의 모든 응답 내용이 폐기됩니다. 프레임워크를 중지하고 현재 응답을 출력하려면 stop 메소드를 사용하십시오:

Flight::stop();

HTTP 캐싱

Flight는 HTTP 수준 캐싱을 위한 내장 지원을 제공합니다. 캐싱 조건이 충족되면, Flight는 HTTP 304 Not Modified 응답을 반환합니다. 다음에 클라이언트가 동일한 리소스를 요청하면 로컬로 캐시된 버전을 사용하도록 요청됩니다.

라우트 수준 캐싱

전체 응답을 캐시하려면 cache() 메소드를 사용하여 캐시할 시간을 전달할 수 있습니다.


// 이것은 응답을 5분 동안 캐시합니다
Flight::route('/news', function () {
  Flight::response()->cache(time() + 300);
  echo '이 콘텐츠는 캐시됩니다.';
});

// 또는 strtotime() 메소드에 전달할 문자열을 사용할 수도 있습니다
Flight::route('/news', function () {
  Flight::response()->cache('+5 minutes');
  echo '이 콘텐츠는 캐시됩니다.';
});

마지막 수정된 날짜

lastModified 메소드를 사용하여 페이지가 마지막으로 수정된 날짜와 시간을 설정할 수 있습니다. 클라이언트는 마지막 수정된 값이 변경될 때까지 그들의 캐시를 계속 사용할 것입니다.

Flight::route('/news', function () {
  Flight::lastModified(1234567890);
  echo '이 콘텐츠는 캐시됩니다.';
});

ETag

ETag 캐싱은 Last-Modified과 유사하지만 리소스에 원하는 임의의 ID를 지정할 수 있습니다:

Flight::route('/news', function () {
  Flight::etag('내-고유-ID');
  echo '이 콘텐츠는 캐시됩니다.';
});

lastModified 또는 etag 중 하나를 호출하면 캐시 값을 설정하고 확인합니다. 요청 사이에 캐시 값이 동일하면 Flight는 즉시 HTTP 304 응답을 보내고 처리를 중지합니다.

Learn/frameworkinstance

프레임워크 인스턴스

글로벌 정적 클래스로 Flight를 실행하는 대신 선택적으로 객체 인스턴스로 실행할 수 있습니다.

require 'flight/autoload.php';

$app = Flight::app();

$app->route('/', function () {
  echo '안녕 세계!';
});

$app->start();

정적 메서드를 호출하는 대신 Engine 객체의 동일한 이름을 가진 인스턴스 메서드를 호출합니다.

Learn/redirects

리다이렉트

새 URL을 전달하여 redirect 메서드를 사용하여 현재 요청을 리다이렉트할 수 있습니다:

Flight::redirect('/new/location');

기본적으로 Flight는 HTTP 303 상태 코드를 보냅니다. 선택적으로 사용자 정의 코드를 설정할 수 있습니다:

Flight::redirect('/new/location', 401);

Learn/views

뷰(Views)

Flight는 기본적인 템플릿 기능을 제공합니다. 뷰 템플릿을 표시하려면 템플릿 파일의 이름과 선택적인 템플릿 데이터를 사용하여 render 메소드를 호출하십시오:

Flight::render('hello.php', ['name' => 'Bob']);

전달한 템플릿 데이터는 자동으로 템플릿에 주입되며 로컬 변수처럼 참조할 수 있습니다. 템플릿 파일은 단순히 PHP 파일입니다. hello.php 템플릿 파일의 내용이 다음과 같다면:

Hello, <?= $name ?>!

출력은 다음과 같을 것입니다:

Hello, Bob!

뷰 변수를 수동으로 설정할 수도 있습니다.

Flight::view()->set('name', 'Bob');

변수 name은 이제 모든 뷰에서 사용할 수 있습니다. 따라서 다음과 같이 할 수 있습니다:

Flight::render('hello');

render 메소드에서 템플릿 이름을 지정할 때 .php 확장자를 생략할 수 있음을 유의하십시오.

Flight는 기본적으로 템플릿 파일을 위해 views 디렉토리를 찾습니다. 다음 구성을 설정하여 템플릿의 대체 경로를 설정할 수 있습니다:

Flight::set('flight.views.path', '/path/to/views');

레이아웃(Layouts)

웹 사이트에 단일 레이아웃 템플릿 파일이 있는 것이 일반적입니다. 레이아웃에 사용할 콘텐츠를 렌더링하려면 render 메소드에 선택적 매개변수를 전달할 수 있습니다.

Flight::render('header', ['heading' => 'Hello'], 'headerContent');
Flight::render('body', ['body' => 'World'], 'bodyContent');

그럼 뷰는 headerContentbodyContent 라는 변수로 저장되어 있을 것입니다. 그럼 다음과 같이 레이아웃을 렌더링할 수 있습니다:

Flight::render('layout', ['title' => 'Home Page']);

다음과 같은 템플릿 파일이 있다면:

header.php:

<h1><?= $heading ?></h1>

body.php:

<div><?= $body ?></div>

layout.php:

<html>
  <head>
    <title><?= $title ?></title>
  </head>
  <body>
    <?= $headerContent ?>
    <?= $bodyContent ?>
  </body>
</html>

출력은 다음과 같을 것입니다:

<html>
  <head>
    <title>Home Page</title>
  </head>
  <body>
    <h1>Hello</h1>
    <div>World</div>
  </body>
</html>

사용자 정의 뷰(Custom Views)

Flight를 통해 기본 뷰 엔진을 간단히 교체할 수 있습니다. 여기서는 Smarty 템플릿 엔진을 뷰로 사용하는 방법을 보여드리겠습니다:

// Smarty 라이브러리 로드
require './Smarty/libs/Smarty.class.php';

// 뷰 클래스로 Smarty 등록
// 또한 로드 시 Smarty를 구성하는 콜백 함수를 전달합니다
Flight::register('view', Smarty::class, [], function (Smarty $smarty) {
  $smarty->setTemplateDir('./templates/');
  $smarty->setCompileDir('./templates_c/');
  $smarty->setConfigDir('./config/');
  $smarty->setCacheDir('./cache/');
});

// 템플릿 데이터 할당
Flight::view()->assign('name', 'Bob');

// 템플릿 표시
Flight::view()->display('hello.tpl');

완전성을 위해 Flight의 기본 render 메소드를 재정의해야 합니다:

Flight::map('render', function(string $template, array $data): void {
  Flight::view()->assign($data);
  Flight::view()->display($template);
});

Learn/templates

Flight는 기본적인 템플릿 기능을 제공합니다.

더 복잡한 템플릿이 필요하다면 사용자 정의 뷰 섹션에서 Smarty와 Latte 예제를 참조하세요.

뷰 템플릿을 표시하려면 템플릿 파일과 선택적 템플릿 데이터를 사용하여 render 메소드를 호출하십시오.

Flight::render('hello.php', ['name' => 'Bob']);

전달하는 템플릿 데이터는 자동으로 템플릿에 주입되며 로컬 변수처럼 참조할 수 있습니다. 템플릿 파일은 간단히 PHP 파일입니다. 만약 hello.php 템플릿 파일의 내용이 다음과 같다면:

Hello, <?= $name ?>!

출력은 다음과 같을 것입니다:

Hello, Bob!

set 메소드를 사용하여 뷰 변수를 수동으로 설정할 수도 있습니다.

Flight::view()->set('name', 'Bob');

이제 name 변수는 모든 뷰에서 사용할 수 있습니다. 따라서 다음과 같이 할 수 있습니다:

Flight::render('hello');

render 메소드에서 템플릿의 이름을 지정할 때 .php 확장자를 생략할 수 있습니다.

Flight는 기본적으로 템플릿 파일을 위해 views 디렉토리를 찾습니다. 다음 설정을 설정하여 템플릿의 대체 경로를 설정할 수 있습니다:

Flight::set('flight.views.path', '/경로/에서/뷰');

레이아웃

웹 사이트가 서로 교체되는 콘텐츠를 포함한 단일 레이아웃 템플릿 파일을 가지고 있는 경우가 많습니다. 레이아웃에 사용할 콘텐츠를 렌더링하려면 render 메소드에 선택적 매개변수를 전달할 수 있습니다.

Flight::render('header', ['heading' => 'Hello'], 'headerContent');
Flight::render('body', ['body' => 'World'], 'bodyContent');

그러면 뷰는 headerContentbodyContent라는 저장된 변수를 가지게 됩니다. 이후 레이아웃을 렌더링하려면 다음을 수행할 수 있습니다:

Flight::render('layout', ['title' => '홈 페이지']);

템플릿 파일이 다음과 같다면:

header.php:

<h1><?= $heading ?></h1>

body.php:

<div><?= $body ?></div>

layout.php:

<html>
    <head>
        <title><?= $title ?></title>
    </head>
    <body>
        <?= $headerContent ?>
        <?= $bodyContent ?>
    </body>
</html>

출력은 다음과 같을 것입니다:

<html>
    <head>
        <title>홈 페이지</title>
    </head>
    <body>
        <h1>Hello</h1>
        <div>World</div>
    </body>
</html>

사용자 정의 뷰

Flight를 통해 기본 뷰 엔진을 교체할 수 있습니다. 사용자 정의 뷰 클래스를 등록함으로써 가능합니다.

스마티(Smarty)

뷰로 Smarty 템플릿 엔진을 사용하는 방법은 다음과 같습니다:

// Smarty 라이브러리 로드
require './Smarty/libs/Smarty.class.php';

// 뷰 클래스로 Smarty 등록
// 또한 Smarty를 로드할 때 구성하기 위한 콜백 함수를 전달합니다.
Flight::register('view', Smarty::class, [], function (Smarty $smarty) {
  $smarty->setTemplateDir('./templates/');
  $smarty->setCompileDir('./templates_c/');
  $smarty->setConfigDir('./config/');
});

// 템플릿 데이터 할당
Flight::view()->assign('name', 'Bob');

// 템플릿 표시
Flight::view()->display('hello.tpl');

완성을 위해 Flight의 기본 render 메소드를 재정의해야 합니다:

Flight::map('render', function(string $template, array $data): void {
  Flight::view()->assign($data);
  Flight::view()->display($template);
});

라테(Latte)

뷰로 Latte 템플릿 엔진을 사용하는 방법은 다음과 같습니다:


// 뷰 클래스로 Latte 등록
// Latte를 로드할 때 구성하기 위한 콜백 함수를 전달합니다.
Flight::register('view', Latte\Engine::class, [], function (Latte\Engine $latte) {
  // 이곳은 Latte가 템플릿을 캐시하는 위치입니다.
    // Latte의 멋진 점 중 하나는 템플릿을 수정할 때 자동으로 캐시를 새로고침한다는 것입니다!
    $latte->setTempDirectory(__DIR__ . '/../cache/');

    // Latte에게 뷰의 루트 디렉토리를 알려줍니다.
    $latte->setLoader(new \Latte\Loaders\FileLoader(__DIR__ . '/../views/'));
});

// Flight::render()를 올바르게 사용할 수 있도록 묶어줍니다.
Flight::map('render', function(string $template, array $data): void {
  // 이는 $latte_engine->render($template, $data); 와 같습니다.
  echo Flight::view()->render($template, $data);
});

Learn/extending

확장

Flight은 확장 가능한 프레임워크로 설계되었습니다. 이 프레임워크에는 기본 메서드와 구성 요소 세트가 함께 제공되지만 사용자 정의 메서드를 매핑하거나 자체 클래스를 등록하거나 기존 클래스 및 메서드를 재정의할 수 있습니다.

만약 DIC (의존성 주입 컨테이너)를 찾고 있다면 의존성 주입 컨테이너 페이지로 이동하십시오.

메서드 매핑

자체 단순한 사용자 정의 메서드를 매핑하려면 map 함수를 사용합니다:

// 여러분의 메서드 매핑
Flight::map('hello', function (string $name) {
  echo "hello $name!";
});

// 사용자 정의 메서드 호출
Flight::hello('Bob');

이는 의도한 값을 얻기 위해 변수를 메서드로 전달해야 할 때 더 많이 사용됩니다. 아래처럼 register() 메서드를 사용하는 것은 구성을 전달하고 그 후 미리 구성된 클래스를 호출할 때 더 많이 사용됩니다.

클래스 등록

자체 클래스를 등록하고 구성하려면 register 함수를 사용합니다:

// 여러분의 클래스 등록
Flight::register('user', User::class);

// 여러분의 클래스 인스턴스 얻기
$user = Flight::user();

등록 방법을 사용하면 클래스 생성자에 매개변수를 전달할 수도 있습니다. 따라서 사용자 정의 클래스를 로드할 때 미리 초기화된 상태로 제공됩니다. 추가 배열을 전달하여 생성자 매개변수를 정의할 수 있습니다. 아래는 데이터베이스 연결을 로드하는 예시입니다:

// 생성자 매개변수로 클래스 등록
Flight::register('db', PDO::class, ['mysql:host=localhost;dbname=test', 'user', 'pass']);

// 여러분의 클래스 인스턴스 얻기
// 이는 정의된 매개변수로 객체를 생성합니다
//
// new PDO('mysql:host=localhost;dbname=test','user','pass');
//
$db = Flight::db();

// 그리고 이후에 필요한 경우 나중에 코드에서 동일한 메서드를 호출하기만 하면 됩니다
class SomeController {
  public function __construct() {
    $this->db = Flight::db();
  }
}

추가 콜백 매개변수를 전달하면 클래스 생성 후 즉시 실행됩니다. 이를 통해 새 객체에 대한 설정 절차를 수행할 수 있습니다. 콜백 함수는 새 객체의 인스턴스를 나타내는 하나의 매개변수를 전달합니다.

// 생성된 객체가 전달됩니다
Flight::register(
  'db',
  PDO::class,
  ['mysql:host=localhost;dbname=test', 'user', 'pass'],
  function (PDO $db) {
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  }
);

기본적으로 클래스를 로드할 때마다 공유 인스턴스를 얻게 됩니다. 클래스의 새 인스턴스를 가져오려면 매개변수로 false를 전달합니다:

// 클래스의 공유 인스턴스
$shared = Flight::db();

// 클래스의 새 인스턴스
$new = Flight::db(false);

매핑된 메서드가 등록된 클래스보다 우선순위가 있습니다. 동일한 이름을 사용하여 두 가지를 선언한 경우 매핑된 메서드만 호출됩니다.

프레임워크 메서드 재정의

Flight을 사용하면 어떠한 코드도 수정하지 않고 자체 요구 사항에 맞게 기본 기능을 재정의할 수 있습니다.

예를 들어, Flight이 URL을 라우트에 매칭시킬 수 없을 때 'notFound' 메서드를 호출하여 일반적인 HTTP 404 응답을 보냅니다. 이 동작을 map 메서드를 사용하여 재정의할 수 있습니다:

Flight::map('notFound', function() {
  // 사용자 정의 404 페이지 표시
  include 'errors/404.html';
});

Flight은 또한 프레임워크의 핵심 구성 요소를 대체할 수 있습니다. 예를 들어, 기본 Router 클래스를 사용자 정의 클래스로 대체할 수 있습니다:

// 여러분의 사용자 정의 클래스 등록
Flight::register('router', MyRouter::class);

// Flight이 Router 인스턴스를 로드할 때 여러분의 클래스가 로드됩니다
$myrouter = Flight::router();

그러나 mapregister와 같은 프레임워크 메서드는 재정의할 수 없습니다. 그렇게 시도할 경우 오류가 발생합니다.

Learn/json

JSON

Flight은 JSON 및 JSONP 응답을 보내는 데 지원을 제공합니다. JSON 응답을 보내려면 JSON으로 인코딩 할 데이터를 전달하면 됩니다:

Flight::json(['id' => 123]);

JSONP 요청의 경우, 콜백 함수를 정의하는 데 사용하는 쿼리 매개변수 이름을 선택적으로 전달할 수 있습니다:

Flight::jsonp(['id' => 123], 'q');

따라서 ?q=my_func를 사용하여 GET 요청을 보낼 때 아래 출력을 받아야 합니다:

my_func({"id":123});

쿼리 매개변수 이름을 전달하지 않으면 jsonp로 기본 설정됩니다.

Learn/autoloading

오토로딩

오토로딩은 PHP에서 클래스를 로드할 디렉토리 또는 디렉토리를 지정하는 개념입니다. 클래스를 로드하는 데 require 또는 include를 사용하는 것보다 훨씬 유익합니다. 또한, Composer 패키지를 사용하기 위한 필수 조건이기도 합니다.

기본적으로 Flight 클래스는 Composer 덕분에 자동으로 오토로드됩니다. 그러나 직접 클래스를 오토로드하려면 Flight::path 메서드를 사용하여 클래스를 로드할 디렉토리를 지정할 수 있습니다.

기본 예시

다음과 같은 디렉토리 트리가 있다고 가정해 봅시다:

# 예시 경로
/home/user/project/my-flight-project/
├── app
│   ├── cache
│   ├── config
│   ├── controllers - 이 프로젝트의 컨트롤러를 포함합니다
│   ├── translations
│   ├── UTILS - 이 애플리케이션을 위한 클래스를 포함합니다 (이 예시에서는 대문자로 명시적으로 표시함)
│   └── views
└── public
    └── css
    └── js
    └── index.php

이것이 이 문서 사이트와 같은 파일 구조임을 알아채셨을 것입니다.

다음과 같이 각 디렉토리를 지정할 수 있습니다:


/**
 * public/index.php
 */

// 오토로더에 경로 추가
Flight::path(__DIR__.'/../app/controllers/');
Flight::path(__DIR__.'/../app/utils/');

/**
 * app/controllers/MyController.php
 */

// 네임스페이스 필요 없음

// 모든 오토로드된 클래스는 파스칼 케이스로 권장됩니다 (각 단어를 대문자로, 공백 없이)
// 클래스 이름에 밑줄을 사용할 수 없다는 요구 사항이 있습니다
class MyController {

    public function index() {
        // 무언가 수행
    }
}

네임스페이스

네임스페이스가 있는 경우 실제로 이를 구현하는 것이 매우 쉬워집니다. 애플리케이션의 루트 디렉토리 (문서 루트 또는 public/ 폴더가 아님)를 지정하기 위해 Flight::path() 메서드를 사용해야 합니다.


/**
 * public/index.php
 */

// 오토로더에 경로 추가
Flight::path(__DIR__.'/../');

이제 컨트롤러가 다음과 같이 보일 것입니다. 아래 예시를 살펴보되 중요한 정보에 주의하십시오.

/**
 * app/controllers/MyController.php
 */

// 네임스페이스 필수
// 네임스페이스는 디렉토리 구조와 동일합니다
// 네임스페이스는 디렉토리 구조와 동일한 케이스를 따라야 합니다
// 네임스페이스와 디렉토리에 언더스코어를 포함할 수 없습니다
namespace app\controllers;

// 모든 오토로드된 클래스는 파스칼 케이스로 권장됩니다 (각 단어를 대문자로, 공백 없이)
// 클래스 이름에 밑줄을 사용할 수 없다는 요구 사항이 있습니다
class MyController {

    public function index() {
        // 무언가 수행
    }
}

그리고 utils 디렉토리에 클래스를 오토로드하려면 아래와 같이 거의 동일한 작업을 수행하면 됩니다:


/**
 * app/UTILS/ArrayHelperUtil.php
 */

// 네임스페이스는 디렉토리 구조와 케이스와 일치해야 합니다 (위의 파일 트리에서 UTILS 디렉토리가 모두 대문자임에 주목)
namespace app\UTILS;

class ArrayHelperUtil {

    public function changeArrayCase(array $array) {
        // 무언가 수행
    }
}

Install

설치

파일 다운로드

만약 Composer를 사용하고 있다면, 다음 명령어를 실행할 수 있습니다:

composer require flightphp/core

또는 파일을 직접 다운로드하여 웹 디렉토리에 압축을 푸십시오.

웹서버 구성

Apache

Apache를 위해, 다음과 같이 .htaccess 파일을 편집하십시오:

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]

참고: flight를 하위 디렉토리에서 사용해야 한다면, RewriteEngine On 다음에 줄을 추가하세요. RewriteBase /subdir/

참고: db 또는 환경 파일과 같은 모든 서버 파일을 보호해야 한다면, 아래 내용을 .htaccess 파일에 넣으십시오:

RewriteEngine On
RewriteRule ^(.*)$ index.php

Nginx

Nginx를 위해, 다음을 서버 선언에 추가하십시오:

server {
  location / {
    try_files $uri $uri/ /index.php;
  }
}

index.php 파일 생성

<?php

// 만약 Composer를 사용 중이라면, 오토로더를 요구하세요.
require 'vendor/autoload.php';
// 만약 Composer를 사용 중이 아니라면, 프레임워크를 직접 로드하세요
// require 'flight/Flight.php';

// 그런 다음 경로를 정의하고 요청을 처리할 함수를 할당하세요.
Flight::route('/', function () {
  echo 'hello world!';
});

// 마지막으로, 프레임워크를 시작하세요.
Flight::start();

License

The MIT License (MIT)

=====================

저작권 © 2023 @mikecao, @n0nag0n

어떤 사람에게나 무료로 소프트웨어 및 관련 문서 파일(이하 "소프트웨어"라 함) 사본을 취득할 수 있는 권한이 여기에 부여됩니다. 이 권한은 소프트웨어를 제한 없이 사용, 복사, 수정, 병합, 게시, 배포, 라이선스 부여 및/또는 판매할 수 있는 권한을 포함하여 어떠한 제한도 없이 인정됩니다. 또한 소프트웨어를 취급할 수 있도록 소프트웨어를 제공받은 자에게 이를 동일하게 행할 수 있도록 허용됩니다. 다만, 다음 조건에 따라야 합니다.

상기 저작권 고지와 본 허가 고지는 소프트웨어의 모든 사본 또는 주요 부분에 포함되어야 합니다.

본 소프트웨어는 "있는 그대로" 제공되며, 어떠한 종류의 보증 없이 명시적이든 묵시적이든 제공됩니다. 이에는 상품성, 특정 목적에 대한 적합성 및 비침해성에 대한 보증이 포함되나 이에 한하지 않습니다. 저자나 저작권 보유자는 소프트웨어 또는 소프트웨어 사용 또는 기타 거래와 관련하여 발생하는 어떠한 청구, 손해 또는 기타 책임에 대해서도 책임을 지지 않습니다.

About

비행이란 무엇인가?

비행은 PHP용 빠르고 간단하며 확장 가능한 프레임워크입니다. 매우 다양하며 어떤 종류의 웹 애플리케이션을 구축하는 데 사용할 수 있습니다. 이는 간결함을 염두에 두고 작성되어 이해하고 사용하기 쉬운 방식으로 작성되었습니다.

비행은 PHP에 익숙하지 않고 웹 애플리케이션을 구축하는 방법을 배우려는 초보자들에게 좋은 프레임워크입니다. 웹 애플리케이션에 대해 더 많은 제어를 원하는 경험 많은 개발자들에게도 좋은 프레임워크입니다. 이것은 RESTful API, 간단한 웹 애플리케이션 또는 복잡한 웹 애플리케이션을 쉽게 구축하기 위해 만들어졌습니다.

빠른 시작

<?php

// 만약 컴포저로 설치한 경우
require 'vendor/autoload.php';
// 수동으로 zip 파일로 설치한 경우
// require 'flight/Flight.php';

Flight::route('/', function() {
  echo '안녕, 세계!';
});

Flight::route('/json', function() {
  Flight::json(['안녕' => '세계']);
});

Flight::start();

단순하죠? Flight에 대해 더 알아보세요!

스켈레톤/보일러플레이트 앱

비행 프레임워크로 시작할 수 있는 예제 앱이 있습니다. 시작하는 방법에 대한 지침은 flightphp/skeleton에서 확인할 수 있습니다! 또한 examples 페이지를 방문하여 Flight로 수행할 수 있는 몇 가지 기능에 대한 영감을 얻을 수 있습니다.

커뮤니티

우리는 Matrix에 있습니다! #flight-php-framework:matrix.org에서 채팅해보세요.

기여

Flight에 기여할 수 있는 두 가지 방법이 있습니다:

  1. 핵심 저장소를 방문하여 핵심 프레임워크에 기여할 수 있습니다.
  2. 문서에 기여할 수 있습니다. 이 문서 웹사이트는 Github에 호스팅되어 있습니다. 오류를 발견하거나 더 나은 내용을 구체화하고 싶다면 자유롭게 수정하여 풀 리퀘스트를 제출해주세요! 우리는 최신 정보를 유지하려 노력하지만 업데이트 및 언어 번역은 환영합니다.

요구 사항

Flight는 PHP 7.4 이상을 필요로 합니다.

참고: PHP 7.4는 현재 작성 시점(2024년)에 일부 LTS Linux 배포판의 기본 버전이기 때문에 지원됩니다. PHP >8로 이동을 강제하면 해당 사용자들에게 많은 불편을 끼칠 것입니다. 이 프레임워크는 또한 PHP >8을 지원합니다.

라이선스

비행은 MIT 라이선스에 따라 공개됩니다.

Awesome-plugins/php_cookie

쿠키

overclokk/cookie은 앱 내에서 쿠키를 관리하는 간단한 라이브러리입니다.

설치

컴포저를 사용하여 설치가 간단합니다.

composer require overclokk/cookie

사용

사용법은 Flight 클래스에 새로운 메소드를 등록하는 것만큼 간단합니다.


use Overclokk\Cookie\Cookie;

/*
 * 부트스트랩 또는 public/index.php 파일에서 설정
 */

Flight::register('cookie', Cookie::class);

/**
 * ExampleController.php
 */

class ExampleController {
    public function login() {
        // 쿠키 설정

        // 새로운 인스턴스를 받으려면 false로 설정해야 합니다
        // 자동완성을 위해 아래 주석을 사용하려면
        /** @var \Overclokk\Cookie\Cookie $cookie */
        $cookie = Flight::cookie(false);
        $cookie->set(
            'stay_logged_in', // 쿠키의 이름
            '1', // 설정할 값
            86400, // 쿠키가 유지될 시간(초)
            '/', // 쿠키가 유효한 경로
            'example.com', // 쿠키가 유효한 도메인
            true, // 안전한 HTTPS 연결로만 쿠키를 전송
            true // HTTP 프로토콜을 통해서만 쿠키에 접근 가능
        );

        // 선택적으로, 기본값을 유지하고 오랫동안 쿠키를 설정하려는 경우
        $cookie->forever('stay_logged_in', '1');
    }

    public function home() {
        // 쿠키가 있는지 확인
        if (Flight::cookie()->has('stay_logged_in')) {
            // 예를 들어 사용자를 대시보드 영역에 넣습니다.
            Flight::redirect('/dashboard');
        }
    }
}

Awesome-plugins/php_encryption

PHP 암호화

defuse/php-encryption은 데이터를 암호화하고 복호화하는 데 사용할 수 있는 라이브러리입니다. 시작하고 실행하는 것은 암호화하고 복호화하는 것이 상당히 간단합니다. 그들은 라이브러리 사용법과 암호화에 관한 중요한 보안 요소에 대해 설명하는 훌륭한 튜토리얼이 있습니다.

설치

composer로 간단히 설치할 수 있습니다.

composer require defuse/php-encryption

설정

그런 다음 암호화 키를 생성해야 합니다.

vendor/bin/generate-defuse-key

이 과정에서 안전하게 보관해야 할 키가 표시됩니다. 이 키를 파일 하단에 있는 배열 안에 app/config/config.php 파일에 저장할 수 있습니다. 완벽한 위치는 아니지만 적어도 어딘가에 있습니다.

사용법

이제 라이브러리와 암호화 키가 준비되었으므로 데이터를 암호화하고 복호화할 수 있습니다.

use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key;

/*
 * 부트스트랩 또는 public/index.php 파일에 설정
 */

// 암호화 메서드
Flight::map('encrypt', function($raw_data) {
    $encryption_key = /* $config['encryption_key'] 또는 키가 위치한 파일에 대한 file_get_contents */;
    return Crypto::encrypt($raw_data, Key::loadFromAsciiSafeString($encryption_key));
});

// 복호화 메서드
Flight::map('decrypt', function($encrypted_data) {
    $encryption_key = /* $config['encryption_key'] 또는 키가 위치한 파일에 대한 file_get_contents */;
    try {
        $raw_data = Crypto::decrypt($encrypted_data, Key::loadFromAsciiSafeString($encryption_key));
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
        // 공격! 잘못된 키가 로드되었거나 암호문이 생성된 후 변경되었습니다. 데이터베이스에서 손상된 상태이거나 악의적으로 수정되어 있는 경우입니다.

        // ... 응용 프로그램에 적합한 방식으로 이 경우를 처리합니다 ...
    }
    return $raw_data;
});

Flight::route('/encrypt', function() {
    $encrypted_data = Flight::encrypt('비밀 정보입니다');
    echo $encrypted_data;
});

Flight::route('/decrypt', function() {
    $encrypted_data = '...'; // 어딘가에서 암호화된 데이터 가져오기
    $decrypted_data = Flight::decrypt($encrypted_data);
    echo $decrypted_data;
});

Awesome-plugins/php_file_cache

Wruczek/PHP-File-Cache

가벼우며 간단하고 독립형 PHP 파일 캐싱 클래스

장점

설치

컴포저를 통해 설치하세요:

composer require wruczek/php-file-cache

사용법

사용법은 꽤 간단합니다.

use Wruczek\PhpFileCache\PhpFileCache;

$app = Flight::app();

// 캐시가 저장될 디렉토리를 생성자에 전달합니다.
$app->register('cache', PhpFileCache::class, [ __DIR__ . '/../cache/' ], function(PhpFileCache $cache) {

    // 이렇게 하면 캐시가 프로덕션 모드일 때만 사용됨을 보장합니다.
    // ENVIRONMENT는 부트스트랩 파일이나 앱의 다른 곳에서 설정된 상수입니다.
    $cache->setDevMode(ENVIRONMENT === 'development');
});

그런 다음 코드에서 다음과 같이 사용할 수 있습니다:


// 캐시 인스턴스 가져오기
$cache = Flight::cache();
$data = $cache->refreshIfExpired('simple-cache-test', function () {
    return date("H:i:s"); // 캐시할 데이터 반환
}, 10); // 10초

// 또는
$data = $cache->retrieve('simple-cache-test');
if(empty($data)) {
    $data = date("H:i:s");
    $cache->store('simple-cache-test', $data, 10); // 10초
}

문서

전체 문서를 보려면 https://github.com/Wruczek/PHP-File-Cache를 방문하고 examples 폴더를 확인하세요.

Awesome-plugins/index

멋진 플러그인들

Flight는 믿을 수 없을 만큼 확장 가능합니다. Flight 애플리케이션에 기능을 추가할 수 있는 여러 플러그인이 있습니다. 일부는 Flight 팀에서 공식적으로 지원하고 있고, 다른 것들은 시작하기를 도와주는 마이크로/라이트 라이브러리입니다.

캐싱

캐싱은 애플리케이션을 빠르게 만드는 좋은 방법입니다. Flight와 함께 사용할 수 있는 여러 캐싱 라이브러리가 있습니다.

디버깅

로컬 환경에서 개발할 때 디버깅은 중요합니다. 몇 가지 플러그인을 이용하여 디버깅 경험을 높일 수 있습니다.

데이터베이스

데이터베이스는 대부분의 애플리케이션의 핵심입니다. 데이터를 저장하고 검색하는 방법입니다. 일부 데이터베이스 라이브러리는 단순히 쿼리를 작성하고 실행하는 래퍼일 뿐이며, 일부는 전체 ORM입니다.

세션

세션은 API에는 그리 유용하지 않지만, 웹 애플리케이션을 구축할 때, 상태 및 로그인 정보를 유지하는 데 필수적일 수 있습니다.

템플릿

템플릿은 UI가 있는 모든 웹 애플리케이션의 핵심입니다. Flight와 함께 사용할 수 있는 여러 템플릿 엔진이 있습니다.

기여

공유하고 싶은 플러그인이 있나요? 이것을 목록에 추가하려면 풀 리퀘스트를 제출하세요!

Awesome-plugins/pdo_wrapper

PdoWrapper PDO 도우미 클래스

Flight은 PDO를 위한 도우미 클래스와 함께 제공됩니다. 이를 사용하면 데이터베이스를 손쉽게 쿼리할 수 있으며 모든 준비/실행/fetchAll() 관련 기능을 활용할 수 있습니다. 데이터베이스를 쿼리하는 방법이 크게 단순화됩니다. 각 행 결과는 Flight Collection 클래스로 반환되며, 이를 통해 배열 구문 또는 객체 구문을 사용하여 데이터에 액세스할 수 있습니다.

PDO 도우미 클래스 등록

// PDO 도우미 클래스 등록
Flight::register('db', \flight\database\PdoWrapper::class, ['mysql:host=localhost;dbname=cool_db_name', 'user', 'pass', [
        PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'utf8mb4\'',
        PDO::ATTR_EMULATE_PREPARES => false,
        PDO::ATTR_STRINGIFY_FETCHES => false,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
    ]
]);

사용법

이 객체는 PDO를 확장하기 때문에 모든 일반 PDO 메서드를 사용할 수 있습니다. 다음 메서드는 데이터베이스 쿼리를 더 쉽게 수행하기 위해 추가되었습니다:

runQuery(string $sql, array $params = []): PDOStatement

INSERT, UPDATE 또는 SELECT를 while 루프에서 사용할 경우에 사용합니다.

$db = Flight::db();
$statement = $db->runQuery("SELECT * FROM table WHERE something = ?", [ $something ]);
while($row = $statement->fetch()) {
    // ...
}

// 또는 데이터베이스에 쓰기
$db->runQuery("INSERT INTO table (name) VALUES (?)", [ $name ]);
$db->runQuery("UPDATE table SET name = ? WHERE id = ?", [ $name, $id ]);

fetchField(string $sql, array $params = []): mixed

쿼리에서 첫 번째 필드를 가져옵니다.

$db = Flight::db();
$count = $db->fetchField("SELECT COUNT(*) FROM table WHERE something = ?", [ $something ]);

fetchRow(string $sql, array $params = []): array

쿼리에서 한 행을 가져옵니다.

$db = Flight::db();
$row = $db->fetchRow("SELECT id, name FROM table WHERE id = ?", [ $id ]);
echo $row['name'];
// 또는
echo $row->name;

fetchAll(string $sql, array $params = []): array

쿼리에서 모든 행을 가져옵니다.

$db = Flight::db();
$rows = $db->fetchAll("SELECT id, name FROM table WHERE something = ?", [ $something ]);
foreach($rows as $row) {
    echo $row['name'];
    // 또는
    echo $row->name;
}

IN() 구문 사용 시 유의사항

IN() 문에 대한 유용한 래퍼도 제공됩니다. IN()에 대한 placeholder로 하나의 물음표를 간단히 전달한 다음 값 배열을 전달할 수 있습니다. 다음은 그 예시입니다:

$db = Flight::db();
$name = 'Bob';
$company_ids = [1,2,3,4,5];
$rows = $db->fetchAll("SELECT id, name FROM table WHERE name = ? AND company_id IN (?)", [ $name, $company_ids ]);

완전한 예시

// 예시 루트 및 이 래퍼의 사용 방법
Flight::route('/users', function () {
    // 모든 사용자 가져오기
    $users = Flight::db()->fetchAll('SELECT * FROM users');

    // 모든 사용자 스트리밍
    $statement = Flight::db()->runQuery('SELECT * FROM users');
    while ($user = $statement->fetch()) {
        echo $user['name'];
        // 또는 echo $user->name;
    }

    // 단일 사용자 가져오기
    $user = Flight::db()->fetchRow('SELECT * FROM users WHERE id = ?', [123]);

    // 단일 값 가져오기
    $count = Flight::db()->fetchField('SELECT COUNT(*) FROM users');

    // 도움이 되는 IN() 구문 (IN이 대문자임을 확인하세요)
    $users = Flight::db()->fetchAll('SELECT * FROM users WHERE id IN (?)', [[1,2,3,4,5]]);
    // 다음과 같이도 할 수 있습니다
    $users = Flight::db()->fetchAll('SELECT * FROM users WHERE id IN (?)', [ '1,2,3,4,5']);

    // 새 사용자 삽입
    Flight::db()->runQuery("INSERT INTO users (name, email) VALUES (?, ?)", ['Bob', 'bob@example.com']);
    $insert_id = Flight::db()->lastInsertId();

    // 사용자 업데이트
    Flight::db()->runQuery("UPDATE users SET name = ? WHERE id = ?", ['Bob', 123]);

    // 사용자 삭제
    Flight::db()->runQuery("DELETE FROM users WHERE id = ?", [123]);

    // 영향 받은 행 수 가져오기
    $statement = Flight::db()->runQuery("UPDATE users SET name = ? WHERE name = ?", ['Bob', 'Sally']);
    $affected_rows = $statement->rowCount();

});

Awesome-plugins/session

고스트/세션

PHP 세션 관리자 (비차단, 플래시, 세그먼트, 세션 암호화). PHP open_ssl을 사용하여 세션 데이터의 선택적 암호화/해독을 지원합니다. 파일, MySQL, Redis, 및 Memcached을 지원합니다.

설치

Composer로 설치합니다.

composer require ghostff/session

기본 구성

세션을 사용하여 기본 설정을 변경할 필요는 없습니다. 세션에 대한 자세한 설정은 Github Readme에서 확인할 수 있습니다.


use Ghostff\Session\Session;

require 'vendor/autoload.php';

$app = Flight::app();

$app->register('session', Session::class);

// 각 페이지 로드마다 세션을 커밋해야 합니다.
// 또는 구성에서 auto_commit을 실행해야 합니다.

간단한 예제

이렇게 사용할 수 있는 간단한 예제입니다.

Flight::route('POST /login', function() {
    $session = Flight::session();

    // 로그인 로직 실행
    // 비밀번호 확인 등

    // 로그인이 성공하면
    $session->set('is_logged_in', true);
    $session->set('user', $user);

    // 세션에 쓸 때마다 명시적으로 커밋해야 합니다.
    $session->commit();
});

// 이 체크는 제한된 페이지 로직에 있거나 미들웨어로 래핑될 수 있습니다.
Flight::route('/some-restricted-page', function() {
    $session = Flight::session();

    if(!$session->get('is_logged_in')) {
        Flight::redirect('/login');
    }

    // 제한된 페이지 로직 실행
});

// 미들웨어 버전
Flight::route('/some-restricted-page', function() {
    // 일반 페이지 로직
})->addMiddleware(function() {
    $session = Flight::session();

    if(!$session->get('is_logged_in')) {
        Flight::redirect('/login');
    }
});

더 복잡한 예제

이렇게 사용할 수 있는 더 복잡한 예제입니다.


use Ghostff\Session\Session;

require 'vendor/autoload.php';

$app = Flight::app();

// 세션 구성 파일에 사용자 지정 경로 설정 및 임의의 문자열로 세션 id 설정
$app->register('session', Session::class, [ 'path/to/session_config.php', bin2hex(random_bytes(32)) ], function(Session $session) {
        // 또는 구성 옵션을 수동으로 재정의할 수 있습니다.
        $session->updateConfiguration([
            // 세션 데이터를 데이터베이스에 저장하려면 (예: "모든 디바이스에서 로그아웃" 기능과 같은 것이 필요한 경우 유용)
            Session::CONFIG_DRIVER        => Ghostff\Session\Drivers\MySql::class,
            Session::CONFIG_ENCRYPT_DATA  => true,
            Session::CONFIG_SALT_KEY      => hash('sha256', 'my-super-S3CR3T-salt'), // 이 값을 변경해야 합니다
            Session::CONFIG_AUTO_COMMIT   => true, // 필요하거나 세션을 커밋하는 것이 어려운 경우에만 수행하세요.
                                                // 또한 Flight::after('start', function() { Flight::session()->commit(); }); 사용 가능
            Session::CONFIG_MYSQL_DS         => [
                'driver'    => 'mysql',             # PDO dns에 대한 데이터베이스 드라이버 예(mysql:host=...;dbname=...)
                'host'      => '127.0.0.1',         # 데이터베이스 호스트
                'db_name'   => 'my_app_database',   # 데이터베이스 이름
                'db_table'  => 'sessions',          # 데이터베이스 테이블
                'db_user'   => 'root',              # 데이터베이스 사용자명
                'db_pass'   => '',                  # 데이터베이스 비밀번호
                'persistent_conn'=> false,          # 매번 새로운 연결을 설정하는 오버헤드를 피하면서 빠른 웹 응용 프로그램을 얻으려면 false로 설정
            ]
        ]);
    }
);

문서

자세한 문서는 Github Readme를 방문하세요. 구성 옵션은 default_config.php 파일에서 잘 문서화되어 있습니다. 패키지를 직접 살펴보고 싶다면 이 코드는 이해하기 쉽습니다.

Awesome-plugins/tracy_extensions

Tracy Flight Panel 확장 기능

이것은 Flight를 사용하는 것을 좀 더 풍부하게 만들기 위한 확장 기능 세트입니다.

이것이 패널입니다

Flight 바

그리고 각 패널은 애플리케이션에 대한 매우 유용한 정보를 표시합니다!

Flight 데이터 Flight 데이터베이스 Flight 요청

설치

composer require flightphp/tracy-extensions --dev를 실행하면 됩니다!

구성

시작하려면 아주 적은 구성이 필요합니다. 이를 시작하기 전에 Tracy 디버거를 초기화해야 합니다 https://tracy.nette.org/en/guide:

<?php

use Tracy\Debugger;
use flight\debug\tracy\TracyExtensionLoader;

// 부트스트랩 코드
require __DIR__ . '/vendor/autoload.php';

Debugger::enable();
// Debugger::enable(Debugger::DEVELOPMENT)으로 환경을 지정해야 할 수도 있습니다

// 앱에서 데이터베이스 연결을 사용하는 경우
// 개발 중에만 사용해야 하는 필수 PDO 래퍼가 있습니다 (상용 제품에서는 사용하지 마십시오!)
// 일반 PDO 연결과 동일한 매개변수를 가지고 있습니다
$pdo = new PdoQueryCapture('sqlite:test.db', 'user', 'pass');
// 또는 Flight 프레임워크에 이를 첨부하는 경우
Flight::register('db', PdoQueryCapture::class, ['sqlite:test.db', 'user', 'pass']);
// 이제 쿼리를 실행할 때마다 시간, 쿼리 및 매개변수가 캡처됩니다

// 이것이 점을 이어주는 것입니다
if(Debugger::$showBar === true) {
    Flight::set('flight.content_length', false);
    new TracyExtensionLoader(Flight::app());
}

// 더 많은 코드

Flight::start();

추가 구성

세션 데이터

사용자 지정 세션 핸들러(예: ghostff/session)가 있는 경우, Tracy에 어떤 배열의 세션 데이터를 전달하면 자동으로 출력됩니다. TracyExtensionLoader 생성자의 두 번째 매개변수에서 session_data 키로 전달합니다.


use Ghostff\Session\Session;

require 'vendor/autoload.php';

$app = Flight::app();

$app->register('session', Session::class);

if(Debugger::$showBar === true) {
    Flight::set('flight.content_length', false);
    new TracyExtensionLoader(Flight::app(), [ 'session_data' => Flight::session()->getAll() ]);
}

// 라우트 및 기타 작업...

Flight::start();

라테

프로젝트에 라테가 설치되어 있는 경우, 라테 패널을 사용하여 템플릿을 분석할 수 있습니다. TracyExtensionLoader 생성자의 두 번째 매개변수에서 latte 키로 LaTte 인스턴스를 전달할 수 있습니다.



use Latte\Engine;

require 'vendor/autoload.php';

$app = Flight::app();

$app->register('latte', Engine::class, [], function($latte) {
    $latte->setTempDirectory(__DIR__ . '/temp');

    $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
});

if(Debugger::$showBar === true) {
    Flight::set('flight.content_length', false);
    new TracyExtensionLoader(Flight::app());
}

Awesome-plugins/tracy

트레이시 (Tracy)

트레이시는 Flight와 함께 사용할 수 있는 놀라운 에러 핸들러입니다. 애플리케이션을 디버깅하는 데 도움이 되는 여러 패널을 갖고 있습니다. 또한 손쉽게 확장하여 사용자 정의 패널을 추가할 수 있습니다. Flight 팀은 flightphp/tracy-extensions 플러그인을 통해 Flight 프로젝트를 위해 몇 가지 패널을 특별히 만들었습니다.

설치

컴포저로 설치합니다. 실제로 이것은 Tracy가 프로덕션 에러 핸들링 구성 요소와 함께 제공되므로 개발 버전 없이 설치하는 것이 좋습니다.

composer require tracy/tracy

기본 구성

시작하려면 몇 가지 기본 구성 옵션이 있습니다. 더 자세한 내용은 Tracy 문서를 참조하십시오.

require 'vendor/autoload.php';

use Tracy\Debugger;

// Tracy를 활성화합니다
Debugger::enable();
// Debugger::enable(Debugger::DEVELOPMENT) // 경우에 따라 명시적으로 설정해야 할 수도 있습니다 (또한 Debugger::PRODUCTION)
// Debugger::enable('23.75.345.200'); // IP 주소의 배열을 제공할 수도 있습니다

// 여기에 오류와 예외가 기록됩니다. 이 디렉터리가 존재하고 쓰기 가능한지 확인하십시오.
Debugger::$logDirectory = __DIR__ . '/../log/';
Debugger::$strictMode = true; // 모든 오류 표시
// Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // 사용되지 않은 공지를 제외한 모든 오류
if (Debugger::$showBar) {
    $app->set('flight.content_length', false); // Debugger 표시줄이 표시되는 경우 Flight에서 내용 길이를 설정할 수 없습니다

    // 이것은 Tracy Extension for Flight에 특화된 사항이며, 해당 항목을 포함했을 경우만 활성화합니다
    // 그렇지 않으면 주석 처리하십시오.
    new TracyExtensionLoader($app);
}

유용한 팁

코드를 디버깅하는 경우 데이터를 출력하기 위한 매우 유용한 함수들이 있습니다.

Awesome-plugins/active_record

Flight Active Record

액티브 레코드는 데이터베이스 엔티티를 PHP 객체에 매핑하는 것을 의미합니다. 간단히 말해서 데이터베이스에 사용자 테이블이 있다면 이 테이블의 행을 User 클래스와 코드베이스의 $user 객체로 "변환"할 수 있습니다. 기본 예제를 참조하세요.

기본 예제

다음 테이블을 가정해 봅시다:

CREATE TABLE users (
    id INTEGER PRIMARY KEY, 
    name TEXT, 
    password TEXT 
);

이제 이 테이블을 표현하는 새 클래스를 설정할 수 있습니다:

/**
 * 액티브레코드 클래스는 일반적으로 단수형으로 지정합니다.
 * 
 * 여기에 테이블의 속성을 주석으로 추가하는 것이 권장됩니다.
 * 
 * @property int    $id
 * @property string $name
 * @property string $password
 */ 
class User extends flight\ActiveRecord {
    public function __construct($database_connection)
    {
        // 이렇게 설정할 수 있습니다
        parent::__construct($database_connection, 'users');
        // 또는 이렇게 설정할 수도 있습니다
        parent::__construct($database_connection, null, [ 'table' => 'users']);
    }
}

이제 마법이 벌어집니다!

// sqlite를 사용하는 경우
$database_connection = new PDO('sqlite:test.db'); // 이것은 예시일뿐이라 실제 데이터베이스 연결을 사용해야 합니다

// mysql을 사용하는 경우
$database_connection = new PDO('mysql:host=localhost;dbname=test_db&charset=utf8bm4', 'username', 'password');

// 또는 mysqli를 사용하는 경우
$database_connection = new mysqli('localhost', 'username', 'password', 'test_db');
// 또는 객체를 사용하지 않는 mysqli
$database_connection = mysqli_connect('localhost', 'username', 'password', 'test_db');

$user = new User($database_connection);
$user->name = 'Bobby Tables';
$user->password = password_hash('some cool password');
$user->insert();
// 또는 $user->save();

echo $user->id; // 1

$user->name = 'Joseph Mamma';
$user->password = password_hash('some cool password again!!!');
$user->insert();
// 여기서 $user->save()를 사용할 수 없습니다. 그렇지 않으면 업데이트로 인식됩니다!

echo $user->id; // 2

새 사용자를 추가하는 것이 매우 쉬웠습니다! 이제 데이터베이스에 사용자 행이 있으므로 어떻게 가져올까요?

$user->find(1); // 데이터베이스에서 id = 1을 찾아 반환합니다.
echo $user->name; // 'Bobby Tables'

모든 사용자를 찾고 싶으면 어떻게 하나요?

$users = $user->findAll();

특정 조건으로 찾고 싶다면?

$users = $user->like('name', '%mamma%')->findAll();

재미있는 것을 보셨나요? 이를 설치하고 시작해 봅시다!

설치

Composer로 간단히 설치하세요

composer require flightphp/active-record 

사용법

이 라이브러리를 독립적으로 사용하거나 Flight PHP 프레임워크와 함께 사용할 수 있습니다. 완전히 선택은 당신에게 달렸습니다.

독립적으로

생성자에 PDO 연결을 전달해야 합니다.

$pdo_connection = new PDO('sqlite:test.db'); // 이것은 예시일뿐이라 실제 데이터베이스 연결을 사용해야 합니다

$User = new User($pdo_connection);

Flight PHP 프레임워크

Flight PHP 프레임워크를 사용하는 경우 ActiveRecord 클래스를 서비스로 등록할 수 있습니다(하지만 되도록 사용하지 않는 게 좋습니다).

Flight::register('user', 'User', [ $pdo_connection ]);

// 그런 다음 컨트롤러, 함수 등에서 다음과 같이 사용할 수 있습니다.

Flight::user()->find(1);

CRUD 함수

find($id = null) : boolean|ActiveRecord

하나의 레코드를 찾아서 현재 객체에 할당합니다. 어떤 $id를 전달하면 해당 값의 기본 키에서 조회를 실행합니다. 아무것도 전달하지 않은 경우에는 테이블에서 첫 번째 레코드를 찾습니다.

추가로 쿼리 테이블을 조회하는 데 도움이 되는 다른 도우미 메서드를 전달할 수 있습니다.

// 사전에 일부 조건이 지정된 레코드 찾기
$user->notNull('password')->orderBy('id DESC')->find();

// 특정 id로 레코드 찾기
$id = 123;
$user->find($id);

findAll(): array<int,ActiveRecord>

지정한 테이블에서 모든 레코드를 찾습니다.

$user->findAll();

isHydrated(): boolean (v0.4.0)

현재 레코드가 수집되었는지 여부를 반환합니다(데이터베이스에서 가져온 것).

$user->find(1);
// 데이터와 함께 레코드가 검색된 경우...
$user->isHydrated(); // true

insert(): boolean|ActiveRecord

현재 레코드를 데이터베이스에 삽입합니다.

$user = new User($pdo_connection);
$user->name = 'demo';
$user->password = md5('demo');
$user->insert();

update(): boolean|ActiveRecord

현재 레코드를 데이터베이스에서 업데이트합니다.

$user->greaterThan('id', 0)->orderBy('id desc')->find();
$user->email = 'test@example.com';
$user->update();

save(): boolean|ActiveRecord

현재 레코드를 데이터베이스에 삽입하거나 업데이트합니다. 레코드에 id가 있으면 업데이트되고, 그렇지 않으면 삽입됩니다.

$user = new User($pdo_connection);
$user->name = 'demo';
$user->password = md5('demo');
$user->save();

참고: 클래스에서 관계가 정의된 경우, 정의되고 초기화된 경우 해당 관계도 재귀적으로 업데이트됩니다. (v0.4.0 이상)

delete(): boolean

현재 레코드를 데이터베이스에서 삭제합니다.

$user->gt('id', 0)->orderBy('id desc')->find();
$user->delete();

검색 이전에 몇몇 레코드를 삭제할 수도 있습니다.

$user->like('name', 'Bob%')->delete();

dirty(array $dirty = []): ActiveRecord

변경된 데이터를 참조합니다.

$user->greaterThan('id', 0)->orderBy('id desc')->find();

// 이 시점에서 아무것도 "생기지 않았습니다".

$user->email = 'test@example.com'; // 지금 이메일은 변경됐으므로 "dirty"로 간주됩니다.
$user->update();
// 이제 더티 데이터가 없어졌으므로 변경된 내용은 데이터베이스에 반영되었습니다.

$user->password = password_hash()'newpassword'); // 이제 이것은 더티 상태입니다.
$user->dirty(); // 아무것도 전달하지 않으면 모든 더티 항목이 지워집니다.
$user->update(); // 더티가 캡처되지 않았으므로 아무 것도 업데이트되지 않습니다.

$user->dirty([ 'name' => 'something', 'password' => password_hash('a different password') ]);
$user->update(); // 이름과 암호가 모두 업데이트됩니다.

copyFrom(array $data): ActiveRecord (v0.4.0)

dirty() 메서드의 별칭입니다. 무엇을 수행할지 명확히 할 수 있습니다.

$user->copyFrom([ 'name' => 'something', 'password' => password_hash('a different password') ]);
$user->update(); // 이름과 암호가 모두 업데이트됩니다.

isDirty(): boolean (v0.4.0)

현재 레코드가 변경되었는지 여부를 반환합니다.

$user->greaterThan('id', 0)->orderBy('id desc')->find();
$user->email = 'test@email.com';
$user->isDirty(); // true

reset(bool $include_query_data = true): ActiveRecord

현재 레코드를 초기 상태로 다시 설정합니다. 이는 루프 형태 동작에서 사용하기에 매우 적합합니다. true를 전달하면 현재 객체를 찾는 데 사용된 쿼리 데이터도 재설정됩니다(기본 동작).

$users = $user->greaterThan('id', 0)->orderBy('id desc')->find();
$user_company = new UserCompany($pdo_connection);

foreach($users as $user) {
    $user_company->reset(); // 깨끗하게 시작
    $user_company->user_id = $user->id;
    $user_company->company_id = $some_company_id;
    $user_company->insert();
}

getBuiltSql(): string (v0.4.1)

find(), findAll(), insert(), update(), 또는 save() 메서드를 실행한 후에 생성된 SQL을 가져와 디버깅 목적으로 사용할 수 있습니다.

SQL 쿼리 메서드

select(string $field1 [, string $field2 ... ])

원하는 경우 테이블의 일부 열만 선택할 수 있습니다(다수의 열이 있는 경우 성능이 좋습니다).

$user->select('id', 'name')->find();

from(string $table)

원하는 경우 다른 테이블을 선택할 수도 있습니다!

$user->select('id', 'name')->from('user')->find();

join(string $table_name, string $join_condition)

데이터베이스에서 다른 테이블에 조인할 수 있습니다.

$user->join('contacts', 'contacts.user_id = users.id')->find();

where(string $where_conditions)

사용자 정의 where 인수를 설정할 수 있습니다(이 where 문에서는 params를 설정할 수 없습니다).

$user->where('id=1 AND name="demo"')->find();

보안 주의 - $user->where("id = '{$id}' AND name = '{$name}'")->find();와 같은 코드를 작성하고자 할 수 있습니다. 이렇게 하지 마십시오!!! 이는 SQL Injection 공격에 노출될 수 있습니다. 온라인에서 많은 기사가 있습니다. "sql injection attacks php"로 검색하여 해당 주제에 대해 알아보시기 바랍니다. 이 라이브러리에서 이를 처리하는 올바른 방법은 where() 메서드 대신 $user->eq('id', $id)->eq('name', $name)->find();와 같은 방식을 사용하는 것입니다. 꼭 필요한 경우 PDO 라이브러리에는 $pdo->quote($var)를 사용하여 이스케이프할 수 있습니다. quote()를 사용한 후에만 where() 문에서 사용할 수 있습니다.

group(string $group_by_statement)/groupBy(string $group_by_statement)

특정 조건에 따라 결과를 그룹화합니다.

$user->select('COUNT(*) as count')->groupBy('name')->findAll();

order(string $order_by_statement)/orderBy(string $order_by_statement)

결과 쿼리를 정해진 방식으로 정렬합니다.

$user->orderBy('name DESC')->find();

limit(string $limit)/limit(int $offset, int $limit)

반환된 레코드 수를 제한합니다. 두 번째 int를 지정할 경우 오프셋 및 제한이 SQL과 같이 적용됩니다.

$user->orderby('name DESC')->limit(0, 10)->findAll();

WHERE 조건

equal(string $field, mixed $value) / eq(string $field, mixed $value)

field = $value인 경우

$user->eq('id', 1)->find();

notEqual(string $field, mixed $value) / ne(string $field, mixed $value)

field <> $value인 경우

$user->ne('id', 1)->find();

isNull(string $field)

field IS NULL인 경우

$user->isNull('id')->find();

isNotNull(string $field) / notNull(string $field)

field IS NOT NULL인 경우

$user->isNotNull('id')->find();

greaterThan(string $field, mixed $value) / gt(string $field, mixed $value)

field > $value인 경우

$user->gt('id', 1)->find();

lessThan(string $field, mixed $value) / lt(string $field, mixed $value)

field < $value인 경우

$user->lt('id', 1)->find();

greaterThanOrEqual(string $field, mixed $value) / ge(string $field, mixed $value) / gte(string $field, mixed $value)

field >= $value인 경우

$user->ge('id', 1)->find();

lessThanOrEqual(string $field, mixed $value) / le(string $field, mixed $value) / lte(string $field, mixed $value)

field <= $value인 경우

$user->le('id', 1)->find();

like(string $field, mixed $value) / notLike(string $field, mixed $value)

field LIKE $value 또는 field NOT LIKE $value인 경우

$user->like('name', 'de')->find();

in(string $field, array $values) / notIn(string $field, array $values)

field IN($value) 또는 field NOT IN($value)인 경우

$user->in('id', [1, 2])->find();

between(string $field, array $values)

field BETWEEN $value AND $value1인 경우

$user->between('id', [1, 2])->find();

관계

이 라이브러리를 사용하여 여러 종류의 관계를 설정할 수 있습니다. 테이블 간에 일대다 및 일대일 관계를 설정할 수 있습니다. 이전에 클래스에 $relations 배열을 설정해야 하지만 올바른 구문을 추측하는 데 어려움이 있습니다.

protected array $relations = [
    // 키 이름을 원하는 대로 설정할 수 있습니다. ActiveRecord의 이름이 좋을 것입니다. 예: user, contact, client
    'user' => [
        // 필수
        // self::HAS_MANY, self::HAS_ONE, self::BELONGS_TO
        self::HAS_ONE, // 관계 유형

        // 필수
        'Some_Class', // 참조할 "다른" ActiveRecord 클래스

        // 필수
        // 관계 유형에 따라
        // self::HAS_ONE = 조인을 참조하는 외래 키
        // self::HAS_MANY = 조인을 참조하는 외래 키
        // self::BELONGS_TO = 조인을 참조하는 로컬 키
        'local_or_foreign_key',
        // 이는 "다른" 모델의 주요 키

와 함께 조인을 구성하므로 사용할 수 있습니다!

```php
class User extends ActiveRecord{
    protected array $relations = [
        'contacts' => [ self::HAS_MANY, Contact::class, 'user_id' ],
        'contact' => [ self::HAS_ONE, Contact::class, 'user_id' ],
    ];

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }
}

class Contact extends ActiveRecord{
    protected array $relations = [
        'user' => [ self::BELONGS_TO, User::class, 'user_id' ],
        'user_with_backref' => [ self::BELONGS_TO, User::class, 'user_id', [], 'contact' ],
    ];
    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'contacts');
    }
}

이제 참조를 설정했으므로 매우 쉽게 사용할 수 있습니다!

$user = new User($pdo_connection);

// 가장 최근 사용자 찾기.
$user->notNull('id')->orderBy('id desc')->find();

// 관계를 사용하여 연락처 가져오기:
foreach($user->contacts as $contact) {
    echo $contact->id;
}

// 또는 다른 방법으로도 할 수 있습니다.
$contact = new Contact();

// 하나의 연락처 찾기
$contact->find();

// 관계를 사용하여 사용자 가져오기:
echo $contact->user->name; // 이것은 사용자 이름입니다

정말 멋지죠?

사용자 정의 데이터 설정

가끔씩 ActiveRecord에 사용자 정의 계산과 같은 고유한 것을 첨부해야 할 수도 있습니다. 이를 템플릿에 전달하기에 더 쉬울 수 있습니다.

setCustomData(string $field, mixed $value)

setCustomData() 메서드를 사용하여 사용자 정의 데이터를 첨부할 수 있습니다.

$user->setCustomData('page_view_count', $page_view_count);

그런 다음 일반 객체 속성처럼 참조하면 됩니다.

echo $user->page_view_count;

이벤트

이 라이브러리에 대해 한 가지 더 멋진 기능은 이벤트입니다. 일부 메서드를 호출할 때 특정 시점에서 이벤트가 트리거되며 사용할 수 있습니다. 이런 방식으로 데이터를 자동으로 설정하는 데 매우 효과적입니다.

onConstruct(ActiveRecord $ActiveRecord, array &config)

기본 연결을 설정해야 하는 경우에 매우 유용합니다.

// index.php 또는 bootstrap.php
Flight::register('db', 'PDO', [ 'sqlite:test.db' ]);

//
//
//

// User.php
class User extends flight\ActiveRecord {

    protected function onConstruct(self $self, array &$config) { // & 참조를 잊지 마세요
        // 연결을 자동으로 설정할 수 있습니다.
        $config['connection'] = Flight::db();
        // 또는 이렇게
        $self->transformAndPersistConnection(Flight::db());

        // 이렇게 테이블 이름도 설정할 수 있습니다.
        $config['table'] = 'users';
    } 
}

beforeFind(ActiveRecord $ActiveRecord)

각 실행 시마다 쿼리 조작이 필요한 경우에 유용합니다.

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeFind(self $self) {
        // 항상 id >= 0을 실행하도록합니다
        $self->gte('id', 0); 
    } 
}

afterFind(ActiveRecord $ActiveRecord)

검색된 레코드마다 항상 일부 로직을 실행해야하는 경우에 유용합니다. 복사할 암호화가 필요한 경우 등(성능은 떨어지지만 무슨일이야).

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function afterFind(self $self) {
        // 암호 복호화 중
        $self->secret = yourDecryptFunction($self->secret, $some_key);

        // 각 회수마다 사용자 정의 카운트 쿼리 실행이 필요한 경우
        $self->setCustomData('view_count', $self->select('COUNT(*) count')->from('user_views')->eq('user_id', $self->id)['count']; 
    } 
}

나머지 이벤트에 대해는 한 가지만 이해하면 됩니다! 현재 레코드가 변경되면 일부 논리를 항상 실행해야 하는 지점이 됩니다.

beforeFindAll(ActiveRecord $ActiveRecord)

각 실행 시마다 쿼리 조작이 필요한 경우에 유용합니다.

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeFindAll(self $self) {
        // 항상 id >= 0을 실행하도록합니다
        $self->gte('id', 0); 
    } 
}

afterFindAll(array<int,ActiveRecord> $results)

afterFind()와 유사하지만 모든 레코드에 대해 작동합니다!

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function afterFindAll(array $results) {

        foreach($results as $self) {
            // afterFind()처럼 멋진 일 몇 가지를 수행하기
        }
    } 
}

beforeInsert(ActiveRecord $ActiveRecord)

항상 일부 기본값이 설정되어야 하는 경우 매우 유용합니다.

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeInsert(self $self) {
        // 일부 안정적인 기본값 설정
        if(!$self->created_date) {
            $self->created_date = gmdate('Y-m-d');
        }

        if(!$self->password) {
            $self->password = password_hash((string) microtime(true));
        }
    } 
}

afterInsert(ActiveRecord $ActiveRecord)

삽입 후 데이터를 변경해야 하는 사용 사례가 있는 경우?

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function afterInsert(self $self) {
        // 내용을 저장하십시오.
        Flight::cache()->set('most_recent_insert_id', $self->id);
        // 또는 무엇이든..
    } 
}

beforeUpdate(ActiveRecord $ActiveRecord)

更新 시마다 기본값을 설정해야 하는 경우 매우 유용합니다.

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeInsert(self $self) {
        // 미래의 경우를 준비하십시오
        if(!$self->updated_date) {
            $self->updated_date = gmdate('Y-m-d');
        }
    } 
}

afterUpdate(ActiveRecord $ActiveRecord)

업데이트 후 데이터를 변경해야 하는 사용 사례가 있는 경우?

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function afterInsert(self $self) {
        // 내용을 저장하십시오
        Flight::cache()->set('most_recently_updated_user_id', $self->id);
        // 또는 무엇이든..
    } 
}

beforeSave(ActiveRecord $ActiveRecord)/afterSave(ActiveRecord $ActiveRecord)

insert() 또는 update()가 발생할 때 마다 이벤트를 사용하려는 경우 유용합니다. 긴 설명을 삼가합니다.

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeSave(self $self) {
        $self->last_updated = gmdate('Y-m-d H:i:s');
    } 
}

beforeDelete(ActiveRecord $ActiveRecord)/afterDelete(ActiveRecord $ActiveRecord)

여기에 무엇을 하려는지는 잘 모르겠습니다만, 여기서는 이상이 없습니다! 하세요!

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeDelete(self $self) {
        echo '용감한 전사였습니다... :cry-face:';
    } 
}

데이터베이스 연결 관리

이 라이브러리를 사용할 때, 데이터베이스 연결을 설정하는 몇 가지 방법이 있습니다. 생성자에서 연결을 설정하거나 설정 변수 $config['connection']를 통해 설정하거나 setDatabaseConnection()을 통해 설정할 수 있습니다.

$pdo_connection = new PDO('sqlite:test.db'); // 예시
$user = new User($pdo_connection);
// 또는
$user = new User(null, [ 'connection' => $pdo_connection ]);
// 또는
$user = new User();
$user->setDatabaseConnection($pdo_connection);

예를 들어 CLI 스크립트를 실행하는 경우 데이터베이스 연결을 주기적으로 새로 고칠 필요가 있는 경우 $your_record->setDatabaseConnection($pdo_connection)로 연결을 다시 설정할 수 있습니다.

기여

기여해 주세요. :D

설정

기여하는 경우 composer test-coverage를 실행하여 100%의 테스트 커버리지를 유지하세요(이는 실제 유닛 테스트 커버리지가 아니라 통합 테스트입니다).

또한 composer beautify를 실행하고 composer phpcs를 실행하여 린트 오류를 수정하세요.

라이선스

MIT

Awesome-plugins/latte

라떼

라떼는 매우 쉽게 사용할 수 있고 PHP 구문에 가까운 느낌을 주는 Twig나 Smarty보다 풀 기능을 갖춘 템플릿 엔진입니다. 또한 확장하고 자체 필터 및 함수를 추가하는 것도 매우 쉽습니다.

설치

컴포저로 설치하세요.

composer require latte/latte

기본 구성

시작하기 위한 몇 가지 기본 구성 옵션이 있습니다. 더 자세한 내용은 Latte Documentation에서 확인할 수 있습니다.


use Latte\Engine as LatteEngine;

require 'vendor/autoload.php';

$app = Flight::app();

$app->register('latte', LatteEngine::class, [], function(LatteEngine $latte) use ($app) {

    // 여기가 라떼가 템플릿을 캐시하여 속도를 높일 위치입니다
    // 라떼의 멋진 기능 중 하나는 템플릿을 수정할 때 자동으로 캐시를 새로 고쳐준다는 것입니다!
    $latte->setTempDirectory(__DIR__ . '/../cache/');

    // 라떼에게 보기의 루트 디렉토리가 어디에 있는지 알려줍니다.
    $latte->setLoader(new \Latte\Loaders\FileLoader($app->get('flight.views.path')));
});

간단한 레이아웃 예제

여기에 레이아웃 파일의 간단한 예제가 있습니다. 이 파일은 다른 모든 보기를 래핑하는 데 사용될 것입니다.

<!-- app/views/layout.latte -->
<!doctype html>
<html lang="en">
    <head>
        <title>{$title ? $title . ' - '}My App</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <nav>
                <!-- 네비게이션 요소를 여기에 추가하세요 -->
            </nav>
        </header>
        <div id="content">
            <!-- 이게 바로 마법입니다 -->
            {block content}{/block}
        </div>
        <div id="footer">
            &copy; 저작권
        </div>
    </body>
</html>

그리고 이제 해당 컨텐츠 블록 내에서 렌더링될 파일이 준비되어 있습니다:

<!-- app/views/home.latte -->
<!-- 이를 통해 라떼에게 이 파일이 layout.latte 파일의 "내부"에 있다는 것을 알려줍니다 -->
{extends layout.latte}

<!-- 이 컨텐츠가 layout의 내부에서 콘텐츠 블록 내에서 렌더링될 내용입니다 -->
{block content}
    <h1>홈 페이지</h1>
    <p>내 앱에 오신 것을 환영합니다!</p>
{/block}

그럼 이를 함수 또는 컨트롤러 내에서 렌더링하려면 다음과 같이 수행하세요:

// 간단한 경로
Flight::route('/', function () {
    Flight::latte()->render('home.latte', [
        'title' => '홈 페이지'
    ]);
});

// 또는 컨트롤러를 사용하는 경우
Flight::route('/', [HomeController::class, 'index']);

// HomeController.php
class HomeController
{
    public function index()
    {
        Flight::latte()->render('home.latte', [
            'title' => '홈 페이지'
        ]);
    }
}

Latte Documentation에서 라떼를 최대한 활용하는 방법에 대해 자세히 알아보세요!

Awesome-plugins/awesome_plugins

멋진 플러그인

Flight는 매우 확장 가능합니다. Flight 애플리케이션에 기능을 추가하는 데 사용할 수 있는 여러 플러그인이 있습니다. 일부는 공식적으로 Flight 팀에서 지원하며, 다른 것들은 시작하는 데 도움이 되는 마이크로/라이트 라이브러리입니다.

캐싱

캐싱은 응용 프로그램을 빠르게 만드는 좋은 방법입니다. Flight와 함께 사용할 수 있는 여러 캐싱 라이브러리가 있습니다.

쿠키

쿠키는 클라이언트 측에 작은 데이터 조각을 저장하는 좋은 방법입니다. 사용자 기본 설정, 응용 프로그램 설정 등을 저장하는 데 사용할 수 있습니다.

디버깅

로컬 환경에서 개발할 때 디버깅은 중요합니다. 디버깅 경험을 향상시킬 수 있는 몇 가지 플러그인이 있습니다.

데이터베이스

데이터베이스는 대부분의 응용 프로그램의 핵심입니다. 데이터를 저장하고 검색하는 방법입니다. 일부 데이터베이스 라이브러리는 단순히 쿼리를 작성하고 실행하기 위한 래퍼에 불과하며, 일부는 완전한 ORM입니다.

암호화

민감한 데이터를 저장하는 모든 애플리케이션에 암호화는 중요합니다. 데이터를 암호화하고 해독하는 것은 어렵지 않지만, 암호화 키를 올바르게 저장하는 것은 어려울 수 있습니다. 가장 중요한 것은 암호화 키를 공개 디렉터리에 저장하거나 코드 리포지토리에 커밋하지 않는 것입니다.

세션

세션은 API에는 실용적이지 않지만, 웹 애플리케이션을 개발할 때는 상태 및 로그인 정보를 유지하는 데 중요할 수 있습니다.

템플릿

UI가 있는 모든 웹 애플리케이션에는 템플릿이 근본적입니다. Flight와 함께 사용할 수 있는 여러 템플릿 엔진이 있습니다.

기여

공유하고 싶은 플러그인이 있으신가요? 리스트에 추가하려면 풀 리퀘스트를 제출해주세요!

Examples

빠른 시작이 필요하세요?

flightphp/skeleton 리포지토리로 이동해서 시작하세요! 이 프로젝트에는 앱을 실행하는 데 필요한 모든 것이 포함된 단일 페이지 파일이 포함되어 있습니다. 또한 컨트롤러와 뷰가 포함된 더 완전한 예제도 포함되어 있습니다.

영감이 필요하세요?

Flight 팀이 공식적으로 후원하지는 않지만, 이 프로젝트들은 Flight를 기반으로 한 프로젝트를 구조화하는 방법에 대한 아이디어를 제공할 수 있습니다!

자신의 예제를 공유하고 싶으신가요?

자신이 공유하고 싶은 프로젝트가 있다면, 이 목록에 추가하기 위해 풀 리퀘스트를 제출해주세요!