PhalconPHP: 고부하 RESTful API를 위한 솔루션
게시 됨: 2022-03-11PHP MVC 프레임워크를 기반으로 고부하 프로젝트를 생성해야 한다고 가정합니다. 가능하면 캐싱을 사용할 것입니다. 단일 파일에 프로젝트를 빌드하거나 최소한의 기능으로 자체 MVC 프레임워크를 작성하거나 다른 프레임워크의 일부를 다시 작성할 수도 있습니다. 예, 이것은 작동하지만 약간 까다롭지 않습니까? 다행히도 이러한 조작의 대부분을 불필요하게 만드는 솔루션이 하나 더 있으며(캐시를 제외하면) 이 솔루션을 PhalconPHP 프레임워크라고 합니다.
PhalconPHP란 무엇입니까?
PhalconPHP는 C로 작성되고 컴파일된 PHP 확장으로 제공되는 PHP용 MVC 프레임워크입니다. 이것이 가장 빠른 프레임워크 중 하나가 되는 이유입니다(완전히 가장 빠른 프레임워크는 Yaf이지만 마이크로 프레임워크이며 Phalcon보다 훨씬 훨씬 더 제한된 기능을 가지고 있습니다). PhalconPHP는 PHP 파일에 대한 긴 작업이 필요하지 않으며 모든 요청에서 해석될 필요가 없습니다. 웹 서버가 시작될 때 RAM에 한 번 로드되고 매우 적은 리소스를 소비합니다.
MVC 프레임워크는 오랫동안 웹 개발의 모범 사례로 간주되어 왔습니다. 이제 이것은 일종의 전문 표준이므로 대부분의 웹 개발자는 적어도 하나의 PHP용 MVC 프레임워크(Symfony, Yii, Laravel, CodeIgniter, Zend)에 익숙합니다. 프레임워크 등 각각의 장단점이 있지만 공통점은 무엇입니까? 모두 PHP로 작성되었으며 코드가 실행될 때마다 모든 요청에 대해 인터프리터가 실행해야 하는 엄청난 양의 논리가 포함된 많은 PHP 파일로 구성됩니다. 이를 통해 투명성을 높이면서도 성과에 따라 비용을 지불합니다. 많은 양의 코드와 많은 포함된 파일은 특히 PHP에서 많은 메모리와 시간을 소비합니다(컴파일이 아니라 해석되기 때문에). 네, PHP 7에서는 상황이 훨씬 나아졌지만 여전히 개선해야 할 부분이 많으며 PhalconPHP는 이러한 개선 사항을 테이블에 제공합니다.
몇 가지 벤치마크를 살펴보겠습니다.
PhalconPHP 벤치마크
공식 벤치마크는 5년 된 것입니다. 너무 오래되어 지금 유효하지 않지만 그때에도 PhalconPHP를 차별화하는 요소를 극적으로 볼 수 있습니다. 더 새로운 것을 봅시다. 2016년 비교에서 Phalcon은 상위 5위 안에 들었습니다. 전문 프레임워크 중 확실한 리더이며 원시 PHP와 일부 마이크로 프레임워크만 인정합니다.
그래서 팔콘은 빠릅니다. Raw PHP도 빠르지만 MVC 프레임워크가 제공해야 하는 모든 기능이 필요하며 Phalcon은 다음과 같은 구성 요소를 포함하여 문제에 도전합니다.
- ORM
- 볼트 템플릿 엔진
- 종속성 주입(DI) 컨테이너
- 캐싱
- 벌채 반출
- 라우팅 시스템
- 보안 블록
- 오토로더
- 양식 모듈
그것은 단지 몇 가지 이름입니다. 간단히 말해서 PhalconPHP에는 고부하 시스템용 RESTful API와 같은 대규모 엔터프라이즈 애플리케이션을 구축하는 데 필요한 모든 것이 있습니다.
Phalcon의 또 다른 좋은 점은 작은 스타일입니다. Phalcon ORM과 거대한 Doctrine 2를 비교해 보세요.
PhalconPHP 프로젝트를 만드는 방법을 살펴보겠습니다.
두 가지 유형의 Phalcon 프로젝트: 전체 스택 및 마이크로
일반적으로 MVC 프레임워크에는 전체 스택 프레임워크(예: Symfony, Yii)와 마이크로 프레임워크(예: Lumen, Slim, Silex)의 두 가지 유형이 있습니다.
풀 스택 프레임워크는 더 많은 기능을 제공하기 때문에 큰 프로젝트에 좋은 선택이지만 실행하는 데 더 많은 자격과 시간이 필요합니다.
마이크로 프레임워크를 사용하면 가벼운 프로토타입을 매우 빠르게 만들 수 있지만 기능이 부족하므로 일반적으로 대규모 프로젝트에는 사용하지 않는 것이 좋습니다. 그러나 마이크로 프레임워크의 한 가지 장점은 성능입니다. 일반적으로 전체 스택보다 훨씬 빠릅니다(예: Yaf 프레임워크는 원시 PHP보다 성능면에서 열등합니다).
PhalconPHP는 다음 두 가지를 모두 지원합니다. 전체 스택 또는 마이크로 애플리케이션을 만들 수 있습니다. 더 나아가 PhalconPHP에서 프로젝트를 마이크로 애플리케이션으로 개발할 때 Phalcon의 강력한 기능 대부분에 계속 액세스할 수 있으며 성능은 여전히 전체 스택 애플리케이션보다 더 빠릅니다.
과거 직장에서 우리 팀은 고부하 RESTful 시스템을 구축해야 했습니다. 우리가 한 일 중 하나는 Phalcon의 전체 스택 애플리케이션과 Phalcon 마이크로 애플리케이션 간의 프로토타입 성능을 비교하는 것이었습니다. 우리는 PhalconPHP의 마이크로 애플리케이션이 훨씬 더 빠른 경향이 있음을 발견했습니다. NDA 이유로 벤치마크를 보여드릴 수는 없지만 Phalcon의 성능을 최대한 활용하려면 마이크로 애플리케이션을 사용하는 것이 좋습니다. 전체 스택보다 마이크로 애플리케이션을 코딩하는 것이 덜 편리하지만 PhalconPHP의 마이크로 애플리케이션에는 프로젝트와 더 나은 성능에 필요한 모든 것이 있습니다. 설명을 위해 Phalcon에서 매우 간단한 RESTful 마이크로 애플리케이션을 작성해 보겠습니다.
RESTful API 빌드
RESTful 애플리케이션의 거의 모든 순열에는 한 가지 공통점이 있습니다. 바로 User
엔터티입니다. 따라서 예제 프로젝트에서는 사용자를 생성, 읽기, 업데이트 및 삭제하는 작은 REST 애플리케이션(CRUD라고도 함)을 생성합니다.
내 GitLab 저장소에서 이 프로젝트가 완전히 완료된 것을 볼 수 있습니다. 이 프로젝트를 두 부분으로 나누기로 결정했기 때문에 두 개의 분기가 있습니다. 첫 번째 분기인 master
에는 특정 PhalconPHP 기능이 없는 기본 기능만 포함되어 있고 두 번째 분기인 logging-and-cache
에는 Phalcon의 로깅 및 캐싱 기능이 포함되어 있습니다. 그것들을 비교하고 Phalcon에서 그러한 기능을 구현하는 것이 얼마나 쉬운지 알 수 있습니다.
설치
설치에 대해서는 다루지 않겠습니다. 원하는 데이터베이스, 운영 체제 및 웹 서버를 사용할 수 있습니다. 공식 설치 문서에 잘 설명되어 있으므로 운영 체제에 따라 지침을 따르십시오.
웹 서버 설치 정보는 공식 Phalcon 문서에서도 볼 수 있습니다.
PHP 버전은 5.6 이상이어야 합니다.
Ubuntu 16.10, PostgreSQL 9.5.6, Nginx 1.10.0, PHP 7 및 Phalcon 3.0을 사용합니다. 프로젝트에 Nginx 구성 샘플과 PostgreSQL 덤프 파일을 포함했으므로 자유롭게 사용하십시오. 다른 구성을 선호하는 경우 변경하는 것이 어렵지 않습니다.
프로젝트 구조 및 구성
먼저 초기 프로젝트 구조를 만듭니다.
Phalcon을 사용하면 원하는 구조를 사용할 수 있지만 이 연습에서 선택한 구조는 부분적으로 MVC 패턴을 구현합니다. RESTful 프로젝트이기 때문에 보기가 없지만 각각 고유한 폴더와 서비스가 있는 컨트롤러와 모델이 있습니다. 서비스는 MVC의 "모델" 부분을 데이터 모델(데이터베이스와 통신)과 비즈니스 논리 모델의 두 부분으로 나누는 프로젝트의 비즈니스 논리를 구현하는 클래스입니다.
public
폴더에 있는 index.php
는 필요한 모든 부분과 구성을 로드하는 부트스트랩 파일입니다. 모든 구성 파일은 config
폴더에 있습니다. 이것을 부트스트랩 파일에 넣을 수 있지만(공식 문서에 표시된 방식입니다), 제 생각에는 큰 프로젝트에서는 읽을 수 없으므로 처음부터 폴더를 분리하는 것을 선호합니다.
index.php
생성
index.php
의 첫 번째 패스는 구성 및 자동 로드 클래스를 로드한 다음 경로, 종속성 주입 컨테이너 및 PhalconPHP 마이크로 애플리케이션을 초기화합니다. 그런 다음 경로에 따라 요청을 처리하고 비즈니스 로직을 실행하고 결과를 반환하는 마이크로 애플리케이션 코어로 제어를 넘깁니다.
코드를 살펴보겠습니다.
<?php try { // Loading Configs $config = require(__DIR__ . '/../app/config/config.php'); // Autoloading classes require __DIR__ . '/../app/config/loader.php'; // Initializing DI container /** @var \Phalcon\DI\FactoryDefault $di */ $di = require __DIR__ . '/../app/config/di.php'; // Initializing application $app = new \Phalcon\Mvc\Micro(); // Setting DI container $app->setDI($di); // Setting up routing require __DIR__ . '/../app/config/routes.php'; // Making the correct answer after executing $app->after( function () use ($app) { // Returning a successful response } ); // Processing request $app->handle(); } catch (\Exception $e) { // Returning an error response }
\Phalcon\Config
개체 구성
Phalcon에 구성 파일을 저장하는 방법에는 여러 가지가 있습니다.
- YAML 파일
- JSON 파일
- INI 파일
- PHP 배열
PHP 배열에 구성을 저장하는 것이 가장 빠른 옵션이며, 우리는 고부하 응용 프로그램을 작성하고 있고 성능을 저하시킬 필요가 없기 때문에 이것이 우리가 할 일입니다. 특히 \Phalcon\Config
개체를 사용하여 구성 옵션을 프로젝트에 로드합니다. 매우 짧은 구성 개체가 있습니다.
<?php return new \Phalcon\Config( [ 'database' => [ 'adapter' => 'Postgresql', 'host' => 'localhost', 'port' => 5432, 'username' => 'postgres', 'password' => '12345', 'dbname' => 'articledemo', ], 'application' => [ 'controllersDir' => "app/controllers/", 'modelsDir' => "app/models/", 'baseUri' => "/", ], ] );
이 파일에는 데이터베이스용과 애플리케이션용의 두 가지 기본 구성이 포함되어 있습니다. 분명히 데이터베이스 구성은 데이터베이스에 연결하는 데 사용되며 application
배열의 경우 Phalcon의 시스템 도구에서 사용하기 때문에 나중에 필요합니다. 공식 문서에서 Phalcon 구성에 대해 자세히 읽을 수 있습니다.
loader.php
구성
다음 구성 파일인 loader.php
를 살펴보겠습니다. loader.php
파일은 \Phalcon\Loader
객체를 통해 해당 디렉토리에 네임스페이스를 등록합니다. 훨씬 더 간단합니다.
<?php $loader = new \Phalcon\Loader(); $loader->registerNamespaces( [ 'App\Services' => realpath(__DIR__ . '/../services/'), 'App\Controllers' => realpath(__DIR__ . '/../controllers/'), 'App\Models' => realpath(__DIR__ . '/../models/'), ] ); $loader->register();
이제 이 네임스페이스의 모든 클래스가 자동으로 로드되어 사용할 수 있습니다. 새 네임스페이스와 디렉토리를 추가하려면 이 파일에 한 줄만 추가하면 됩니다. 특정 디렉토리 또는 특정 파일을 등록하여 네임스페이스 사용을 피할 수도 있습니다. 이러한 모든 가능성은 PhalconPHP 로더 문서에 설명되어 있습니다.
종속성 주입 컨테이너 구성
다른 많은 현대 프레임워크와 마찬가지로 Phalcon은 종속성 주입(DI) 패턴을 구현합니다. 개체는 DI 컨테이너에서 초기화되고 DI 컨테이너에서 액세스할 수 있습니다. 마찬가지로 DI 컨테이너는 응용 프로그램 개체에 연결되며 컨트롤러 및 서비스와 같이 \Phalcon\DI\Injectable
클래스에서 상속되는 모든 클래스에서 액세스할 수 있습니다.
Phalcon의 DI 패턴은 매우 강력합니다. 저는 이 구성 요소를 이 프레임워크에서 가장 중요한 것 중 하나로 생각하며, 작동 방식을 이해하기 위해 전체 설명서를 읽을 것을 강력히 권장합니다. Phalcon의 많은 기능에 대한 키를 제공합니다.
그 중 몇 가지를 살펴보겠습니다. di.php
파일은 다음과 같습니다.
<?php use Phalcon\Db\Adapter\Pdo\Postgresql; // Initializing a DI Container $di = new \Phalcon\DI\FactoryDefault(); /** * Overriding Response-object to set the Content-type header globally */ $di->setShared( 'response', function () { $response = new \Phalcon\Http\Response(); $response->setContentType('application/json', 'utf-8'); return $response; } ); /** Common config */ $di->setShared('config', $config); /** Database */ $di->set( "db", function () use ($config) { return new Postgresql( [ "host" => $config->database->host, "username" => $config->database->username, "password" => $config->database->password, "dbname" => $config->database->dbname, ] ); } ); return $di;
보시다시피 DI(종속성 주입) 파일은 조금 더 복잡하며 알아야 할 몇 가지 세부 사항이 있습니다. 먼저 초기화 문자열을 고려하십시오. $di = new \Phalcon\DI\FactoryDefault();
. \Phalcon\Di
를 상속하는 FactoryDefault
객체를 생성합니다(Phalcon을 사용하면 원하는 DI 팩토리를 생성할 수 있습니다). 설명서에 따르면 FactoryDefault
는 "프레임워크에서 제공하는 모든 서비스를 자동으로 등록합니다. 덕분에 개발자는 풀 스택 프레임워크를 제공하여 각 서비스를 개별적으로 등록할 필요가 없습니다.” 이는 Request
및 Response
과 같은 공통 서비스가 프레임워크 클래스 내에서 액세스할 수 있음을 의미합니다. Phalcon 서비스 문서에서 이러한 서비스의 전체 목록을 볼 수 있습니다.
다음으로 중요한 것은 설정 과정입니다. DI 컨테이너에 무언가를 등록하는 방법에는 여러 가지가 있으며, 모두 PhalconPHP 등록 문서에 완전히 설명되어 있습니다. 그러나 우리 프로젝트에서는 익명 함수, 변수 및 문자열의 세 가지 방법을 사용합니다.
익명 함수를 사용하면 클래스를 초기화하는 동안 많은 작업을 수행할 수 있습니다. 특히 이 프로젝트에서는 먼저 Response
개체를 재정의하여 모든 프로젝트 응답에 대해 content-type
을 JSON
으로 설정한 다음 구성 개체를 사용하여 데이터베이스 어댑터를 초기화합니다.
앞서 언급했듯이 이 프로젝트는 PostgreSQL을 사용합니다. 다른 데이터베이스 엔진을 사용하기로 결정했다면 db
set 함수에서 데이터베이스 어댑터를 변경하기만 하면 됩니다. PhalconPHP의 데이터베이스 문서에서 사용 가능한 데이터베이스 어댑터와 데이터베이스 계층에 대해 자세히 알아볼 수 있습니다.
세 번째로 주의할 점은 \Phalcon\Config
서비스를 구현하는 $config
변수를 등록한다는 것입니다. 우리의 예제 프로젝트에서 실제로 사용되지는 않지만 가장 일반적으로 사용되는 서비스 중 하나이기 때문에 여기에 포함하기로 결정했습니다. 다른 프로젝트는 거의 모든 곳에서 구성에 액세스해야 할 수 있습니다.
여기서 마지막으로 흥미로운 점은 실제 setShared
메서드 자체입니다. 이것을 호출하면 서비스가 "공유"되어 싱글톤처럼 작동하기 시작합니다. 문서에 따르면: "서비스가 처음으로 해결되면 소비자가 컨테이너에서 서비스를 검색할 때마다 동일한 인스턴스가 반환됩니다."
routes.php
구성 ...또는 아님
마지막으로 포함된 파일은 routes.php
입니다. 지금은 비워 두겠습니다. 컨트롤러와 함께 채울 것입니다.
RESTful 코어 구현
웹 프로젝트를 RESTful로 만드는 것은 무엇입니까? Wikipedia에 따르면 RESTful 애플리케이션에는 세 가지 주요 부분이 있습니다. - 기본 URL - 상태 전환 데이터 요소를 정의하는 인터넷 미디어 유형 - 표준 HTTP 메서드( GET
, POST
, PUT
, DELETE
) 및 표준 HTTP 응답 코드(200, 403, 400, 500 등).
우리 프로젝트에서 기본 URL은 routes.php
파일에 배치되고 다른 언급된 사항이 지금 설명됩니다.
요청 데이터를 application/x-www-form-urlencoded
로 수신하고 응답 데이터를 application/json
으로 보냅니다. 실제 응용 프로그램에서 x-www-form-urlencoded
를 사용하는 것이 좋은 생각이라고는 생각하지 않지만(x-www-form- x-www-form-urlencoded
를 사용하여 복잡한 데이터 구조와 연관 배열을 보내는 데 어려움을 겪을 것이기 때문에) 단순함을 위해 이 표준을 구현하기로 결정했습니다.
기억하신다면 DI 파일에 이미 응답 JSON 헤더를 설정했습니다.
$di->setShared( 'response', function () { $response = new \Phalcon\Http\Response(); $response->setContentType('application/json', 'utf-8'); return $response; } );
이제 응답 코드와 응답 형식을 설정해야 합니다. 공식 자습서에서는 모든 단일 방법에서 JSON 응답을 구성할 것을 제안하지만 좋은 생각은 아닙니다. 컨트롤러 메서드 결과를 배열로 반환한 다음 표준 JSON 응답으로 변환하는 것이 훨씬 더 보편적입니다. 또한 프로젝트 내 한 곳에서 HTTP 응답 코드를 구성하는 것이 더 현명합니다. index.php
파일에서 이 작업을 수행할 것입니다.
이를 위해 $app->before()
및 $app->after()
메서드로 요청 처리 전후에 코드를 실행하는 Phalcon의 기능을 활용할 것입니다. 목적을 위해 $app->after()
메서드에 콜백을 배치합니다.
// Making the correct answer after executing $app->after( function () use ($app) { // Getting the return value of method $return = $app->getReturnedValue(); if (is_array($return)) { // Transforming arrays to JSON $app->response->setContent(json_encode($return)); } elseif (!strlen($return)) { // Successful response without any content $app->response->setStatusCode('204', 'No Content'); } else { // Unexpected response throw new Exception('Bad Response'); } // Sending response to the client $app->response->send(); }
여기에서 반환 값을 얻고 배열을 JSON으로 변환합니다. 모든 것이 정상이지만 반환 값이 비어 있는 경우(예: 새 사용자를 성공적으로 추가한 경우) 204 HTTP 코드를 제공하고 콘텐츠를 보내지 않습니다. 다른 모든 경우에는 예외가 발생합니다.
예외 처리
RESTful 애플리케이션의 가장 중요한 측면 중 하나는 정확하고 유익한 응답입니다. 고부하 애플리케이션은 일반적으로 크기가 크고 유효성 검사 오류, 액세스 오류, 연결 오류, 예기치 않은 오류 등 다양한 유형의 오류가 모든 곳에서 발생할 수 있습니다. 이러한 모든 오류를 통합 HTTP 응답 코드로 변환하려고 합니다. 예외의 도움으로 쉽게 수행할 수 있습니다.
내 프로젝트에서 두 가지 다른 종류의 예외를 사용하기로 결정했습니다. "로컬" 예외가 있습니다. \RuntimeException
클래스에서 상속된 특수 클래스가 서비스, 모델, 어댑터 등으로 구분됩니다(이러한 구분은 각 수준을 처리하는 데 도움이 됩니다. MVC 모델의 별도 모델)- 그런 다음 AbstractHttpException
클래스에서 상속된 HttpExceptions
가 있습니다. 이러한 예외는 HTTP 응답 코드와 일치하므로 해당 이름은 Http400Exception
, Http500Exception
등입니다.
AbstractHttpException
클래스에는 httpCode
, httpMessage
및 appError
의 세 가지 속성이 있습니다. 처음 두 속성은 상속자에서 재정의되고 httpCode: 400
및 httpMessage: Bad request
와 같은 기본 응답 정보를 포함합니다. appError
속성은 오류 설명을 포함한 자세한 오류 정보의 배열입니다.
index.php
의 최종 버전은 세 가지 유형의 예외를 포착합니다. 위에서 설명한 대로 AbstractHttpExceptions
; 요청을 구문 분석하는 동안 발생할 수 있는 Phalcon 요청 예외 및 기타 모든 예상치 못한 예외. 모두 예쁜 JSON 형식으로 변환되어 표준 Phalcon Response 클래스를 통해 클라이언트로 전송됩니다.
<?php use App\Controllers\AbstractHttpException; try { // Loading Configs $config = require(__DIR__ . '/../app/config/config.php'); // Autoloading classes require __DIR__ . '/../app/config/loader.php'; // Initializing DI container /** @var \Phalcon\DI\FactoryDefault $di */ $di = require __DIR__ . '/../app/config/di.php'; // Initializing application $app = new \Phalcon\Mvc\Micro(); // Setting DI container $app->setDI($di); // Setting up routing require __DIR__ . '/../app/config/routes.php'; // Making the correct answer after executing $app->after( // After Code ); // Processing request $app->handle(); } catch (AbstractHttpException $e) { $response = $app->response; $response->setStatusCode($e->getCode(), $e->getMessage()); $response->setJsonContent($e->getAppError()); $response->send(); } catch (\Phalcon\Http\Request\Exception $e) { $app->response->setStatusCode(400, 'Bad request') ->setJsonContent([ AbstractHttpException::KEY_CODE => 400, AbstractHttpException::KEY_MESSAGE => 'Bad request' ]) ->send(); } catch (\Exception $e) { // Standard error format $result = [ AbstractHttpException::KEY_CODE => 500, AbstractHttpException::KEY_MESSAGE => 'Some error occurred on the server.' ]; // Sending error response $app->response->setStatusCode(500, 'Internal Server Error') ->setJsonContent($result) ->send(); }
Phalcon Dev Tools로 모델 생성하기
최신 IDE를 사용하는 경우 코드 강조 표시 및 완성에 익숙할 것입니다. 마찬가지로, 일반적인 PHP 프레임워크에서 프레임워크가 있는 폴더를 포함하면 한 번의 클릭으로 함수 선언으로 이동할 수 있습니다. Phalcon은 확장 기능이므로 이 옵션을 자동으로 가져오지 않습니다. 다행스럽게도 Composer를 통해 설치할 수 있는 "Phalcon Dev Tools"라는 이러한 격차를 메우는 도구가 있습니다. Phalcon Dev Tools는 Phalcon의 모든 클래스와 기능에 대한 코드 스텁으로 구성되어 있으며 PhalconPHP 웹사이트에 문서화된 콘솔 및 GUI 버전이 있는 일부 코드 생성기를 제공합니다. 이러한 도구는 MVC 패턴의 모든 부분을 만드는 데 도움이 될 수 있지만 모델 생성만 다룰 것입니다.

자, Composer를 통해 Phalcon Dev Tools를 설치해 보겠습니다. 우리의 composer.json
파일은 다음과 같을 것입니다:
{ "require": { "php": ">=5.6.0", "ext-phalcon": ">=3", "ext-pgsql": "*" }, "require-dev": { "phalcon/devtools": "3.*.*@dev" } }
보시다시피 PHP 5.6, Phalcon 3 및 pgsql
확장(데이터베이스 확장으로 변경하거나 완전히 제외할 수 있음)이 필요합니다.
PHP, Phalcon 및 DB 확장 버전이 올바른지 확인하고 작곡가를 실행합니다.
$ composer install
다음 단계는 데이터베이스를 만드는 것입니다. 그것은 매우 간단하고 단 하나의 테이블, users
로 구성되어 있습니다. 프로젝트에 pg_dump
파일을 포함했지만 PostgreSQL 언어로 된 SQL은 다음과 같습니다.
CREATE DATABASE articledemo; CREATE TABLE public.users ( id INTEGER PRIMARY KEY NOT NULL DEFAULT nextval('users_id_seq'::regclass), first_name CHARACTER VARYING(255), last_name CHARACTER VARYING(255), pass CHARACTER VARYING(255), login CHARACTER VARYING(255) NOT NULL );
이제 데이터베이스가 생성되었으므로 모델 생성 프로세스를 진행할 수 있습니다. Phalcon Dev Tools는 빈 .phalcon
폴더를 사용하여 애플리케이션이 Phalcon 프로젝트인지 감지하므로 프로젝트 루트에 이 빈 폴더를 만들어야 합니다. 또한 우리가 만든 구성 파일의 일부 설정( application
섹션 아래에 저장된 모든 변수와 database
섹션의 adapter
)을 사용합니다. 모델을 생성하려면 프로젝트 루트 폴더에서 다음 명령을 실행해야 합니다.
$ php vendor/phalcon/devtools/phalcon.php model users --namespace="App\Models" --get-set
이전 단계가 모두 올바르게 수행되었으면 models
폴더에 작업 모델 파일인 Users.php
가 표시되며, 이 파일은 명령줄에 표시된 대로 getter 및 setter가 있는 네임스페이스에 이미 배치되어 있습니다. 다음은 컨트롤러입니다.
컨트롤러 및 라우팅
우리 응용 프로그램은 사용자를 CRUD(생성, 읽기, 업데이트 및 삭제)하기만 하므로 다음 작업으로 Users
컨트롤러인 하나의 컨트롤러만 생성합니다.
- 사용자 추가
- 사용자 목록 표시
- 사용자 업데이트
- 사용자 삭제
Phalcon Dev Tools의 도움으로 컨트롤러를 생성할 수 있지만, 우리는 수동으로 생성하고 AbstractController
와 그 자식인 UsersController
를 구현할 것입니다.
AbstractController
를 생성하는 것은 Phalcon에게 좋은 결정입니다. 의존성 주입에서 얻을 수 있는 모든 필수 클래스를 PHPDoc 블록에 배치할 수 있기 때문입니다. 이것은 IDE 자동 완성 기능에 도움이 됩니다. 또한 모든 잠재적 컨트롤러에 공통적인 몇 가지 오류 상수를 프로그래밍할 수 있습니다.
지금은 추상 컨트롤러가 다음과 같이 보일 것입니다.
<?php namespace App\Controllers; /** * Class AbstractController * * @property \Phalcon\Http\Request $request * @property \Phalcon\Http\Response $htmlResponse * @property \Phalcon\Db\Adapter\Pdo\Postgresql $db * @property \Phalcon\Config $config * @property \App\Services\UsersService $usersService * @property \App\Models\Users $user */ abstract class AbstractController extends \Phalcon\DI\Injectable { /** * Route not found. HTTP 404 Error */ const ERROR_NOT_FOUND = 1; /** * Invalid Request. HTTP 400 Error. */ const ERROR_INVALID_REQUEST = 2; }
extends
구문에 지정된 대로 단순한 Phalcon 주입 가능 클래스일 뿐입니다. 다음으로 UsersController
스켈레톤을 생성해 보겠습니다.
<?php namespace App\Controllers; /** * Operations with Users: CRUD */ class UsersController extends AbstractController { /** * Adding user */ public function addAction() { } /** * Returns user list * * @return array */ public function getUserListAction() { } /** * Updating existing user * * @param string $userId */ public function updateUserAction($userId) { } /** * Delete an existing user * * @param string $userId */ public function deleteUserAction($userId) { } }
현재로서는 해당 HTTP 요청을 궁극적으로 보유할 빈 작업이 있는 클래스일 뿐입니다.
이제 routes.php
파일을 채울 시간입니다. Phalcon 마이크로 애플리케이션에서 각 컨트롤러에 대해 하나씩 컬렉션을 만들고 처리된 모든 요청을 get
, post
, put
, delete
메서드로 추가합니다. 이 메서드는 경로 패턴과 진행 기능을 인수로 사용합니다. 계속되는 함수는 익명 함수이거나 컨트롤러의 메서드 이름이어야 합니다. 다음은 routes.php
파일의 모습입니다.
<?php $usersCollection = new \Phalcon\Mvc\Micro\Collection(); $usersCollection->setHandler('\App\Controllers\UsersController', true); $usersCollection->setPrefix('/user'); $usersCollection->post('/add', 'addAction'); $usersCollection->get('/list', 'getUserListAction'); $usersCollection->put('/{userId:[1-9][0-9]*}', 'updateUserAction'); $usersCollection->delete('/{userId:[1-9][0-9]*}', 'deleteUserAction'); $app->mount($usersCollection); // not found URLs $app->notFound( function () use ($app) { $exception = new \App\Controllers\HttpExceptions\Http404Exception( _('URI not found or error in request.'), \App\Controllers\AbstractController::ERROR_NOT_FOUND, new \Exception('URI not found: ' . $app->request->getMethod() . ' ' . $app->request->getURI()) ); throw $exception; } );
또한 핸들링 컨트롤러와 URI 접두사를 설정합니다. 이 예에서 URI는 http://article.dev/user/add
와 같이 표시되며 post
요청이어야 합니다. 사용자 데이터를 변경하려는 경우 URI는 put
요청이어야 하며 ID가 12
인 사용자의 데이터를 변경하기 위해 http://article.dev/user/12
와 같이 표시됩니다. 또한 오류를 발생시키는 찾을 수 없는 URL 처리기를 정의합니다. 자세한 내용은 전체 스택 응용 프로그램의 경로와 마이크로 응용 프로그램의 경로에 대한 PhalconPHP 설명서를 참조하십시오.
컨트롤러의 본체, 특히 addAction
메서드로 이동하겠습니다(다른 모든 항목은 유사하며 애플리케이션 코드에서 볼 수 있음). 컨트롤러 메서드는 5가지 작업을 수행합니다.
- 요청 매개변수를 가져오고 유효성을 검사합니다.
- 서비스 방법에 대한 데이터 준비
- 서비스 메서드를 호출합니다.
- 예외 처리
- 응답을 보냅니다
유효성 검사부터 시작하여 각 단계를 살펴보겠습니다. Phalcon에는 강력한 유효성 검사 구성 요소가 있지만 이 경우에는 이전 스타일 방식으로 데이터 유효성을 검사하는 것이 훨씬 더 편리하므로 유효성 검사 블록은 다음과 같습니다.
$errors = []; $data = []; $data['login'] = $this->request->getPost('login'); if (!is_string($data['login']) || !preg_match('/^[A-z0-9_-]{3,16}$/', $data['login'])) { $errors['login'] = 'Login must consist of 3-16 latin symbols, numbers or \'-\' and \'_\' symbols'; }
여기에서 post
매개변수가 정규식과 일치하는 문자열인지 확인합니다. 모든 값은 $data
배열에 넣은 다음 UsersService
클래스에 전달됩니다. 모든 오류는 $errors
배열에 배치되고 Http400Exception
내부의 오류 세부정보 배열에 추가됩니다. 여기에서 index.php
에서 볼 수 있는 자세한 응답으로 변환됩니다.
다음은 모든 유효성 검사가 포함된 전체 addAction
메서드 코드입니다. 여기에는 아직 생성하지 않은 UsersService
의 createUser
메서드 호출이 포함되어 있습니다.
public function addAction() { /** Init Block **/ $errors = []; $data = []; /** End Init Block **/ /** Validation Block **/ $data['login'] = $this->request->getPost('login'); if (!is_string($data['login']) || !preg_match('/^[A-z0-9_-]{3,16}$/', $data['login'])) { $errors['login'] = 'Login must consist of 3-16 latin symbols, numbers or \'-\' and \'_\' symbols'; } $data['password'] = $this->request->getPost('password'); if (!is_string($data['password']) || !preg_match('/^[A-z0-9_-]{6,18}$/', $data['password'])) { $errors['password'] = 'Password must consist of 6-18 latin symbols, numbers or \'-\' and \'_\' symbols'; } $data['first_name'] = $this->request->getPost('first_name'); if ((!empty($data['first_name'])) && (!is_string($data['first_name']))) { $errors['first_name'] = 'String expected'; } $data['last_name'] = $this->request->getPost('last_name'); if ((!empty($data['last_name'])) && (!is_string($data['last_name']))) { $errors['last_name'] = 'String expected'; } if ($errors) { $exception = new Http400Exception(_('Input parameters validation error'), self::ERROR_INVALID_REQUEST); throw $exception->addErrorDetails($errors); } /** End Validation Block **/ /** Passing to business logic and preparing the response **/ try { $this->usersService->createUser($data); } catch (ServiceException $e) { switch ($e->getCode()) { case AbstractService::ERROR_ALREADY_EXISTS: case UsersService::ERROR_UNABLE_CREATE_USER: throw new Http422Exception($e->getMessage(), $e->getCode(), $e); default: throw new Http500Exception(_('Internal Server Error'), $e->getCode(), $e); } } /** End Passing to business logic and preparing the response **/ }
보시다시피, 우리는 마지막 섹션에서 두 가지 알려진 예외를 처리합니다. user already exists
하고 데이터베이스 연결 오류와 같은 내부 문제로 인해 unable to create user
. 기본적으로 알 수 없는 예외는 HTTP 500
(내부 서버 오류)으로 발생합니다. 최종 사용자에게 세부 정보를 제공하지는 않지만 모든 오류 세부 정보(추적 포함)를 로그에 저장하는 것이 좋습니다.
그리고 다른 네임스페이스에서 빌린 모든 필수 클래스를 use
하는 것을 잊지 마십시오.
use App\Controllers\HttpExceptions\Http400Exception; use App\Controllers\HttpExceptions\Http422Exception; use App\Controllers\HttpExceptions\Http500Exception; use App\Services\AbstractService; use App\Services\ServiceException; use App\Services\UsersService;
비즈니스 로직
마지막으로 생성할 부분은 비즈니스 로직입니다. 컨트롤러와 마찬가지로 추상 서비스 클래스를 생성합니다.
<?php namespace App\Services; /** * Class AbstractService * * @property \Phalcon\Db\Adapter\Pdo\Postgresql $db * @property \Phalcon\Config $config */ abstract class AbstractService extends \Phalcon\DI\Injectable { /** * Invalid parameters anywhere */ const ERROR_INVALID_PARAMETERS = 10001; /** * Record already exists */ const ERROR_ALREADY_EXISTS = 10002; }
아이디어는 컨트롤러 블록과 완전히 동일하므로 언급하지 않겠습니다. 다음은 UsersService
클래스의 골격입니다.
<?php namespace App\Services; use App\Models\Users; /** * business logic for users * * Class UsersService */ class UsersService extends AbstractService { /** Unable to create user */ const ERROR_UNABLE_CREATE_USER = 11001; /** * Creating a new user * * @param array $userData */ public function createUser(array $userData) { } }
그리고 createUser
메소드 자체:
public function createUser(array $userData) { try { $user = new Users(); $result = $user->setLogin($userData['login']) ->setPass(password_hash($userData['password'], PASSWORD_DEFAULT)) ->setFirstName($userData['first_name']) ->setLastName($userData['last_name']) ->create(); if (!$result) { throw new ServiceException('Unable to create user', self::ERROR_UNABLE_CREATE_USER); } } catch (\PDOException $e) { if ($e->getCode() == 23505) { throw new ServiceException('User already exists', self::ERROR_ALREADY_EXISTS, $e); } else { throw new ServiceException($e->getMessage(), $e->getCode(), $e); } } }
이 방법은 가능한 한 쉽습니다. 우리는 단지 새로운 모델 객체를 생성하고, 그것의 setter를 호출하고(객체 자체를 반환합니다; 이것은 우리가 호출 체인을 만들 수 있게 해줍니다), 오류의 경우 ServiceException
을 던집니다. 그게 다야! 이제 테스트를 진행할 수 있습니다.
테스트
이제 Postman을 사용하여 결과를 살펴보겠습니다. 먼저 일부 쓰레기 데이터를 테스트해 보겠습니다.
요구:
POST http://article.dev/user/add login:1 password:1 first_name:Name last_name:Sourname
응답(400: 잘못된 요청):
{ "error": 2, "error_description": "Input parameters validation error", "details": { "login": "Login must consist of 3-16 latin symbols, numbers or '-' and '_' symbols", "password": "Password must consist of 6-18 latin symbols, numbers or '-' and '_' symbols" } }
확인합니다. 이제 일부 올바른 데이터에 대해 다음을 수행합니다.
요구:
POST http://article.dev/user/add login:user4 password:password4 first_name:Name last_name:Sourname
응답(204):
내용이 없습니다. 예상했던 대로입니다. Now let's make sure it worked and get the full user list (which we didn't describe in the article, but you can see it in the application example):
Request:
GET http://article.dev/user/list
Response (200 OK):
[ { "id": 1, "login": "user4", "first_name": "Name", "last_name": "Sourname" } ]
Well, it works!
Logging and Caching
It's hard to imagine a high-load application without logging and caching, and Phalcon provides very seductive classes for it. But I'm writing an article here, not a book; I've added logging and caching to the sample application, but I've placed this code into another branch called logging-and-cache
so you can easily look at it and see the difference in the code. Just like the other Phalcon features, these two are well-documented: Logging and Caching.
단점
As you can see, Phalcon is really cool, but like other frameworks, it has its disadvantages, the first of which is the same as its main advantage—it's a compiled C extension. That's why there is no way for you to change its code easily. Well, if you know C, you can try to understand its code and make some changes, run make
and get your own modification of Phalcon, but it is much more complicated than making some tweaks in PHP code. So, generally, if you find a bug inside Phalcon, it won't be so easy to fix.
This is partially solved in Phalcon 2 and Phalcon 3, which let you write extensions to Phalcon in Zephir. Zephir is a programming language designed to ease the creation and maintainability of extensions for PHP with a focus on type and memory safety. Its syntax is very close to PHP and Zephir code is compiled into shared libraries, same as the PHP extension. So, if you want to enhance Phalcon, now you can.
The second disadvantage is the free framework structure. While Symfony makes developers use a firm project structure, Phalcon has very few strict rules; developers can create any structure they like, though there is a structure that is recommended by its authors. This isn't a critical disadvantage, but some people may consider it too raw when you write the paths to all the directories in a bootstrap file manually.
PhalconPHP: Not Just For High-load Apps
I hope you've enjoyed this brief overview of PhalconPHP's killing features and the accompanying simple example of a Phalcon project. Obviously, I didn't cover all the possibilities of this framework since it's impossible to describe all of them in one article, but fortunately Phalcon has brilliantly detailed documentation with seven marvelous tutorials which help you understand almost everything about Phalcon.
You've now got a brand new way to create high load applications easily, and you'll find, if you like Phalcon, it can be a good choice for other types of applications too.