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
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Ryo Tajima
August 27, 2022
Programming
600
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
GitHub Issueで書くブログをCakePHP4で実装する
PHPカンファレンス沖縄2022の登壇資料です。
GitHub Issueで書くブログ実装をテーマにCakePHP4のバッチ処理とGitHub Actionsについてお話しました。
Ryo Tajima
August 27, 2022
More Decks by Ryo Tajima
See All by Ryo Tajima
スクラムのイテレーションを導入してチームの雰囲気がより良くなった話
eccyun
0
250
法制度と向き合う仕事 - インボイス制度対応の取り組み
eccyun
0
380
また、ゲームをつくりはじめた
eccyun
0
820
Other Decks in Programming
See All in Programming
AutonomyとControlのあいだ:Graflowで記述するAIエージェント協調
myui
0
110
さぁV100、メモリをお食べ・・・
nilpe
0
130
Lessons from Spec-Driven Development
simas
PRO
0
140
AI駆動開発で崩れていくコードベースを立て直す
kyoko_nr_nr
1
440
TSKaigi Night Talks 2026_TypeScriptでサプライチェーンの整合性を型に閉じ込める
geekplus_tech
0
310
Semantic Version 単位で戦略を柔軟に変えて、パッケージアップデートを自動化する
daitasu
0
170
メソッドのジェネリクスでGoの夢は広がるか? / Kyoto.go #65
utgwkk
3
580
AI駆動開発勉強会 広島支部 第一回勉強会 AI駆動開発概要とワークショップ
hayatoshimiu
0
450
並列実装の現場、2ヶ月間実務でAIを使い倒したAIもPCも私も限界が近い
ming_ayami
0
110
GitHub Copilot CLIのいいところ
htkym
2
1.3k
Spec-Driven Development with AI-Agents: From High-Level Requirements to Working Software
antonarhipov
2
470
Language Server 使ってる? 〜VSCode と Zed の場合〜 / Are you using a Language Server? ~For VS Code and Zed~
handlename
0
770
Featured
See All Featured
SEO for Brand Visibility & Recognition
aleyda
0
4.6k
The Straight Up "How To Draw Better" Workshop
denniskardys
239
140k
Optimizing for Happiness
mojombo
378
71k
KATA
mclloyd
PRO
35
15k
From π to Pie charts
rasagy
0
200
Claude Code どこまでも/ Claude Code Everywhere
nwiizo
65
56k
AI Search: Implications for SEO and How to Move Forward - #ShenzhenSEOConference
aleyda
1
1.3k
Into the Great Unknown - MozCon
thekraken
41
2.5k
Leadership Guide Workshop - DevTernity 2021
reverentgeek
1
300
The Limits of Empathy - UXLibs8
cassininazir
1
350
Jamie Indigo - Trashchat’s Guide to Black Boxes: Technical SEO Tactics for LLMs
techseoconnect
PRO
0
160
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
10
1.2k
Transcript
GitHub IssueͰॻ͘ϒϩάΛ CakePHP4Ͱ࣮͢Δ at PHPΧϯϑΝϨϯεԭೄ2022
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/workflows/ʹ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/workflows/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/workflows/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/workflows/sample.yml
None
ϫʔΫϑϩʔ্ͷॲཧͰ S3ʹίϯςϯπΛΞοϓϩʔυ͢Δ 1)1ΧϯϑΝϨϯεԭೄ
S3ͷΞοϓϩʔυ • AWS CLI • $ aws s3 cp {ϑΝΠϧύε}
s3://{όέοτ໊} • ಛʹԿͤͣͱϫʔΫϑϩʔ্Ͱར༻Մೳ • ͨͩ͠ɺར༻ʹ͋ͨΓೝূपΓͰઃఆ͕ඞཁ 1)1ΧϯϑΝϨϯεԭೄ
aws-actions/configure-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/configure-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/workflows/sample.yml
͝੩ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ʂ