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
540
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
180
法制度と向き合う仕事 - インボイス制度対応の取り組み
eccyun
0
300
また、ゲームをつくりはじめた
eccyun
0
810
Other Decks in Programming
See All in Programming
ReadMoreTextView
fornewid
1
480
なぜ適用するか、移行して理解するClean Architecture 〜構造を超えて設計を継承する〜 / Why Apply, Migrate and Understand Clean Architecture - Inherit Design Beyond Structure
seike460
PRO
1
700
来たるべき 8.0 に備えて React 19 新機能と React Router 固有機能の取捨選択とすり合わせを考える
oukayuka
2
860
PHPでWebSocketサーバーを実装しよう2025
kubotak
0
220
Cursor AI Agentと伴走する アプリケーションの高速リプレイス
daisuketakeda
1
130
すべてのコンテキストを、 ユーザー価値に変える
applism118
2
880
LINEヤフー データグループ紹介
lycorp_recruit_jp
0
980
GoのGenericsによるslice操作との付き合い方
syumai
3
690
AIエージェントはこう育てる - GitHub Copilot Agentとチームの共進化サイクル
koboriakira
0
440
AWS CDKの推しポイント 〜CloudFormationと比較してみた〜
akihisaikeda
3
320
Go1.25からのGOMAXPROCS
kuro_kurorrr
1
810
LT 2025-06-30: プロダクトエンジニアの役割
yamamotok
0
540
Featured
See All Featured
Principles of Awesome APIs and How to Build Them.
keavy
126
17k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
124
52k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
281
13k
How to Ace a Technical Interview
jacobian
277
23k
Intergalactic Javascript Robots from Outer Space
tanoku
271
27k
Java REST API Framework Comparison - PWX 2021
mraible
31
8.6k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
107
19k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
181
53k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
31
1.2k
Done Done
chrislema
184
16k
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
32
2.3k
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
5
220
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
͝੩ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ʂ