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
Cursor AI Agentと伴走する アプリケーションの高速リプレイス
daisuketakeda
1
130
PHP 8.4の新機能「プロパティフック」から学ぶオブジェクト指向設計とリスコフの置換原則
kentaroutakeda
2
490
エンジニア向け採用ピッチ資料
inusan
0
160
GraphRAGの仕組みまるわかり
tosuri13
7
480
データの民主化を支える、透明性のあるデータ利活用への挑戦 2025-06-25 Database Engineering Meetup#7
y_ken
0
320
Elixir で IoT 開発、 Nerves なら簡単にできる!?
pojiro
1
150
Is Xcode slowly dying out in 2025?
uetyo
1
190
つよそうにふるまい、つよい成果を出すのなら、つよいのかもしれない
irof
1
300
What Spring Developers Should Know About Jakarta EE
ivargrimstad
0
220
生成AIコーディングとの向き合い方、AIと共創するという考え方 / How to deal with generative AI coding and the concept of co-creating with AI
seike460
PRO
1
330
VS Code Update for GitHub Copilot
74th
1
320
Team operations that are not burdened by SRE
kazatohiei
1
190
Featured
See All Featured
Adopting Sorbet at Scale
ufuk
77
9.4k
The Cost Of JavaScript in 2023
addyosmani
51
8.4k
The Power of CSS Pseudo Elements
geoffreycrofte
77
5.8k
How to Ace a Technical Interview
jacobian
277
23k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
30
2.1k
Git: the NoSQL Database
bkeepers
PRO
430
65k
BBQ
matthewcrist
89
9.7k
Writing Fast Ruby
sferik
628
61k
Build your cross-platform service in a week with App Engine
jlugia
231
18k
How to train your dragon (web standard)
notwaldorf
92
6.1k
Raft: Consensus for Rubyists
vanstee
140
7k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
107
19k
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
͝੩ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ʂ