2年間の開発の後、 PHPixieフレームワークの3番目のバージョンが完成しました 。 なぜそんなに長いの? 実際、この間に3つ以上のORMとテンプレートエンジンが作成されましたが、それらは削除され、再び書き換えられました。 特にテストに多くの時間が費やされ、それなしでは膨大な数の改善に気付かなかったでしょう。 多くの場合、私はこのビジネスを離れ、2番目のバージョンを停止し、それにモジュールを追加したかっただけです。 しかし、今、これらすべての反復が完了したとき、これが私が知っている(そして私ができること)最高の実装であると自信を持って言うことができます。 これは、PHPixie 3があなたを喜ばせるものです:
- PSR-2およびPSR-4標準に従う
- PSR-7リクエストとそれらとの便利な作業のためのライブラリのサポート
- シンプルなPHPを使用するテンプレートエンジンですが、Twigのような継承とブロックをサポートしています。 拡張機能や他の形式(HAMLなど)を簡単に追加できます。
- ActiveRecordとして使いやすいが、同時にクエリ、エンティティ、リポジトリのロジックを個別に壊すORM。 MongoDBコレクションへのリンクと、多くのエンティティに対するクエリの最適化を同時にサポートします(たとえば、1つのリクエストで複数のタグを持つ複数の記事をリンクできます)。
- 通常のコントローラーの代わりにプロセッサーを使用するアプローチにより、任意のアーキテクチャーを作成できます。
- 設定をフォルダに詳細に分割できる構成コンポーネント(たとえば、キーlanguages.en.plural.mouseは、 languages / en.phpファイル内のキーplural.mouseを参照できます)
- バンドルシステムにより、複数のプロジェクトで1つのコードを簡単に使用し、他のユーザーと共有できます。 バンドルは、他のライブラリと同様にコンポーザーを介してインストールされます。
そして今、PHPixie 3で開発を開始するために知っておく必要があるすべてを示す短いチュートリアルです。
設置
最初に、Composerを使用してプロジェクトスケルトンを作成します。
php composer.phar create-project phpixie/project your_project_folder 3.*-dev
HTTPサーバーを/ webディレクトリフォルダーに設定します。 Nginxの場合、これを構成に追加する必要もあります。
location / { try_files $uri $uri/ /index.php; }
Windowsを使用している場合、ショートカット/ web / bundles / appを指す/ bundles / app / webを作成する必要があります。
LinuxおよびMacOSでは、ショートカットはそのまま使用できます。 デフォルトのバンドルWebファイルへのアクセスを開くために必要です(すべてを説明します)。
これで、 localhostリンクに続いて、短い挨拶が表示されます。
バンドル
PHPixie 3は、たとえばSymfony2のように、バンドルシステムをサポートしています。 それらをまだ使用していない場合は、サイトを論理的な部分に分割し、他のプロジェクトに簡単に転送してComposerで共有できることを想像してください。 たとえば、ユーザー認証は、独自のテンプレート、スタイル、画像を含む個別のバンドルとして作成し、複数のプロジェクトで使用できます。 バンドルは、Linuxなどのプロジェクトで「マウント」され、ディスクはファイルシステムにマウントされます。
最初に、プロジェクトは、「/ bundles / app」内の単一のバンドル「app」で作成されます。 また、3番目のバージョンのプロジェクトの構造は2番目のバージョンよりも複雑に見えますが、これまでのところバンドルは1つしかありませんが、このディレクトリ以外で何かを行うことはほとんどありません。
プロセッサー
MVCコントローラーの使い慣れた概念はPHPixieで大幅に拡張され、無限のネストと任意のアーキテクチャをサポートするようになりました。 考え方は、フレームワークに関して「コントローラー」が1つしかないということです。 しかし、たとえば、彼は自分の機能を「サブコントローラー」に委任し、一般的には何でもしたいことができます。 プロセッサインターフェイス(現在呼び出されている)は、1つのプロセス($要求)メソッドのみで構成されています。 これにより、リクエストを異なる方法で処理するプロセッサのいくつかの基本クラスを作成できました。 もちろん、オプションの1つは通常のコントローラーと完全に類似しているため、すべてを旧式の方法で行うことができます。これは、標準のGreetプロセッサーが行うことで、インストール後に挨拶を表示します。
次に、すべてを試す新しいQuickstartプロセッサを作成します。
// bundles/app/src/Project/App/HTTPProcessors/Quickstart.php namespace Project\App\HTTPProcessors; use PHPixie\HTTP\Request; // class Quickstart extends \PHPixie\DefaultBundle\Processor\HTTP\Actions { /** * Builder * * @var Project\App\Builder */ protected $builder; public function __construct($builder) { $this->builder = $builder; } // public function defaultAction(Request $request) { return "Quickstart tutorial"; } // }
次に、バンドルのメインプロセッサに登録する必要があります。 デフォルトでは、ルーティングパラメータで指定されたプロセッサにリクエストを送信するように構成されています。 2番目のバージョンでは、コントローラはネームスペースとクラス名によって自動的に決定されるため、どこにも登録する必要はありませんでした。 このアプローチは、クラスの命名とフォルダー構造を備えたいくつかのフレームワークに適合するという点で不便です。 これで、各コントローラーにビルダーメソッドが追加されるだけで、クラスに好きな名前を付けたり、コンストラクターを介してさまざまなパラメーターを渡したりできます。
// bundles/app/src/Project/App/HTTPProcessors.php //... protected function buildQuickstartProcessor() { return new HTTPProcessors\Quickstart( $this->builder ); } //...
localhost /クイックスタートに移動すると、「クイックスタートチュートリアル」メッセージが表示されます。
これで、フレームワークの他の部分を試すことができます。
ルーティング
多くの場合、ページまたは製品のIDまたは名前を含む/ quickstart / view / 4のようなリンクを作成する必要があります。 これを行うには、まずプロセッサーで単純なアクションを作成します。
// bundles/app/src/Project/App/HTTPProcessors/Quickstart.php //... public function viewAction($request) { // 'id' return $request->attributes()->get('id'); } //... }
ここで、構成に既に登録されているルールに、このパラメーターを使用したルールを追加する必要もあります。 しかし、最初に、それがどのように見えるかを考慮してください:
// bundles/app/assets/config/routeResolver return array( // // // - 'type' => 'group', 'resolvers' => array( //... // 'default' => array( 'type' => 'pattern', // 'path' => '(<processor>(/<action>))', // // /greet // 'action' 'default' 'defaults' => array( 'processor' => 'greet', 'action' => 'default' ) ) ) );
追加する部分は次のようになります。
'view' => array( 'type' => 'pattern', // id // 'path' => 'quickstart/view/<id>', 'defaults' => array( 'processor' => 'quickstart', 'action' => 'view' ) )
ルートは順番に選択されるため、より具体的なルールが一般的なルールよりも構成内で高いことが重要です。
localhost / quickstart / view / 5に移動すると、応答に「5」が表示されます。
設定で達成できることの簡単な例として、いくつかのルートに共通のプレフィックスをパラメーターで指定しようとします。 複雑に思えても構いませんが、現時点では問題ではありません。
array( // 'type' => 'prefix', // // 'path' => 'user/<userId>/', 'resolver' => array( 'type' => 'group', 'resolvers' => array( // /user/5/friends to Friends::userFriends() 'friends' => array( 'path' => 'friends', 'defaults' => array( 'processor' => 'friends', 'action' => 'usersFriends' ) ), // /user/5/profile to Profile::userProfile() 'profile' => array( 'path' => 'profile', 'defaults' => array( 'processor' => 'profile', 'action' => 'userProfile' ) ) ) ) );
このアプローチにより、より柔軟な設定を取得できるだけでなく(結局、プレフィックスを1箇所で変更するだけで十分であり、ネストされたすべてのルートで変更されます)、パフォーマンスが向上します。プレフィックスが適合しない場合、登録されているすべてのルートがスキップされるためです
入出力
既にお気づきのように、各アクションはパラメーターとしてリクエストを受け取り、何らかのレスポンスを返します。 リクエストからさまざまな情報を取得する方法は次のとおりです。
//$_GET['name'] $request->query()->get('name'); //$_POST['name'] $request->data()->get('name'); // $request->attributes()->get('name');
そして今、もう少し興味深い:
$data = $request->data(); // $data->get('name', 'Trixie'); // // 'name' $data->getRequired('name'); // // $_POST['users']['pixie']['name'] $data->get('users.pixie.name'); // '' // $pixie = $data->slice('users.pixie'); $pixie->get('name'); // $data->get(); // $data->keys(); // // foreach($data as $key => $value) { } // // phpixie/slice //
JSONリクエストは、自動的に$ request-> data()に解析されます。これにより、AJAXリクエストの処理が既に容易になります。
結論はさらに簡単です:
// return 'hello'; // JSON // return array('success' => true); // // HTTP $http = $this->builder->components()->http(); $httpResponses = $http->responses(); // return $httpResponses->redirect('http://phpixie.com/'); // return $httpResponses->stringResponse('Not found', $headers = array(), 404); // return $httpResponses->downloadFile('pixie.jpg', 'image/png', $filePath); // // CSV return $httpResponses->download('report.csv', 'text/csv', $contents);
パターン
PHPixieテンプレートエンジンは、テンプレートの継承、ブロック、およびカスタム拡張機能やフォーマットを簡単に追加する機能をサポートしています。
デフォルトでは、標準バンドルはbundles / app / asset / templatesフォルダーからテンプレートをロードします。
標準のGreetプロセッサで使用される2つのファイルが既に含まれています。
簡単なテンプレートを作成することから始めましょう:
<!-- bundles/app/assets/templates/quickstart/message.php --> <!-- $_() HTML, '<', '>' .. , XSS . --> <p><?=$_($message)?></p>
次に、プロセッサに別のアクションを追加します。
// bundles/app/src/Project/App/HTTPProcessors/Quickstart.php //... public function renderAction($request) { $template = $this->builder->components()->template(); return $template->render( 'app:quickstart/message', array( 'message' => 'hello' ) ); } //... }
リンクlocalhost / quickstart / renderに結果が表示されます
配列ではなくテンプレートにパラメータを動的に渡す場合は、このアプローチも利用できます。
$template = $this->components()->template(); $container = $template->get('app:quickstart/message'); $container->message = 'hello'; return $container->render(); // // return $container;
テンプレートの継承
基本的な親テンプレートを追加する
<!-- bundles/app/assets/templates/quickstart/layout.php --> <h1>Quickstart</h1> <div> <!-- --> <?=$this->childContent();?> </div>
それをmessage.phpで使用します
<!-- bundles/app/assets/templates/quickstart/message.php --> <?php $this->layout('app:quickstart/layout');?> <p><?=$_($message)?></p>
これで、 localhost / quickstart / renderで 、テンプレートはすでに親でラップされています。 ちなみに、親テンプレートはその親を持つこともできます。
親テンプレートのさまざまな場所にコンテンツを置き換えたり追加したりできるブロックを使用すると、より柔軟になります。
<!-- bundles/app/assets/templates/quickstart/layout.php --> <!-- 'header' --> <?php $this->startBlock('header'); ?> <h1>Quickstart</h1> <?php $this->endBlock(); ?> <!-- --> <?=$this->block('header') ?> <div> <!-- --> <?=$this->childContent();?> </div>
次に、message.phpからこのブロックにコンテンツを追加します。
<!-- bundles/app/assets/templates/quickstart/message.php --> <?php $this->layout('app:quickstart/layout');?> <?php $this->startBlock('header'); ?> <h2>Message</h2> <?php $this->endBlock(); ?> <p><?=$_($message)?></p>
デフォルトでは、ブロック内のコンテンツは追加順になっています。 これにより、たとえば、ページに応じてスタイルとスクリプトの接続を簡単に整理できます。 しかし、子がそれ自体を追加していない場合にのみ、親テンプレートにコンテンツをブロックに追加するように指示することもできます。
<!-- bundles/app/assets/templates/quickstart/layout.php --> <?php if($this->startBlock('header', true)): ?> <h1>Quickstart</h1> <?php $this->endBlock(); endif;?> <!-- ... -->
または、たとえば、コンテンツを最後ではなくブロックの最初に追加します。
$this->startBlock('header', false, true);
サブパターン
サブパターンを有効にすることも可能です。
<?php include $this->resolve('some:template');?>
リンク生成
テンプレートにリンクを生成するには、 httpPathとhttpUriの 2つのメソッドがあります 。
<?php $url=$this->httpPath( 'app.default', array( 'processor' => 'hello', 'action' => 'greet' ) ); ?> <a href="<?=$_($url)?>">Hello</a>
データベースとORM
データベース接続は、バンドル外のグローバル構成で記述されます。 たとえば、MySQLへの接続は次のようになります。
// assets/config/database.php return array( 'default' => array( 'driver' => 'pdo', 'connection' => 'mysql:host=localhost;dbname=quickstart', 'user' => 'pixie', 'password' => 'pixie' ) );
PHPixieは、リレーショナルデータベースだけでなく、** MongoDB **もサポートしています。
リレーショナルテーブルとMongoDBコレクション間の関係(1対1、1対多、多対多)を記述し、同じ構文を使用してそれらを操作することもできます。 現在、このレベルの統合をサポートしている他のPHP ORMはありません。
データベースを満たします。 たとえば、タスクで構成されるプロジェクトのトラッカーを作成するとします。 図は次のようになります。
CREATE TABLE `projects`( `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY, `name` VARCHAR(255), `tasksTotal` INT DEFAULT 0, `tasksDone` INT DEFAULT 0 ); CREATE TABLE `tasks`( `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY, `projectId` INT NOT NULL, `name` VARCHAR(255), `isDone` BOOLEAN DEFAULT 0 ); INSERT INTO `projects` VALUES (1, 'Quickstart', 4, 3), (2, 'Build a website', 3, 0); INSERT INTO `tasks` VALUES (1, 1, 'Installing', 1), (2, 1, 'Routing', 1), (3, 1, 'Templating', 1), (4, 1, 'Database', 0), (5, 2, 'Design', 0), (6, 2, 'Develop', 0), (7, 2, 'Deploy', 0);
そして、ORMを使用してこのデータにアクセスできます。 プロセッサにormアクションを追加します。
// bundles/app/src/Project/App/HTTPProcessors/Quickstart.php //... public function ormAction(Request $request) { $orm = $this->builder->components->orm(); $projects = $orm->query('project')->find(); // // . // , // , // JSON return $projects->asArray(true); } //...
localhost / quickstart / ormに移動すると、プロジェクトデータを含むJSON応答が表示されます。
新しいORMの可能性を詳しく調べる前に、1対多の関係を設定します
プロジェクトとそのタスクの間。
// bundles/app/assets/config/orm.php <?php return array( 'relationships' => array( array( 'type' => 'oneToMany', 'owner' => 'project', 'items' => 'task', // // 'itemsOptions' => array( 'onOwnerDelete' => 'delete' ) ) ) );
エンティティ
エンティティの作成、変更、削除は非常に直感的です。
$orm = $this->builder->components->orm(); // $projectRepository = $orm->repository('project'); $project = $projectRepository->create(); // $project = $orm->createEntity('project'); // $project->name = 'Buy Groceries'; $project->save(); $task = $orm->createEntity('task'); $task->name = 'Milk'; $task->save(); // $project->tasks->add($task); // $project->delete(); // $projects = $orm->query('project')->find(); foreach($projects as $project) { foreach($project->tasks() as $task) { //... } }
お問い合わせ
ORMクエリで可能になったもののほんの一部を次に示します。
$orm = $this->builder->components->orm(); // $orm->query('project')->where('name', 'Quickstart')->findOne(); // id $orm->query('project')->in($id)->findOne(); // id $orm->query('project')->in($ids)->findOne(); // $orm->query('project') ->where('tasksTotal', '>', 2) ->or('tasksDone', '<', 5) ->find(); // //WHERE name = 'Quickstart' OR ( ... ) $orm->query('project') ->where('name', 'Quickstart') ->or(function($query) { $querty ->where('tasksTotal', '>', 2) ->or('tasksDone', '<', 5); }) ->find(); // $orm->query('project') ->where('name', 'Quickstart') ->startWhereConditionGroup('or') ->where('tasksTotal', '>', 2) ->or('tasksDone', '<', 5) ->endGroup() ->find(); // , '*' // tasksTotal = tasksDone $orm->query('project') ->where('tasksTotal', '=*', 'tasksDone') ->find(); // $orm->query('project') ->relatedTo('task') ->find(); // $orm->query('project') ->where('tasks.name', 'Routing') ->findOne(); // $orm->query('project') ->orRelatedTo('task', function($query) { $query->where('name', 'Routing'); }) ->findOne(); // // (eager loading) $orm->query('project')->find(array('task')); // $orm->query('project')->update(array( 'tasksDone' => 0 )); // // . // $query = $orm->query('project') ->where('tasksTotal', '=*', 'tasksDone'); $count = $query->count(); $query ->limit(5) ->offset(0) ->find();
PHPixie ORMには、データベースクエリの数を減らすための多くの最適化が含まれています。
たとえば、1つのクエリで複数のタスクをプロジェクトにリンクできます。
$orm = $this->builder->components->orm(); // $projectQuery = $orm->query('project') ->where('name', 'Quickstart'); // 5 $tasksQuery = $orm->query('task')->limit(5); // // $projectQuery->tasks->add($tasksQuery);
エンティティを操作する代わりにクエリを使用すると、クエリの数が劇的に減少します。 これは、多対多の関係で特に顕著です。 MongoDBサポートと同様に、他のORMはこれを許可しません。
エンティティ展開
ほとんどの場合、モデルクラスを拡張して機能を追加する必要があります。 他の実装では、これにより、ORM自体およびデータベースとロジックが密接に接続されます。 PHPixieでは、エンティティを装飾する完全に独立したラッパーを記述することができるため、ORMから完全に独立しています。 これは、テストの記述がはるかに簡単になり、コミットまたはテストデータが必要ないことを意味します。
簡単なラッパーは次のとおりです。
// bundles/app/src/Project/App/ORMWrappers/Project.php; namespace Project\App\ORMWrappers; class Project extends \PHPixie\ORM\Wrappers\Type\Database\Entity { // // public function isDone() { return $this->tasksDone === $this->tasksTotal; } }
バンドルに登録します:
// bundles/app/src/Project/App/ORMWrappers.php; namespace Project\App; class ORMWrappers extends \PHPixie\ORM\Wrappers\Implementation { // protected $databaseEntities = array( 'project' ); // public function projectEntity($entity) { return new ORMWrappers\Project($entity); } }
すべて、今私たちは試すことができます:
//Find the first project $project = $orm->query('project')->findOne(); //Check if it is done $project->isDone();
リクエストとリポジトリのラッパーを宣言して、機能を追加または変更することもできます。 たとえば、一度に複数の条件を追加するメソッドをクエリに追加できます。
もっとあります!
このガイドのコードはgithubで入手できます。 また、完成したデモプロジェクトに精通することもできます。これは単なるタスクトラッカーです。
すべてがすでに私に合っていました!
バンドルシステム、プロセッサへのアプローチ、およびその他の変更が冗長であると思われ、2番目のバージョンのように作業したい場合、心配しないで、同じアプローチをさらに使用できます。
- / bundles / app / folder以外のすべてを忘れる
- バンドルを使用しないため、/ webフォルダーにショートカットを作成せず、2番目のバージョンで行ったように大胆にWebファイルをバンドルに入れます。 後で気が変わってバンドルを使用したい場合は、/ webから/ bundles / app / webにファイルを転送するだけです。 その時まで気にしないでください。
- 標準クラスをわずかに拡張することにより、2番目のバージョンの動作を簡単に実現できます。
HTTPProcessorにプロセッサを登録する必要なく、名前空間に従ってプロセッサを自動的に構築します。
// /bundles/app/src/Project/App/HTTPProcessor.php namespace Project\App; class HTTPProcessor extends \PHPixie\DefaultBundle\Processor\HTTP\Builder { protected $builder; protected $attribute = 'processor'; public function __construct($builder) { $this->builder = $builder; } public function processor($name) { if(!array_key_exists($name, $this->processors)) { $class = '\Project\App\HTTPProcessors\\'.ucfirst($name); if(!class_exists($class)) { return null; } $this->processors[$name] = new $class($this->builder); } return $this->processors[$name]; } }
コントローラーの場合のように、before()およびafter()メソッドを追加します。 追加のボーナスとして、before()が何らかの結果を返す場合、アクション自体は発生しなくなります。
// /bundles/app/src/Project/App/HTTPProcessors\Controller.php namespace Project\App\HTTPProcessors; asbtarct class Controller extends \PHPixie\DefaultBundle\Processor\HTTP\Actions { protected $builder; protected $attribute = 'processor'; public function __construct($builder) { $this->builder = $builder; } public function process($request) { $result = $this->before($request); if($result !== null) { return $result; } $result = parent::process($request); return $this->after($request, $result); } public function before($request) { } public function after($request, $result) { return $result; } }
終わり
これで、PHPixie 3での作業を開始するためのすべての準備が整いましたが、サイト上とここハブの両方に未読のドキュメントがまだたくさんあります ほとんどのコンポーネントには、更新されたサイトまたはロシア語版の独自のドキュメントが既にあります。 すべてのコンポーネントはフレームワークなしで使用でき、テストで100%カバーされており、ほとんどすべてがCodeClimateの最大品質メトリックを備えています。
ちなみに、フレームワークサイトも更新され、より真剣なデザインになったため、チームリーダーがその上にだけ書くように説得しやすくなりました。 また、まだ質問がある場合は、チャットでほぼ常に確認できます。