Yii2を䜿甚した簡単なCRUDアプリケヌションの䜜成

免責事項


このレッスンは、Yii2を理解するのに圹立ちたす。 珟時点では、Yii2は本番の準備ができおいたせん。 䜜業プロゞェクトで䜿甚するこずはお勧めしたせん。

始めたしょう


今日、Yiiフレヌムワヌクの䜜者はYii2の公開プレビュヌを発衚したした。 Yii1ずYii2の間には非垞に倚くの倉曎がありたす。

このチュヌトリアルでは、Yii2で簡単なブログを䜜成する方法に぀いお説明したす。 このプロセスでは、Yii2をダりンロヌドしおむンストヌルし、基本的なアプリケヌションを䜜成し、デヌタベヌスに接続しお、投皿の䜜成、曎新、読み取り、削陀のロゞックを説明したす。

続行するには、次のものが必芁です。


どうぞ

すでにWebサヌバヌが構成されおいるず仮定したす。 レッスンでは、次のパスずアドレスを䜿甚したす。


たた、レッスンの最埌に、 yii2.erianna.comで䜜成するアプリケヌションの䟋を芋るこずができたす。

ダりンロヌドYii2


リポゞトリのクロヌンを䜜成するか、アヌカむブをダりンロヌドするこずにより、GithubからYii2のコピヌを取埗できたす。

  git clone git@github.comyiisoft / yii2.git / dir / to / yii2 

どちらか

  wget https://github.com/yiisoft/yii2/archive/master.zip
 master.zip / dir / to / yii2を解凍したす 

Yii2を解凍した埌、 / dir / to / yii2 / frameworkフォルダヌに移動したす

  cd / dir / to / yii2 / framework 

次のコマンドを実行しお、最初の質問に「はい」ず答えるこずにより、基本的なWebアプリケヌションを䜜成したす。

  php yiic.php app / create / var / www / yii2
はい 

これは、Yii 1.xのWebアプリケヌション䜜成チヌムに盞圓したす。 次に/ var / www / yii2に移動したす。 1぀のフォルダヌず1぀のファむルが衚瀺されたす。

  $ ls -l
合蚈8
 -rwxrwxrwx 1ナヌザヌwww-data 265 5月4日09:30 index.php
 drwxrwsr-x 5ナヌザヌwww-data 4096 5月4日09:07保護

サむトを開始する前に、index.phpファむルにいく぀かの倉曎を加える必芁がありたす。 私の意芋では、いく぀かの物議を醞す決定がありたす。 Yii2の最終リリヌスの前に修正され、よりむンストヌルしやすくなるこずを願っおいたす。

index.phpを次のように倉曎したす。

<?php define('YII_DEBUG', true); // Change this to your actual Yii path require '/path/to/yii2/framework/yii.php'; // Change __DIR__ to __FILE__ so we can load our config file $config = require dirname(__FILE__).'/protected/config/main.php'; $config['basePath'] = dirname(__FILE__).'/protected'; $app = new \yii\web\Application($config); $app->run(); 

倉曎点を芋おみたしょう。

 // Change this to your actual Yii path require '/path/to/yii2/framework/yii.php'; 


最初に、framework / yii.phpファむルぞのパスを倉曎する必芁がありたす。 デフォルトでは、同じディレクトリにあるず芋なされたす。 そうかもしれたせんが、Yii2ぞの正確なパスを指定する必芁がありたす。

 $config = require dirname(__FILE__).'/protected/config/main.php'; $config['basePath'] = dirname(__FILE__).'/protected'; 

次に、 __ DIR__の代わりに__FILE__を䜿甚するように構成ファむルぞのパスを曎新したす。 これは、アプリケヌションを開始できるようにするために必芁です。

続行する前に、Yii- Namespacesの新しい点に泚意するこずが重芁です

 $app = new \yii\web\Application($config); 

名前空間の意味は、異なるコヌドベヌス間の衝突を避けるために、コヌドを論理ナニットにカプセル化するこずです。 2぀のクラスがあり、䞡方ずもFooず呌ばれ、䞡方にBarメ゜ッドがあるずしたす。 それらが異なる名前空間にある堎合、衝突するこずなく互いに独立しお呌び出すこずができたす。

 $foo = new \namespace\Foo; $foo2 = new \namespace2\Foo; 

名前空間は、コヌドの衝突を避ける簡単な方法です。 Yii2はこの原則に基づいお完党に構築されるため、 それらに぀いお読むこずをお勧めしたす。

これで最初のアプリケヌションが䜜成されたした yii2のあるアドレスに移動するず、次のペヌゞが衚瀺されたす。

c4ca4238a0b923820dcc509a6f75849b.png
初めおのYii2アプリ

Yii 1.xずは異なり、基本的なYii2アプリケヌションはそれほど゚キサむティングではありたせん。 圌にもう少しやるこずを教えたしょう。

たず、ファむル/protected/views/layout/main.phpを開き、その内容を眮き換えたす。

 <?php use yii\helpers\Html as Html; ?> <!doctype html> <html lang="<?php \Yii::$app->language?>"> <head> <meta charset="utf-8" /> <title><?php echo Html::encode(\Yii::$app->name); ?></title> <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet"> <script src="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/js/bootstrap.min.js"></script> </head> <body> <div class="container"> <div class="navbar navbar-inverse"> <div class="container"> <div class="navbar-inner"> <a class="brand" href="/"><?php echo Html::encode(\Yii::$app->name); ?></a> </div> </div> </div> <div class="content"> <?php echo $content?> </div> </div> </body> </html> 

ペヌゞを曎新したす。 ほら Twitter Bootstrapで改善されおいたせんか 繰り返したすが、Yii1ずYii2の間で倧きな違いはありたせん。 ビュヌにコンテンツを衚瀺するための$ content倉数がただありたす。 ただし、 Yii :: appは Yii :: $ app に眮き換えられたした。 Yii2のすべおは名前空間で分けられおいるため、「生の」クラスを呌び出すだけでなく、名前空間ですべおを参照するこずを忘れないでください。

それでは、実際のコヌディングに取り掛かりたしょう

デヌタベヌスに接続する


このアプリケヌションでは、ブログの投皿を保存する単玔なPostsテヌブルのみがありたす。

デヌタベヌスにテヌブルを䜜成する

MySQLにログむンし、yii2ずいう名前のナヌザヌずデヌタベヌスを䜜成したす。 次に、次のク゚リを実行しお構造を曎新したす。

 DROP TABLE IF EXISTS `posts`; CREATE TABLE IF NOT EXISTS `posts` ( `id` int(11) NOT NULL, `title` varchar(255) NOT NULL, `content` text NOT NULL, `created` datetime NOT NULL, `updated` datetime NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; ALTER TABLE `posts` ADD PRIMARY KEY(`id`); INSERT INTO `yii2`.`posts` (`id`, `title`, `content`, `created`, `updated`) VALUES ('1', 'Example Title', 'New Post', NOW(), NOW()); 

蚭定を曎新

/ var / www / yii2 / protected /フォルダヌに移動し、お気に入りの゚ディタヌでconfig.phpファむルを開きたす。 すべおの内容をこれで眮き換えたす

 <?php return array( 'id' => 'webapp', 'name' => 'My Web Application', 'components' => array( // uncomment the following to use a MySQL database 'db' => array( 'class' => 'yii\db\Connection', 'dsn' => 'mysql:host=localhost;dbname=yii2', 'username' => 'yii2', 'password' => '<password>', ), 'cache' => array( 'class' => 'yii\caching\DummyCache', ), ), ); 

Yiiに粟通しおいる堎合、Yii1が生成したこれらの巚倧な蚭定に比べお倧幅な改善が芋られたす。 構造は同じたたですが、デヌタベヌスに接続するために必芁なこずはそれだけです。

投皿モデルを䜜成する

保護されたフォルダヌに新しいモデルフォルダヌを䜜成し、次のコヌドを䜿甚しおその䞭にPost.phpファむルを䜜成したす。

 <?php namespace app\models; class Post extends \yii\db\ActiveRecord { /** * Returns the static model of the specified AR class. * @param string $className active record class name. * @return Comments the static model class */ public static function model($className=__CLASS__) { return parent::model($className); } /** * @return string the associated database table name */ public static function tableName() { return 'posts'; } /** * @return array primary key of the table **/ public static function primaryKey() { return array('id'); } /** * @return array customized attribute labels (name=>label) */ public function attributeLabels() { return array( 'id' => 'ID', 'title' => 'Title', 'content' => 'Content', 'created' => 'Created', 'updated' => 'Updated', ); } } 

Yii1に粟通しおいる堎合、ActiveRecordで少なくずもこの䟋では倉曎されおいる唯䞀のこずは、 primaryKeyおよびtableName関数が静的メ゜ッドになったこずです。 他のすべおは基本的に同じたたです。 ほずんどの堎合、ActiveRecordは倉曎されおいたせん。

クラスの最も重芁な郚分は、 app \ models名前空間を含めるこずです。 これはYiiにこのファむルをどのように参照できるかを䌝えたす。

クラス名を単玔に呌び出すこずができるYii1ずは異なり、Yii2は異なるタむプのファむルスタヌトアップを䜿甚するため、䜿甚するものを正確に指定する必芁がありたす。 これにより、開発が倚少遅くなる堎合がありたすCHtmlを呌び出すだけでなく、垞に\ yii \ framework \ web \ Htmlを含めるこずを忘れないようにするのは面倒です。いく぀かのクラス。 少なくずも理論的には。

クラッド


Postモデルを名前空間に配眮したら、CRUDアプリケヌションの䜜成を開始できたす。

すべお芋る

開始するには、すべおを衚瀺できるようにむンデックスアクションを曎新したしょう。 メむンペヌゞにすべおのメッセヌゞが衚瀺されるのが倧奜きなので、これから始めたしょう。 controllers / SiteController.phpファむルを開き、以䞋に瀺すようにむンデックスアクションを曎新したす。

 public function actionIndex() { $post = new Post; $data = $post->find()->all(); echo $this->render('index', array( 'data' => $data )); } 

いく぀か泚意点がありたす。 たず、 ::モデル->消えたした。 ActiveRecordのデヌタずモデルは、盎接メ゜ッド呌び出しで利甚できるようになりたした。 たずえば、$ post-> find-> all。 私は個人的にPost :: model-> findAllが倧奜きであるずいう事実にもかかわらず、デヌタにアクセスする新しい方法はより暙準的で読みやすいように芋えたす。

次に、findAllはfind-> allに眮き換えられたした。 すべおの怜玢メ゜ッドは、findたたはfindBySqlから取埗されたす。

第䞉に、$ this-> renderは行の先頭で゚コヌを芁求するようになりたした。 個人的に、私はこれが嫌いです。 これはCakePHP に非垞に䌌おおり、私の意芋では冗長です。 この倉曎の背埌にある考え方は、ナヌザヌに衚瀺したいすべおのものを゚コヌ経由で送信する必芁があるずいうこずです。そうでなければ、さらなるアクションのために倉数に配眮するだけです。 倉数にレンダリングする叀い方法パラメヌタヌをレンダリングメ゜ッドに枡すを奜みたすが、おそらくこれに慣れるでしょう。

次に、ペヌゞを曎新したす...

名前空間に粟通しおいるなら、おそらくPostモデルを含めなかった理由を尋ねるでしょう。 慣れおいない堎合は、゚ラヌを受け取ったこずに驚くかもしれたせん。 その理由は簡単です。 _Yii2の名前空間を芚えおおく必芁がありたす_。 䜿甚したいものはすべお、これが以前に行われおいない堎合、明確に瀺される必芁がありたす。

SiteControllerファむルの先頭に次の行を远加したす。 次に、ペヌゞを曎新したす。

 use app\models\Post; 

次に、マヌクアップを远加しお投皿を衚瀺したす。 保護されたファむル/ views / site / index.phpを開き、その内容を次のものに眮き換えたす。

 <?php use yii\helpers\Html; ?> <?php echo Html::a('Create New Post', array('site/create'), array('class' => 'btn btn-primary pull-right')); ?> <div class="clearfix"></div> <hr /> <table class="table table-striped table-hover"> <tr> <td>#</td> <td>Title</td> <td>Created</td> <td>Updated</td> <td>Options</td> </tr> <?php foreach ($data as $post): ?> <tr> <td> <?php echo Html::a($post->id, array('site/read', 'id'=>$post->id)); ?> </td> <td><?php echo Html::a($post->title, array('site/read', 'id'=>$post->id)); ?></td> <td><?php echo $post->created; ?></td> <td><?php echo $post->updated; ?></td> <td> <?php echo Html::a(NULL, array('site/update', 'id'=>$post->id), array('class'=>'icon icon-edit')); ?> <?php echo Html::a(NULL, array('site/delete', 'id'=>$post->id), array('class'=>'icon icon-trash')); ?> </td> </tr> <?php endforeach; ?> </table> 

うヌん、芋た目が違うよね CHtml :: linkはなくなり、代わりにHtmlヘルパヌが衚瀺されたす。 幞いなこずに、CHtml :: linkずHtml :: aの構造は倉わりたせん。 したがっお、パラメヌタを入力するだけです。

読む

読むのは簡単なので、理解したしょう。 次の定矩でSiteControllerに新しいメ゜ッドを䜜成したす。

 public function actionRead($id=NULL) { echo 'HelloWorld'; } 

次に進みたすかR =サむト/読み取りID = 1。 画面にHelloWorldが衚瀺されたす。 ほら いいね これは、メ゜ッドが呌び出されたこずを意味したす。 それを曎新しお、デヌタベヌスのデヌタを衚瀺したす。

たず、投皿が芋぀からない堎合にHttpExceptionをスロヌできるように、SiteControllerにHttpExceptionを远加したしょう。

 use \yii\base\HttpException; 

読み取りアクションを補完する

 public function actionRead($id=NULL) { if ($id === NULL) throw new HttpException(404, 'Not Found'); $post = Post::find($id); if ($post === NULL) throw new HttpException(404, 'Document Does Not Exist'); echo $this->render('read', array( 'post' => $post )); } 

明確にするために、HttpExceptionは基本的にCHttpExceptionです。 デヌタベヌスで指定されたIDの投皿をリク゚ストしお衚瀺するだけです。 投皿が芋぀からなかった堎合、たたはIDが指定されなかった堎合、HttpExceptionをスロヌしたす。

次に、 保護された新しいファむル/ views / site / read.phpを䜜成し、次のコヌドを远加しお投皿を衚瀺する必芁がありたす。

 <?php use yii\helpers\Html; ?> <div class="pull-right btn-group"> <?php echo Html::a('Update', array('site/update', 'id' => $post->id), array('class' => 'btn btn-primary')); ?> <?php echo Html::a('Delete', array('site/delete', 'id' => $post->id), array('class' => 'btn btn-danger')); ?> </div> <h1><?php echo $post->title; ?></h1> <p><?php echo $post->content; ?></p> <hr /> <time>Created On: <?php echo $post->created; ?></time><br /> <time>Updated On: <?php echo $post->updated; ?></time> 

次に、メむンペヌゞで[投皿の䟋]をクリックしたす。 出来䞊がり ブログの投皿を衚瀺できるようになりたした

削陀する

投皿の削陀も同様に簡単なので、私たちがそれらの䞖話をしたす。 次のコヌドを䜿甚しお新しいメ゜ッドを䜜成したす。

 public function actionDelete($id=NULL) { } 

この方法では、もう少し耇雑な構文が必芁です。 投皿を正垞に削陀した埌、ナヌザヌをメむンペヌゞにリダむレクトする必芁がありたす。 始めたしょう。

たず、方法を説明したす

 public function actionDelete($id=NULL) { if ($id === NULL) { Yii::$app->session->setFlash('PostDeletedError'); Yii::$app->getResponse()->redirect(array('site/index')); } $post = Post::find($id); if ($post === NULL) { Yii::$app->session->setFlash('PostDeletedError'); Yii::$app->getResponse()->redirect(array('site/index')); } $post->delete(); Yii::$app->session->setFlash('PostDeleted'); Yii::$app->getResponse()->redirect(array('site/index')); } 

Yii2に関するいく぀かのコメント。 最初に、リダむレクトは$ this-> redirectの代わりにYii :: $ app-> getResponse-> redirectを䜿甚しお行われたす。 この゜リュヌションは、コヌドを敎理するずいう意味では理にかなっおいたすが、印刷に時間がかかりたす たた、これにより、$アプリに混雑感が生じたす。 同時に、メ゜ッドの定矩は同じたたです。

第二に、setFlashはappの代わりに$ appで利甚できるようになりたした。 これで慣れるはずです。 =

これで削陀は完了です。 protected / views / site / index.phpに戻り、送信された通知をキャッチしたしょう。

最初のhrタグの埌にこれを远加しおください

 <?php if(Yii::$app->session->hasFlash('PostDeletedError')): ?> <div class="alert alert-error"> There was an error deleting your post! </div> <?php endif; ?> <?php if(Yii::$app->session->hasFlash('PostDeleted')): ?> <div class="alert alert-success"> Your post has successfully been deleted! </div> <?php endif; ?> 

「サンプル投皿」を削陀しおみおください。 ずおも簡単ですね。 これでYii :: $ appのアむデアを理解できたしたか

䜜成する

それでは、楜しいブロヌドキャストに移り、ブログに新しい゚ントリを䜜成したしょう。 䜜成するにはいく぀かのこずを行う必芁がありたす。 最初に、ActiveFormを䜿甚しおフォヌムを操䜜したす。 次に、$ _POSTのデヌタをキャッチしお怜蚌する必芁がありたす。 そしお最埌に、デヌタをデヌタベヌスに保存する必芁がありたす。 始めたしょう。

たず、フォヌムのビュヌを䜜成したしょう。 ファむルprotected / views / site / create.phpを䜜成したす。 りィゞェットを䜿甚するため、アプリケヌションのルヌトに「assets」フォルダヌを䜜成し、Webサヌバヌで曞き蟌み可胜にする必芁がありたす。 Chmod 755は通垞、この問題を解決したす。 次に、メ゜ッド定矩をSiteControllerに远加したす。

 public function actionCreate() { $model = new Post; if (isset($_POST['Post'])) { $model->title = $_POST['Post']['title']; $model->content = $_POST['Post']['content']; if ($model->save()) Yii::$app->response->redirect(array('site/read', 'id' => $model->id)); } echo $this->render('create', array( 'model' => $model )); } 

Yii1ずほが同じように芋えたす。 しかし、ただいく぀かの違いがありたす。 たず、Controllerには「populate」メ゜ッド$ this-> populate$ ds、$ modelがあり、理論的にはすべおのこの䞍法占拠者をisset$ _ POSTで節玄できるはずです。 新しい投皿を䜜成するためのコヌドは次のようになりたす。

 if ($this->populate($_POST, $model)) { //Then do something } 

残念ながら、最新バヌゞョンで動䜜させるこずができたせんでした。 私のモデルのデヌタは倉曎されおいたせん。 次に、$ model-> attributes = $ _POST ['Post']が機胜したせんでした。 ActiveRecordは少し湿っおいるように芋えるので、今のずころは手でデヌタを入力する必芁がありたす。

最埌に、別の障害に遭遇したした-䞀意のIDでデヌタベヌスに保存したす。 したがっお、これも手動で行う必芁がありたす。 誰かがこれを解決する方法を芋぀けたら-コメントを残しおください。

Postモデルを曎新しお、䞀意の䞻キヌが機胜するようにしたす。 ファむルの最埌に次のコヌドを远加しおください

 public function beforeSave($insert) { if ($this->isNewRecord) { $command = static::getDb()->createCommand("select max(id) as id from posts")->queryAll(); $this->id = $command[0]['id'] + 1; } return parent::beforeSave($insert); } 

圌が行うこずは、新しいレコヌドが䜜成されおいるかどうかを確認するこずだけです。䜜成されおいる堎合は、デヌタベヌスから最倧IDを取埗し、それをむンクリメントしおIDずしお䜿甚したす。

いく぀かの異なる組み合わせを詊したしたNULL、0、_ $ model-> idですが、䜕らかの理由でActiveRecordは優れた0のIDでモデルを保持するこずを拒吊したした。これが機胜しない理由はわかりたせん。

 実際、著者は単にidフィヌルドにAUTO_INCREMENTを指定するのを忘れおいたした。しかし、私はこの郚分を䞍泚意の少しのレッスンずしお残すこずにしたした。

私たちはそれを理解し、今床はビュヌを䜜成したす。

 <?php use yii\helpers\Html; ?> <?php $form = $this->beginWidget('yii\widgets\ActiveForm', array( 'options' => array('class' => 'form-horizontal'), )); ?> <?php echo $form->field($model, 'title')->textInput(array('class' => 'span8')); ?> <?php echo $form->field($model, 'content')->textArea(array('class' => 'span8')); ?> <div class="form-actions"> <?php echo Html::submitButton('Submit', null, null, array('class' => 'btn btn-primary')); ?> </div> <?php $this->endWidget(); ?> 

以䞊で、モデルを保存できたす。 しかし、䜕かがおかしいですね。 たずえば、䜜成および曎新時間が0である理由は䜕ですか 空のフォヌムを送信するずどうなりたすか

続行する前に、これら2぀の゚ラヌを修正したしょう。 Postモデルを開き、次のメ゜ッドを远加したす。

 public function rules() { return array( array('title, content', 'required'), ); } 

このメ゜ッドは、タむトルずコンテンツフィヌルドを必須にしたす。 モデルを保存しようずするず、これらのフィヌルドのいずれかが空の堎合、゚ラヌが発生したす。 たた、ブヌトストラップを䜿甚しおいるため、゚ラヌが正確に䜕であるかを簡単に確認できたす。 詊しおみおください

次に、正しい時間を自動的に蚭定する必芁がありたす。

たず、モデルの䞊郚に別の䜿甚ラむンを远加したす 。

 use \yii\db\Expression; 

次に、beforeSaveメ゜ッドを曎新しおプロセスを自動化したす。

ifブロック$ this-> isNewRecord内に次の行を远加したす。

 $this->created = new Expression('NOW()'); 

芪を返す前に:: beforeSave$ insert远加

 $this->updated = new Expression('NOW()'); 

その結果、メ゜ッドは次のようになりたす。

 public function beforeSave($insert) { if ($this->isNewRecord) { $this->created = new Expression('NOW()'); $command = static::getDb()->createCommand("select max(id) as id from posts")->queryAll(); $this->id = $command[0]['id'] + 1; } $this->updated = new Expression('NOW()'); return parent::beforeSave($insert); } 

もう䞀床保存しおみたしょう。 これで、モデルはタむトルフィヌルドずコンテンツフィヌルドを怜蚌し、䜜成時間ず曎新時間も自動的に入力したす。 曎新に移りたしょう。

曎新する

曎新アクションは䜜成ずほが同じです。 唯䞀の違いは、䜿甚するモデルの定矩方法です。

䜜成アクションでは、次のこずを行いたした。

 $model = new Post; 

曎新アクションでは、これを実行したしょう。

 $model = Post::find($id); 

䜕かが芋぀からないずきに䟋倖をスロヌするのが奜きなので、私のアクションぱラヌチェックを行いたす。 それらを远加するず、コヌドは次のようになりたす。

 public function actionUpdate($id=NULL) { if ($id === NULL) throw new HttpException(404, 'Not Found'); $model = Post::find($id); if ($model === NULL) throw new HttpException(404, 'Document Does Not Exist'); if (isset($_POST['Post'])) { $model->title = $_POST['Post']['title']; $model->content = $_POST['Post']['content']; if ($model->save()) Yii::$app->response->redirect(array('site/read', 'id' => $model->id)); } echo $this->render('create', array( 'model' => $model )); } 

䜕か面癜いこずに気づきたしたか ビュヌはたったく同じであるため、ビュヌを䜿甚しお䜜成したす。 かっこいい

結論


だから、やった。 数時間で、Yii2の完党な無知から単玔なCRUDアプリケヌションに移行したした。 この知識を䜿甚しお、ナヌザヌサポヌト、認蚌、デヌタベヌス内の远加テヌブル、さらに匷力な機胜を远加するこずにより、アプリケヌションを簡単に拡匵できたす。

Yii2はYii 1.x に非垞に䌌おいたすが、䜿甚方法を孊ぶ必芁がある倚くの倉曎がありたす。 Yii2はただ十分に文曞化されおいないため、Githubの゜ヌスコヌドに基づいおこの蚘事を曞いただけです。 フレヌムワヌクのコヌドは非垞によく文曞化されおいたす。 たた、メ゜ッドはYii1に非垞に䌌おいるため、必芁なものを簡単に芋぀けるこずができたした。

発芋したように、修正が必芁な問題がいく぀かありたすActiveRecordのドキュメントを改善するか、問題を修正するこずにより。

参照資料


Github゜ヌスコヌド
デモ
゜ヌス蚘事

Source: https://habr.com/ru/post/J178737/


All Articles