Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
GitHub Issueで書くブログをCakePHP4で実装する
Search
Ryo Tajima
August 27, 2022
Programming
1
500
GitHub Issueで書くブログをCakePHP4で実装する
PHPカンファレンス沖縄2022の登壇資料です。
GitHub Issueで書くブログ実装をテーマにCakePHP4のバッチ処理とGitHub Actionsについてお話しました。
Ryo Tajima
August 27, 2022
Tweet
Share
More Decks by Ryo Tajima
See All by Ryo Tajima
スクラムのイテレーションを導入してチームの雰囲気がより良くなった話
eccyun
0
110
法制度と向き合う仕事 - インボイス制度対応の取り組み
eccyun
0
270
また、ゲームをつくりはじめた
eccyun
0
800
Other Decks in Programming
See All in Programming
CNCF Project の作者が考えている OSS の運営
utam0k
6
710
[JAWS-UG横浜 #80] うわっ…今年のServerless アップデート、少なすぎ…?
maroon1st
1
180
法律の脱レガシーに学ぶフロントエンド刷新
oguemon
5
740
XStateを用いた堅牢なReact Components設計~複雑なClient Stateをシンプルに~ @React Tokyo ミートアップ #2
kfurusho
1
890
ファインディの テックブログ爆誕までの軌跡
starfish719
2
1.1k
Lottieアニメーションをカスタマイズしてみた
tahia910
0
120
DROBEの生成AI活用事例 with AWS
ippey
0
130
ペアーズでの、Langfuseを中心とした評価ドリブンなリリースサイクルのご紹介
fukubaka0825
2
320
密集、ドキュメントのコロケーション with AWS Lambda
satoshi256kbyte
0
190
ファインディLT_ポケモン対戦の定量的分析
fufufukakaka
0
680
社内フレームワークとその依存性解決 / in-house framework and its dependency management
vvakame
1
560
GAEログのコスト削減
mot_techtalk
0
120
Featured
See All Featured
Dealing with People You Can't Stand - Big Design 2015
cassininazir
366
25k
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
29
2.4k
How To Stay Up To Date on Web Technology
chriscoyier
790
250k
RailsConf 2023
tenderlove
29
1k
Gamification - CAS2011
davidbonilla
80
5.1k
Building Your Own Lightsaber
phodgson
104
6.2k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
656
59k
YesSQL, Process and Tooling at Scale
rocio
172
14k
Speed Design
sergeychernyshev
27
790
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
10
1.3k
Measuring & Analyzing Core Web Vitals
bluesmoon
6
240
GitHub's CSS Performance
jonrohan
1030
460k
Transcript
GitHub IssueͰॻ͘ϒϩάΛ CakePHP4Ͱ࣮͢Δ at PHPΧϯϑΝϨϯεԭೄ2022
ϓϩϑΟʔϧ • ాౡ ྅ (ͨ͡· Γΐ͏) • @eccyun • ϥϯαʔζגࣜձࣾ
ձܭ։ൃνʔϜॴଐ • ࡚ݝ ࡚ࢢࡏॅ • https://320321.net/ 1)1ΧϯϑΝϨϯεԭೄ
None
None
None
ࠓ͓͢͠Δ͜ͱ • ίϯςϯπ੩తԽʹֶͿCakePHP4ͷόονॲཧ • GitHub ActionsΛͬͨهࣄެ։ͷࣗಈԽྫ 1)1ΧϯϑΝϨϯεԭೄ
CakePHP4ͷόον࣮ 1)1ΧϯϑΝϨϯεԭೄ
CakePHP4ͷόον࣮ • CakePHPίϯιʔϧϑϨʔϜϫʔΫͱͯ͠ͷػ ೳ͍࣋ͬͯ·͢ • /src/Command/ҎԼʹϑΝΠϧΛઃஔ • bin/cake {ίϚϯυ໊} Ͱ࣮ߦ
• ඪ४ఏڙ͞Ε͍ͯΔίϚϯυ͍͔ͭ͋͘Γ·͢ ʢbakeͱ͔migrationsͱ͔ʣ 1)1ΧϯϑΝϨϯεԭೄ
<?php namespace App\Command; use Cake\Command\Command; use Cake\Console\Arguments; use Cake\Console\ConsoleIo; class
HelloCommand extends Command { public function execute(Arguments $args, ConsoleIo $io) { $io->out('Hello world.'); } } /src/command/HelloCommand.php
[vagrant@localhost html]$ bin/cake hello Hello world. /src/command/HelloCommand.php
IssueΛͱʹHTMLԽ͢Δ 1)1ΧϯϑΝϨϯεԭೄ
༷ͷΠϝʔδ 1. GitHub IssueͷใΛऔಘ͠ɺͦΕΛͱʹ هࣄσʔλΛ࡞ 2. දࣔ༻ͷϏϡʔςϯϓϨʔτΛ४උ 3. HTMLԽͯ͠อଘ 1)1ΧϯϑΝϨϯεԭೄ
<?php namespace App\Command; use Cake\Command\Command; use Cake\Console\Arguments; use Cake\Console\ConsoleIo; use
Cake\Http\Client; use Michelf\Markdown; use Cake\View\View; class MakeStaticContentsCommand extends Command { public function execute(Arguments $args, ConsoleIo $io) { // GitHub Issueをもとに記事データを作成 $articles = $this->getArticles(); // 記事一覧ページのHTML化 $this->putIndexPageHtml($articles); // 記事詳細個別ページのHTML化 $this->putArticlePageHtml($articles); // 事前に用意しておいたCSSをアップロード用にコピーする $this->copyCSS(); } //... 以後各メソッドを実装してます } /src/command/MakeStaticContentsCommand.php
GitHub IssueΛͱʹ هࣄσʔλΛ࡞͢Δ 1)1ΧϯϑΝϨϯεԭೄ
None
private function getArticles():array { $issues = $this->getGitHubIssues(); $articles = [];
foreach($issues as $issue) { $body = $issue->body; if(preg_match('/### Perm:([a-zA-Z0-9\-]{0,})/', $body, $match)){ $permalink = $match[1]; $body = str_replace($match[0], '', $body); $contents = Markdown::defaultTransform($body); $created = date('Y-m-d H:i:s', strtotime($issue->created_at . ' +9hour')); $articles[] = [ 'permalink' => $permalink, 'title' => $issue->title, 'contents' => $contents, 'created' => $created ]; } } return $articles; } /src/command/MakeStaticContentsCommand.php
private function getArticles():array { $issues = $this->getGitHubIssues(); $articles = [];
foreach($issues as $issue) { $body = $issue->body; if(preg_match('/### Perm:([a-zA-Z0-9\-]{0,})/', $body, $match)){ $permalink = $match[1]; $body = str_replace($match[0], '', $body); $contents = Markdown::defaultTransform($body); $created = date('Y-m-d H:i:s', strtotime($issue->created_at . ' +9hour')); $articles[] = [ 'permalink' => $permalink, 'title' => $issue->title, 'contents' => $contents, 'created' => $created ]; } } return $articles; } /src/command/MakeStaticContentsCommand.php
private function getGitHubIssues() :array { $apiUrl = 'https://api.github.com/repos/{user_name}/{repo_name}/issues'; $http =
new Client(); $response = $http->get($apiUrl, null, [ 'headers' => [ 'Accept' => 'application/vnd.github.v3+json', 'Authorization' => 'token ' . {github_access_token} ] ]); return json_decode($response->getStringBody()); } /src/command/MakeStaticContentsCommand.php
private function getArticles():array { $issues = $this->getGitHubIssues(); $articles = [];
foreach($issues as $issue) { $body = $issue->body; if(preg_match('/### Perm:([a-zA-Z0-9\-]{0,})/', $body, $match)){ $permalink = $match[1]; $body = str_replace($match[0], '', $body); $contents = Markdown::defaultTransform($body); $created = date('Y-m-d H:i:s', strtotime($issue->created_at . ' +9hour')); $articles[] = [ 'permalink' => $permalink, 'title' => $issue->title, 'contents' => $contents, 'created' => $created ]; } } return $articles; } /src/command/MakeStaticContentsCommand.php
private function getArticles():array { $issues = $this->getGitHubIssues(); $articles = [];
foreach($issues as $issue) { $body = $issue->body; if(preg_match('/### Perm:([a-zA-Z0-9\-]{0,})/', $body, $match)){ $permalink = $match[1]; $body = str_replace($match[0], '', $body); $contents = Markdown::defaultTransform($body); $created = date('Y-m-d H:i:s', strtotime($issue->created_at . ' +9hour')); $articles[] = [ 'permalink' => $permalink, 'title' => $issue->title, 'contents' => $contents, 'created' => $created ]; } } return $articles; } /src/command/MakeStaticContentsCommand.php
[vagrant@localhost html]$ bin/cake make_static_contents ### Perm:article-automation このブログはS3でホスティングしています。これまでは記事公開の流れとして・・・ - GitHub Issueで記事を書いて
- それをCakePHP4のアプリケーションでHTML化し - S3にアップロードする の流れを手動で行っていたのですが、この度記事を書いてからそれ以降の流れをGitHub Actionsを使って自動化しました。(この記事の公開がテストを兼ねている) GitHub Actionsを触った印象はめちゃくちゃ便利の一言に尽きる感じで、もっと早くにやっておくべきだったなと反省。 最近は記事の更新も月1程度だったんですけど、今回の実装で記事を書くハードルがかなり下がったので今後はまたぼちぼち書いていきたいです。 今回の自動化については、後日詳細な内容をまた書く予定。 $issue->bodyͷத
[vagrant@localhost html]$ bin/cake make_static_contents <p>このブログはS3でホスティングしています。これまでは記事公開の流れとして・・・</p> <ul> <li>GitHub Issueで記事を書いて</li> <li>それをCakePHP4のアプリケーションでHTML化し</li> <li>S3にアップロードする</li>
</ul> <p>の流れを手動で行っていたのですが、この度記事を書いてからそれ以降の流れをGitHub Actionsを使って自動化しました。(この記事の公開がテストを兼ねている)</ p> <p>GitHub Actionsを触った印象はめちゃくちゃ便利の一言に尽きる感じで、もっと早くにやっておくべきだったなと反省。 最近は記事の更新も月1程度だったんですけど、今回の実装で記事を書くハードルがかなり下がったので今後はまたぼちぼち書いていきたいです。</p> <p>今回の自動化については、後日詳細な内容をまた書く予定。</p> HTMLܗޙ
هࣄσʔλΛϏϡʔʹηοτ͠ɺ HTMLԽͯ͠อଘ͢Δ 1)1ΧϯϑΝϨϯεԭೄ
CakePHP4ͷϏϡʔपΓ • ڞ௨ͯ͠දࣔ͢Δ༰Λ·ͱΊΔϨΠΞτ /templates/layout/default.php • ֤ϖʔδݸผʹදࣔ͢ΔϏϡʔςϯϓϨʔτ ྫ: /templates/Articles/index.php
• ViewΛѻ͑ΔΫϥε͕͋Δ • HTMLग़ྗՄೳ 1)1ΧϯϑΝϨϯεԭೄ
<!DOCTYPE html> <html> <head> <?= $this->Html->charset() ?> <meta name="viewport" content="width=device-width,
initial-scale=1"> <title>My Blog</title> <?= $this->Html->css(['main']) ?> </head> <body> <header class="sticky-top"> <nav class="navbar navbar-expand-lg navbar-light bg-light"> <div class="container-fluid"> <?= $this->Html->link('My Blog', '/', ['class' => 'navbar-brand']); ?> </div> </nav> </header> <div class="container"> <?= $this->fetch('content') ?> </div> </body> </html> /templates/layout/default.php(ϨΠΞτ)
<main> <?php foreach($articles as $key => $article) { ?> <article
class="row"> <header class="col-md-12"> <h1> <a href="/<?= $article['permalink'] ?>/"> <?= $article['title']; ?> </a> </h1> <time><?= $article['created']; ?></time> </header> <section class="col-md-12 mt-3"> <?= nl2br($article['contents']); ?> </section> </article> <?php } ?> </main> /templates/Articles/index.php(Ϗϡʔ)
private function putIndexPageHtml(array $articles):void { $view = new View(); $view->setLayout('default');
$view->setTemplatePath('Articles'); $view->setTemplate('index'); $view->set([ 'articles' => $articles ]); $html = $view->render(); file_put_contents(WWW_ROOT . DS . 'uploads' . DS . 'index.html', $html); } /src/command/MakeStaticContentsCommand.php
None
private function putIndexPageHtml(array $articles):void { $view = new View(); $view->setLayout('default');
$view->setTemplatePath('Articles'); $view->setTemplate('index'); $view->set([ 'articles' => $articles ]); $html = $view->render(); file_put_contents(WWW_ROOT . DS . 'uploads' . DS . 'index.html', $html); } /src/command/MakeStaticContentsCommand.php
ͦͷଞͷϝιου 1)1ΧϯϑΝϨϯεԭೄ
private function putArticlePageHtml(array $articles):void { foreach ($articles as $article) {
$directory_path = WWW_ROOT . DS . ‘uploads' . DS . $article['permalink']; if(!file_exists(($directory_path))) { mkdir($directory_path, 0775, true); } // HTML取得 $view = new View(); $view->setTemplatePath('Articles'); $view->setTemplate('detail'); $view->set([ 'article' => $article, ]); $html = $view->render(); file_put_contents($directory_path . DS . 'index.html', $html); } } /src/command/MakeStaticContentsCommand.php
private function copyCSS():void { $directory_path = WWW_ROOT . DS .
'uploads' . DS . 'css'; if(!file_exists(($directory_path))) { mkdir($directory_path, 0775, true); } copy(WWW_ROOT . DS . 'css' . DS . 'main.css', $directory_path . DS . 'main.css'); } /src/command/MakeStaticContentsCommand.php
࣮ߦ݁Ռ 1)1ΧϯϑΝϨϯεԭೄ
࣮ߦ݁Ռ 1)1ΧϯϑΝϨϯεԭೄ
࣮ߦ݁Ռ • ࢦఆͷϑΥϧμʹهࣄҰཡɾݸผϖʔδͷ HTML͕࡞ΒΕΔΑ͏ʹͳͬͨ • ϒϥβͰݟͨײ͡ҧײ͕ͳ͍ • S3ʹΞοϓϩʔυ͢Εɺϒϩάͱͯ͠ӡ༻ Λ͡ΊΒΕͦ͏ 1)1ΧϯϑΝϨϯεԭೄ
ͱݴ͑ɺຖճόονΛ࣮ߦͯ͠खಈͰ Ξοϓϩʔυ͢ΔͷखؒͰ͢ΑͶ… 1)1ΧϯϑΝϨϯεԭೄ
GitHub ActionsΛͬͯ Ξοϓϩʔυ࡞ۀΛ ࣗಈԽͯ͠ΈΑ͏ 1)1ΧϯϑΝϨϯεԭೄ
GitHub Actionsͱ 1)1ΧϯϑΝϨϯεԭೄ
GitHub Actionsͱ • 2019 11݄ެ։ • GitHubͰͷ༷ʑͳૢ࡞ΛτϦΨʔʹɺ͋Β͔ ͡Ίఆٛͨ͠ॲཧ(ϫʔΫϑϩʔ)ͷ࣮ߦ͕Մೳ • .github/work
fl ows/ʹymlͰఆٛ 1)1ΧϯϑΝϨϯεԭೄ
name: sample on: push jobs: deploy: runs-on: ubuntu-latest steps: -
name: Checkout uses: actions/checkout@v2 - name: ls run: ls -al /.github/work fl ows/sample.yml
None
name: sample on: push jobs: deploy: runs-on: ubuntu-latest steps: -
name: Checkout uses: actions/checkout@v2 - name: ls run: ls -al /.github/work fl ows/sample.yml "DUJPOͱ͍͏ׅΓͰ ఆٛࡁΈͷૢ࡞͕࣮ߦՄೳ
Action(ΞΫγϣϯ) • ActionͰఆٛ͞Εͨૢ࡞͕࣮ߦՄೳ • ϚʔέοτϓϨΠεʹެ։͞Ε͍ͯΔͷͰ ୳ͯ͠ΈΔͱྑ͍ • ڥߏஙपΓͰݴ͏ͱجຊతͳͷҰ௨Γ ͋Γͦ͏Ͱ͢
1)1ΧϯϑΝϨϯεԭೄ
None
name: deploy on: push jobs: deploy: runs-on: ubuntu-latest steps: -
name: Checkout uses: actions/checkout@v2 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: 7.4 extensions: intl mbstring simplexml pdo coverage: none - name: composer install run: composer install - name: Setup MySQL run: | sudo /etc/init.d/mysql start mysql -u root -proot -e 'CREATE DATABASE blog;' - name: app_local.php edit run: | sed -ie "0,/'my_app'/ s/'my_app'/'root'/" ./config/app_local.php sed -ie s/'secret'/'root'/ ./config/app_local.php sed -ie "0,/'my_app'/ s/'my_app'/'blog'/" ./config/app_local.php - name: run CakePHP Shell Command run: bin/cake hello /.github/work fl ows/sample.yml
None
ϫʔΫϑϩʔ্ͷॲཧͰ S3ʹίϯςϯπΛΞοϓϩʔυ͢Δ 1)1ΧϯϑΝϨϯεԭೄ
S3ͷΞοϓϩʔυ • AWS CLI • $ aws s3 cp {ϑΝΠϧύε}
s3://{όέοτ໊} • ಛʹԿͤͣͱϫʔΫϑϩʔ্Ͱར༻Մೳ • ͨͩ͠ɺར༻ʹ͋ͨΓೝূपΓͰઃఆ͕ඞཁ 1)1ΧϯϑΝϨϯεԭೄ
aws-actions/con fi gure-aws-credentials • AWS͕ެ։͍ͯ͠ΔAction • ೝূใɾϦʔδϣϯڥͷઃఆΛ୲ͬͯ͘ΕΔ - name: Configure
AWS credentials uses: aws-actions/configure-aws-credentials@v1 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ap-northeast-1 1)1ΧϯϑΝϨϯεԭೄ
aws-actions/con fi gure-aws-credentials • AWS͕ެ։͍ͯ͠ΔAction • ೝূใɾϦʔδϣϯڥͷઃఆΛ୲ͬͯ͘ΕΔ - name: Configure
AWS credentials uses: aws-actions/configure-aws-credentials@v1 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ap-northeast-1 1)1ΧϯϑΝϨϯεԭೄ
γʔΫϨοτ • ${{ secrets.AWS_ACCESS_KEY_ID }}ͳͲ • ॏཁͳใΛγʔΫϨοτͱͯ͠ઃఆ͓ͯ͠ ͘͜ͱͰɺද্ࣔϚεΫ͞ΕΔܗͰ ϫʔΫϑϩʔ্Ͱར༻Ͱ͖ΔΑ͏ʹͳΓ·͢
1)1ΧϯϑΝϨϯεԭೄ
None
name: deploy on: issues: types: [opened, edited] jobs: deploy: runs-on:
ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: 7.4 extensions: intl mbstring simplexml pdo coverage: none - name: composer install run: composer install - name: Setup MySQL run: | sudo /etc/init.d/mysql start mysql -u root -proot -e 'CREATE DATABASE blog;' - name: app_local.php edit run: | sed -ie "0,/'my_app'/ s/'my_app'/'hoge'/" ./config/app_local.php sed -ie s/'secret'/'fuga'/ ./config/app_local.php sed -ie "0,/'my_app'/ s/‘my_app'/'hoge'/" ./config/app_local.php - name: run CakePHP Shell Command run: bin/cake make_static_contents - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v1 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ap-northeast-1 env: S3_BUCKET: ${{ secrets.S3_BUCKET }} run: aws s3 cp ./webroot/uploads $S3_BUCKET/ --recursive /.github/work fl ows/sample.yml
͝੩ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ʂ