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
480
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
200
また、ゲームをつくりはじめた
eccyun
0
800
Other Decks in Programming
See All in Programming
ペアーズにおけるAmazon Bedrockを⽤いた障害対応⽀援 ⽣成AIツールの導⼊事例 @ 20241115配信AWSウェビナー登壇
fukubaka0825
5
1.5k
RubyLSPのマルチバイト文字対応
notfounds
0
100
GitHub Actionsのキャッシュと手を挙げることの大切さとそれに必要なこと
satoshi256kbyte
5
420
Amazon Bedrock Agentsを用いてアプリ開発してみた!
har1101
0
320
Boost Performance and Developer Productivity with Jakarta EE 11
ivargrimstad
0
1.7k
みんなでプロポーザルを書いてみた
yuriko1211
0
230
Identifying User Idenity
moro
6
9.7k
ピラミッド、アイスクリームコーン、SMURF: 自動テストの最適バランスを求めて / Pyramid Ice-Cream-Cone and SMURF
twada
PRO
10
1.2k
推し活の ハイトラフィックに立ち向かう Railsとアーキテクチャ - Kaigi on Rails 2024
falcon8823
6
2.8k
初めてDefinitelyTypedにPRを出した話
syumai
0
250
ActiveSupport::Notifications supporting instrumentation of Rails apps with OpenTelemetry
ymtdzzz
1
210
카카오페이는 어떻게 수천만 결제를 처리할까? 우아한 결제 분산락 노하우
kakao
PRO
0
110
Featured
See All Featured
GraphQLの誤解/rethinking-graphql
sonatard
67
10k
Mobile First: as difficult as doing things right
swwweet
222
8.9k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
169
50k
Large-scale JavaScript Application Architecture
addyosmani
510
110k
Automating Front-end Workflow
addyosmani
1366
200k
Designing the Hi-DPI Web
ddemaree
280
34k
Writing Fast Ruby
sferik
627
61k
Rebuilding a faster, lazier Slack
samanthasiow
79
8.7k
Designing for Performance
lara
604
68k
Building Better People: How to give real-time feedback that sticks.
wjessup
364
19k
Fontdeck: Realign not Redesign
paulrobertlloyd
82
5.2k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
25
1.8k
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
͝੩ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ʂ