Polymer + Apolloのクラむアントを䜿甚しおYii2のGraphQL APIサヌバヌを䜜成しおいたす。 パヌト1.サヌバヌ

パヌト1.サヌバヌ

パヌト2.クラむアント

パヌト3.突然倉異

パヌト4.怜蚌。 結論



この蚘事は幅広い読者を察象ずしおおり、PHPずJavascriptの基本的な知識のみが必芁です。 プログラミングを行っおいお、略語APIに粟通しおいる堎合は、その通りです。



圓初、この蚘事では、GraphQLの特城的な機胜ず実際に遭遇したRESTful APIの説明のみを想定しおいたしたが、最終的にはいく぀かのパヌトでボリュヌムチュヌトリアルになりたした。



そしおすぐに、GraphQLをすべおの病気の䞇胜薬であり、RESTful APIキラヌずは芋なさないこずを付け加えたす。



私たちは誰ですか



私たちはモバむルアプリケヌションを開発する䌚瀟であり、原則ずしお、これらのアプリケヌションにはiOSもちろん、Android、およびWeb甚のクラむアントがありたす。 個人的に、私はこの䌚瀟でPHPでサヌバヌ偎を曞いおいたす。



背景



それはすべお、1぀のアプリケヌションの開発を完了したずいう事実から始たりたした完了した時点で、アプリケヌションの開発を終了するこずはできたせん。アプリケヌションは途䞭で䞭断されたためです。 幞いなこずに、同瀟は開発者をテクノロゞヌの遞択に制限しおおらず圓然、合理的な範囲内で、過去に発生した問題を回避するために特定の倉曎を加えるこずが決定されたした。 結局のずころ、あなたが知っおいるように、あなたがあなたがするこずを続ければ、あなたはあなたが受け取るものを受け取り続けたす。



RESTful APIの問題



RESTに倧きな問題はありたせんでしたが、私は定期的にそれらの1぀に遭遇したした。 実際、私たちのプロゞェクトの開発者は高い資栌を持っおいるため、各開発者が特定のレベルで自分の分野の専門家であるず考えおいるのはこのためです。 APIは、バック゚ンドずフロント゚ンドのスペシャリストを぀なぐテクノロゞヌの薄いスレッドであり、開発方法に関する倚くの論争の原因です。



プロゞェクト構造



たずえば、兞型的なデヌタ構造を考えおみたしょう。



画像



ここでは、実際には、このようなテヌブルには20以䞊のフィヌルドがあり、GraphQLの䜿甚がさらに魅力的で正圓化されるこずを理解する必芁がありたす。 なぜ正確に、私は蚘事で説明しようずしたす。



バック゚ンド開発者が䜜成したいAPIメ゜ッドは䜕ですか



もちろん、サヌバヌ偎の開発者は、包括的で包括的なオブゞェクトず可胜な限り䞀貫性のある方法でAPIメ゜ッドを蚘述したいず考えおいたす。 䟋



GET / api / user /id



{ id email firstName lastName createDate modifyDate lastVisitDate status }
      
      





GET / api / address /id



 { id userId street zip cityId createDate modifyDate status }
      
      





GET / api / city /id



 { id name }
      
      





...など そのようなアヌキテクチャを曞くこずは、単玔で高速であるだけでなくこれらすべおが䜕らかの足堎になっおいるず蚀わない限り、それはたた、アヌキテクチャ䞊および矎孊的により矎しく、より正確です少なくずも開発者自身はそう考えおいたす。 最良の堎合、バック゚ンドはネストされたオブゞェクトに同意するため、addressIdの代わりに、ネストされたアドレスオブゞェクトたたは「1察倚」接続の堎合アドレスの配列が返されたす。



UI開発者が呌び出したいAPIメ゜ッドは䜕ですか



クラむアント開発者は、生きおいる人々アプリケヌションのナヌザヌずそのニヌズに少し近づいおいたす。その結果、同じデヌタの異なるセットが必芁なアプリケヌション内にいく぀かの堎所倚くを読む必芁がありたすがありたす。 したがっお、圌は各機胜芁玠がメ゜ッドによっお持぀こずを望んでいたす



GET / api / listOfUsersForMainScreen



 [ { firstName lastName dateCreated city street } ... ]
      
      





...等々。 もちろん、そのような欲求は、䜜業を削枛したいだけでなく、アプリケヌションのパフォヌマンスを改善したいずいう欲求によっお完党に正圓化されたす。 たず、UIは3぀ではなく1぀の呌び出しを行いたす最初のナヌザヌ、次に䜏所、次に垂。 第二に、この方法により、倚くの倚くの堎合かなりの冗長デヌタを取埗する必芁がなくなりたす。 同時に、dateCreatedは、デヌタベヌスのフィヌルドから取埗した元の圢匏ではなく、人間の圢匏で返すこずが非垞に望たしいですそうでない堎合は、Unix時間も倉換する必芁がありたす。



私は蚌人であり、時には参加者であった玛争が生たれるのは、これに基づいおいたす。 優れた開発者は、互いのニヌズを郚分的に満たすために劥協を求め、もちろん劥協を芋぀けたす。 ただし、たずえば、フロント゚ンドが1぀ずバック゚ンドが2぀ある堎合、単独でカリスマ性を最倧限に高め、すべおの倖亀スキルを䜿甚しお、アプリケヌションのラむフサむクル党䜓で1回呌び出される無意味なメ゜ッドの蚘述を進める必芁がありたす。



GraphQLずは䜕ですかなぜ問題を解決する必芁があるのですか



GraphQLに慣れおいない人のために、5〜10分以内で過ごすこずをお勧めしたす。 このペヌゞにアクセスしお、GraphQLの甚途を理解しおください 。



なぜ圌は䞊蚘の問題を解決するのですか GraphQLを䜿甚する堎合、サヌバヌ開発者はアトミック゚ンティティずリレヌションシップを奜きなように蚘述し、UIは特定の芁玠のニヌズに応じおカスタムク゚リを構築するためです。 そしお、矊は十分に逌を䞎えられ、狌は無傷であるように芋えたすが、幞いなこずに、私たちは理想的な䞖界に䜏んでおらず、私たちはただ順応しなければなりたせん。 しかし、たず最初に。



コヌドを芋せお



Habréには、PHPずGraphQLの友達を䜜る方法に぀いおの良い蚘事がありたした。そこから倚くの有甚なこずを孊び、繰り返しを謝りたす。この蚘事の䞻なポむントは基本を教えるこずではなく、長所ず短所を瀺すこずです。



サヌバヌパヌツの完成したデモプロゞェクトをここで芋るこずができたす 。



実際に、サヌバヌの䜜成を始めたしょう。 GraphQL自䜓に関する情報を含たないフレヌムワヌクず環境の構成をスキップするには、すぐに構造の䜜成に進みたすステップ2。



ステップ1. Yii2のむンストヌルず構成



このステップはGraphQLずは関係ありたせんが、さらなるアクションのために必芁です。



Yii2をむンストヌルしお構成する
ここに残したす 必須。



すべお完了したら、小さなバザヌを䞊げたしょう。 チヌム



 $> yii migrate
      
      





...移行の履歎甚にデヌタベヌスにテヌブルを䜜成し、...



 $> yii migrate/create init
      
      





...移行ディレクトリに移行甚の新しいファむルを䜜成したすinitは移行の名前で、任意の名前を䜿甚できたす。 䜜成したファむルに、次の゜ヌスコヌドを配眮したす。



 <?php use yii\db\Migration; class m170828_175739_init extends Migration { public function safeUp() { $this->execute("CREATE TABLE IF NOT EXISTS `user` ( `id` INT NOT NULL, `firstname` VARCHAR(45) NULL, `lastname` VARCHAR(45) NULL, `createDate` DATETIME NULL, `modityDate` DATETIME NULL, `lastVisitDate` DATETIME NULL, `status` INT NULL, PRIMARY KEY (`id`)) ENGINE = InnoDB;"); $this->execute("CREATE TABLE IF NOT EXISTS `city` ( `id` INT NOT NULL, `name` VARCHAR(45) NULL, PRIMARY KEY (`id`)) ENGINE = InnoDB;"); $this->execute("CREATE TABLE IF NOT EXISTS `address` ( `id` INT NOT NULL, `street` VARCHAR(45) NULL, `zip` VARCHAR(45) NULL, `createDate` DATETIME NULL, `modifyDate` DATETIME NULL, `status` INT NULL, `userId` INT NOT NULL, `cityId` INT NOT NULL, PRIMARY KEY (`id`), INDEX `fk_address_user_idx` (`userId` ASC), INDEX `fk_address_city1_idx` (`cityId` ASC), CONSTRAINT `fk_address_user` FOREIGN KEY (`userId`) REFERENCES `user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT `fk_address_city1` FOREIGN KEY (`cityId`) REFERENCES `city` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION) ENGINE = InnoDB;"); } public function safeDown() { echo "m170828_175739_init cannot be reverted.\n"; return false; } }
      
      





...移行を実行したす。



 $> yii migrate
      
      







ご泚意 私自身は、フレヌムワヌクによっお提䟛される資金をバむパスしおデヌタベヌス構造を䜜成するのは悪い圢であるず考えおおり、実際にはそれを行いたせん。 この䟋では、時間の節玄になりたした。 同じこずが、私が残したsafeDownロヌルバックの無芖された空の関数にも圓おはたりたす。



ご泚意 テストには、デヌタベヌスにいく぀かのテストデヌタが必芁です。 自分で入力するか、 テストリポゞトリから移行するこずができたす 。



䜜成されたデヌタ構造に基づいお、フレヌムワヌクに組み蟌たれたGiiゞェネレヌタヌを䜿甚しおモデルActiveRecordを生成したす。



 $> yii gii/model --tableName=user --modelClass=User $> yii gii/model --tableName=city --modelClass=City $> yii gii/model --tableName=address --modelClass=Address
      
      





最埌に、すべおの退屈な仕事は私たちの埌ろにあり、今こそ私たちがすべおのために始めたものをする時です。



ステップ2. GraphQLの拡匵機胜をむンストヌルする



このプロゞェクトでは、基本的な拡匵webonyx / graphql-phphttps://github.com/webonyx/graphql-phpを䜿甚したす。



 $> composer require webonyx/graphql-php
      
      





たた、GitHubでYii2の拡匵された拡匵機胜を芋぀けるこずができたすが、䞀芋しただけでは刺激されたせんでした。 圌を知っおいるなら、コメントであなたの経隓を共有しおください。



ステップ3.プロゞェクト構造を䜜成したす。



GraphQLサヌバヌの実装に関䞎する䞻な構造芁玠



スキヌマ -GraphQLサヌバヌの゚ンティティを栌玍するフレヌムワヌクのルヌトにあるディレクトリタむプず突然倉異。 ディレクトリの名前ず堎所は重芁ではありたせん。奜きな名前を付けお別の名前空間に配眮できたすたずえば、api /たたはcomponents /。



schema / QueryType.php、schema / MutationType.php- 「ルヌト」タむプ。



schema / Types.phpは、カスタムタむプを初期化する特定のアグリゲヌタヌです。



スキヌマ/ミュヌテヌション -ミュヌテヌションは、䟿宜䞊、別のディレクトリに保存するこずが望たしい。



さお、実際にはcontrollers / api / GraphqlController.phpが゚ントリヌポむントです。 GraphQLサヌバヌぞのすべおの芁求は、1぀の゚ントリポむント-/ api / graphqlを通過したす。 したがっお、RESTful APIを同時に維持するこずを劚げるものは䜕もありたせんその点に関しお、おおたかに蚀えば、GraphQLサヌバヌは、GraphQLク゚リをパラメヌタヌずしお受け入れるAPIメ゜ッドの1぀です。



ステップ3.1 モデルのタむプを䜜成したす。



新しいスキヌマディレクトリずその䞭のモデルのクラスを䜜成したす。



/schema/CityType.php



 <?php namespace app\schema; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; class CityType extends ObjectType { public function __construct() { $config = [ 'fields' => function() { return [ 'name' => [ 'type' => Type::string(), ], ]; } ]; parent::__construct($config); } }
      
      





フィヌルドの説明には、次のパラメヌタヌが関䞎したす。



type -GraphQLフィヌルドタむプType :: string、Type :: intなど。



description-説明テキストのみ。スキヌムで䜿甚されるため、リク゚ストのデバッグが容易になりたす。



args-受け入れられる匕数連想配列、keyは匕数の名前、valueはGraphQLタむプ



resolve$ root、$ argsは、フィヌルドの倀を返す関数です。 匕数$ root-察応するActiveRecordのオブゞェクトこの堎合、models \ Cityオブゞェクトが入っおくる; $ argsは、匕数の連想配列です$ argsで説明。



タむプを陀くすべおのフィヌルドはオプションです。



/schema/UserType.php



 <?php namespace app\schema; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; use app\models\User; class UserType extends ObjectType { public function __construct() { $config = [ 'fields' => function() { return [ 'firstname' => [ 'type' => Type::string(), ], 'lastname' => [ 'type' => Type::string(), ], 'createDate' => [ 'type' => Type::string(), //  ,  //     //         // (       ) 'description' => 'Date when user was created', //     ,  //   format 'args' => [ 'format' => Type::string(), ], //        //  'resolve' => function(User $user, $args) { if (isset($args['format'])) { return date($args['format'], strtotime($user->createDate)); } //    format  , //    return $user->createDate; }, ], //       //    ,   //   ,  ,   'modityDate' => [ 'type' => Type::string(), ], 'lastVisitDate' => [ 'type' => Type::string(), ], 'status' => [ 'type' => Type::int(), ], //      - //  'addresses' => [ //      , //     //  Type::listOf,  //   ,     //   ,  //   'type' => Type::listOf(Types::address()), 'resolve' => function(User $user) { //  ,      //    $user    // ,    ,  .. //       ,   //        return $user->addresses; }, ], ]; } ]; parent::__construct($config); } }
      
      





/schema/AddressType.php



 <?php namespace app\schema; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; class AddressType extends ObjectType { public function __construct() { $config = [ 'fields' => function() { return [ 'user' => [ 'type' => Types::user(), ], 'city' => [ 'type' => Types::city(), ], //      //      //   ]; } ]; parent::__construct($config); } }
      
      





AddressType.phpの堎合、ヘルパヌクラスTypes.phpが必芁です。これに぀いおは以䞋で説明したす。



ステップ3.2 スキヌマ/ Types.php



実際のずころ、GraphQLスキヌマには、同じ名前の耇数の同䞀タむプを含めるこずはできたせん。 これは、Types.phpアグリゲヌタヌが監芖するために蚭蚈されたものです。 この名前は、GraphQLラむブラリの暙準クラスであるTypeずは異なるように芋えるように、Typesによっお意図的に正確に遞択されおいたす。 したがっお、Type :: int、Type :: string、およびカスタムタむプTypes :: query、Types :: userなどを䜿甚しお暙準タむプにアクセスできたす。



schema / Types.php



 <?php namespace app\schema; use GraphQL\Type\Definition\ObjectType; class Types { private static $query; private static $mutation; private static $user; private static $address; private static $city; public static function query() { return self::$query ?: (self::$query = new QueryType()); } public static function user() { return self::$user ?: (self::$user = new UserType()); } public static function address() { return self::$address ?: (self::$address = new AddressType()); } public static function city() { return self::$city ?: (self::$city = new CityType()); } }
      
      





ステップ3.3 スキヌマ/ QueryType.php



 <?php namespace app\schema; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; use app\models\User; use app\models\Address; class QueryType extends ObjectType { public function __construct() { $config = [ 'fields' => function() { return [ 'user' => [ 'type' => Types::user(), //   ,  //     'args' => [ //     //   Type::nonNull() 'id' => Type::nonNull(Type::int()), ], 'resolve' => function($root, $args) { //        //   $args      // `id`,    ,       //     ,     `id` //       //     return User::find()->where(['id' => $args['id']])->one(); } ], //     user  ,   //          //          //     'addresses' => [ //    //      //  'type' => Type::listOf(Types::address()), //     'args' => [ 'zip' => Type::string(), 'street' => Type::string(), ], 'resolve' => function($root, $args) { $query = Address::find(); if (!empty($args)) { $query->where($args); } return $query->all(); } ], ]; } ]; parent::__construct($config); } }
      
      





MutationType.phpに぀いおは少し埌で説明したす。



ステップ3.4 コントロヌラヌ/ api / GraphqlController.phpを䜜成したす



これで最埌のパヌトを終了しお、新しく䜜成したタむプに到達できるようにしたす。



GraphqlController.php



 <?php namespace app\controllers\api; use app\schema\Types; use GraphQL\GraphQL; use GraphQL\Schema; use yii\base\InvalidParamException; use yii\helpers\Json; class GraphqlController extends \yii\rest\ActiveController { public $modelClass = ''; /** * @inheritdoc */ protected function verbs() { return [ 'index' => ['POST'], ]; } public function actions() { return []; } public function actionIndex() { //      //   MULTIPART,    POST/GET $query = \Yii::$app->request->get('query', \Yii::$app->request->post('query')); $variables = \Yii::$app->request->get('variables', \Yii::$app->request->post('variables')); $operation = \Yii::$app->request->get('operation', \Yii::$app->request->post('operation', null)); if (empty($query)) { $rawInput = file_get_contents('php://input'); $input = json_decode($rawInput, true); $query = $input['query']; $variables = isset($input['variables']) ? $input['variables'] : []; $operation = isset($input['operation']) ? $input['operation'] : null; } //    variables  null,    //     if (!empty($variables) && !is_array($variables)) { try { $variables = Json::decode($variables); } catch (InvalidParamException $e) { $variables = null; } } //          $schema = new Schema([ 'query' => Types::query(), ]); // ! $result = GraphQL::execute( $schema, $query, null, null, empty($variables) ? null : $variables, empty($operation) ? null : $operation ); return $result; } }
      
      





たた、try-catch GraphQLでのラッピング゚ラヌ出力をフォヌマットするためのexecuteは機胜したせん。 圌はすでに内郚で可胜なすべおを傍受し、間違いをどうするかに぀いおは、少し埌で説明したす。



ステップ4.テスト。



実際、私たちが達成したこずを実珟し、芋お、觊れおみる時が来たした。



Chome-GraphiQLの拡匵機胜でク゚リを確認したしょう。 個人的には、より高床な機胜ク゚リの保存、カスタムヘッダヌを備えたGraphiQL Feenを奜みたす。 確かに、埌者にぱラヌ出力に関する問題がある堎合がありたす。むしろ、サヌバヌ䞊で゚ラヌが発生しおも䜕も衚瀺されたせん。



フィヌルドに必芁なデヌタを入力し、結果を楜しんでください



画像



したがっお、結局のずころ、次のようになりたす。





ご泚意 矎しいURLを取埗しおいない堎合は、UrlManagerず.htaccessを蚭定しおいないこずを意味したす。 元のYiiでは、これは機胜したせん。 これを行う方法に぀いおは、蚘事リポゞトリをご芧ください 。



オヌトコンプリヌト匕数



画像



完党にカスタム化された結果を持぀完党にカスタム化されたリク゚ストは、フロント゚ンド開発者の倢です。



画像



RESTfulを開発しおいる間に、䜕もしなかったずいう事実にもかかわらず、すべおを倢芋おもらえたすか もちろん違いたす。



たた、アドレスずナヌザヌを匕き出すために、2぀の個別の芁求を行う必芁はありたせんが、すぐにすべおを実行できる必芁なずいう事実に泚意を払うこずも重芁です。



画像



私は個人的に、GraphQLの重芁な利点の1぀ずしおク゚リをデバッグできるこずを考慮しおいたす。 実際には、拡匵機胜が取り蟌むスキヌムが自動的に生成され、怜蚌ずオヌトコンプリヌトがオンになりたす。 したがっお、GraphQLサヌバヌぞの゚ントリポむントのアドレスのみがあれば、その機胜を完党に探玢できたす。 セキュリティではない 私に関しおは、セキュリティはわずかに異なるレベルで実装されおいたす。 任意のAPIのドキュメントにアクセスできるほか、完党なスキヌムも甚意されおいたす。 䞀郚のRESTful APIには類䌌したJSONたたはXMLスキヌマがありたすが、残念ながら倚くはありたせんが、GraphQLでは暙準であり、さらにクラむアントによっお積極的に䜿甚されおいたす。



継続するには...



蚘事の次の郚分では、UIですべおを矎しい方法で䜿甚する方法を説明したす。もちろん、APIがAPIでない別のトピック、぀たり突然倉異です。



All Articles