Slide 1

Slide 1 text

GitHub IssueͰॻ͘ϒϩάΛ CakePHP4Ͱ࣮૷͢Δ at PHPΧϯϑΝϨϯεԭೄ2022

Slide 2

Slide 2 text

ϓϩϑΟʔϧ • ాౡ ྅ (ͨ͡· Γΐ͏) • @eccyun • ϥϯαʔζגࣜձࣾ ձܭ։ൃνʔϜॴଐ • ௕࡚ݝ ௕࡚ࢢࡏॅ • https://320321.net/ 1)1ΧϯϑΝϨϯεԭೄ

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

ࠓ೔͓࿩͢͠Δ͜ͱ • ίϯςϯπ੩తԽʹֶͿCakePHP4ͷόονॲཧ • GitHub ActionsΛ࢖ͬͨهࣄެ։ͷࣗಈԽྫ 1)1ΧϯϑΝϨϯεԭೄ

Slide 7

Slide 7 text

CakePHP4ͷόον࣮૷ 1)1ΧϯϑΝϨϯεԭೄ

Slide 8

Slide 8 text

CakePHP4ͷόον࣮૷ • CakePHP͸ίϯιʔϧϑϨʔϜϫʔΫͱͯ͠ͷػ ೳ΋͍࣋ͬͯ·͢ • /src/Command/ҎԼʹϑΝΠϧΛઃஔ • bin/cake {ίϚϯυ໊} Ͱ࣮ߦ • ඪ४ఏڙ͞Ε͍ͯΔίϚϯυ΋͍͔ͭ͋͘Γ·͢ ʢbakeͱ͔migrationsͱ͔ʣ 1)1ΧϯϑΝϨϯεԭೄ

Slide 9

Slide 9 text

out('Hello world.'); } } /src/command/HelloCommand.php

Slide 10

Slide 10 text

[vagrant@localhost html]$ bin/cake hello Hello world. /src/command/HelloCommand.php

Slide 11

Slide 11 text

IssueΛ΋ͱʹHTMLԽ͢Δ 1)1ΧϯϑΝϨϯεԭೄ

Slide 12

Slide 12 text

࢓༷ͷΠϝʔδ 1. GitHub Issueͷ৘ใΛऔಘ͠ɺͦΕΛ΋ͱʹ هࣄσʔλΛ࡞੒ 2. දࣔ༻ͷϏϡʔςϯϓϨʔτΛ४උ 3. HTMLԽͯ͠อଘ 1)1ΧϯϑΝϨϯεԭೄ

Slide 13

Slide 13 text

getArticles(); // 記事一覧ページのHTML化 $this->putIndexPageHtml($articles); // 記事詳細個別ページのHTML化 $this->putArticlePageHtml($articles); // 事前に用意しておいたCSSをアップロード用にコピーする $this->copyCSS(); } //... 以後各メソッドを実装してます } /src/command/MakeStaticContentsCommand.php

Slide 14

Slide 14 text

GitHub IssueΛ΋ͱʹ 
 هࣄσʔλΛ࡞੒͢Δ 1)1ΧϯϑΝϨϯεԭೄ

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

[vagrant@localhost html]$ bin/cake make_static_contents ### Perm:article-automation このブログはS3でホスティングしています。これまでは記事公開の流れとして・・・ - GitHub Issueで記事を書いて - それをCakePHP4のアプリケーションでHTML化し - S3にアップロードする の流れを手動で行っていたのですが、この度記事を書いてからそれ以降の流れをGitHub Actionsを使って自動化しました。(この記事の公開がテストを兼ねている) GitHub Actionsを触った印象はめちゃくちゃ便利の一言に尽きる感じで、もっと早くにやっておくべきだったなと反省。 最近は記事の更新も月1程度だったんですけど、今回の実装で記事を書くハードルがかなり下がったので今後はまたぼちぼち書いていきたいです。 今回の自動化については、後日詳細な内容をまた書く予定。 $issue->bodyͷத਎

Slide 22

Slide 22 text

[vagrant@localhost html]$ bin/cake make_static_contents

このブログはS3でホスティングしています。これまでは記事公開の流れとして・・・

  • GitHub Issueで記事を書いて
  • それをCakePHP4のアプリケーションでHTML化し
  • S3にアップロードする

の流れを手動で行っていたのですが、この度記事を書いてからそれ以降の流れをGitHub Actionsを使って自動化しました。(この記事の公開がテストを兼ねている) p>

GitHub Actionsを触った印象はめちゃくちゃ便利の一言に尽きる感じで、もっと早くにやっておくべきだったなと反省。 最近は記事の更新も月1程度だったんですけど、今回の実装で記事を書くハードルがかなり下がったので今後はまたぼちぼち書いていきたいです。

今回の自動化については、後日詳細な内容をまた書く予定。

HTML੒ܗޙ

Slide 23

Slide 23 text

هࣄσʔλΛϏϡʔʹηοτ͠ɺ HTMLԽͯ͠อଘ͢Δ 1)1ΧϯϑΝϨϯεԭೄ

Slide 24

Slide 24 text

CakePHP4ͷϏϡʔपΓ • ڞ௨ͯ͠දࣔ͢Δ಺༰Λ·ͱΊΔϨΠΞ΢τ 
 /templates/layout/default.php • ֤ϖʔδݸผʹදࣔ͢ΔϏϡʔςϯϓϨʔτ 
 ྫ: /templates/Articles/index.php • ViewΛѻ͑ΔΫϥε͕͋Δ • HTMLग़ྗ΋Մೳ 1)1ΧϯϑΝϨϯεԭೄ

Slide 25

Slide 25 text

= $this->Html->charset() ?> My Blog = $this->Html->css(['main']) ?>
= $this->Html->link('My Blog', '/', ['class' => 'navbar-brand']); ?>
= $this->fetch('content') ?>
/templates/layout/default.php(ϨΠΞ΢τ)

Slide 26

Slide 26 text

$article) { ?>

= $article['title']; ?>

= nl2br($article['contents']); ?> /templates/Articles/index.php(Ϗϡʔ)

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

ͦͷଞͷϝιου 1)1ΧϯϑΝϨϯεԭೄ

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

࣮ߦ݁Ռ 1)1ΧϯϑΝϨϯεԭೄ

Slide 34

Slide 34 text

࣮ߦ݁Ռ 1)1ΧϯϑΝϨϯεԭೄ

Slide 35

Slide 35 text

࣮ߦ݁Ռ • ࢦఆͷϑΥϧμʹهࣄҰཡɾݸผϖʔδͷ HTML͕࡞ΒΕΔΑ͏ʹͳͬͨ • ϒϥ΢βͰݟͨײ͡΋ҧ࿨ײ͕ͳ͍ • S3ʹΞοϓϩʔυ͢Ε͹ɺϒϩάͱͯ͠ӡ༻ Λ͸͡ΊΒΕͦ͏ 1)1ΧϯϑΝϨϯεԭೄ

Slide 36

Slide 36 text

ͱ͸ݴ͑ɺຖճόονΛ࣮ߦͯ͠खಈͰ Ξοϓϩʔυ͢Δͷ΋खؒͰ͢ΑͶ… 1)1ΧϯϑΝϨϯεԭೄ

Slide 37

Slide 37 text

GitHub ActionsΛ࢖ͬͯ Ξοϓϩʔυ࡞ۀΛ ࣗಈԽͯ͠ΈΑ͏ 1)1ΧϯϑΝϨϯεԭೄ

Slide 38

Slide 38 text

GitHub Actionsͱ͸ 1)1ΧϯϑΝϨϯεԭೄ

Slide 39

Slide 39 text

GitHub Actionsͱ͸ • 2019೥ 11݄ެ։ • GitHubͰͷ༷ʑͳૢ࡞ΛτϦΨʔʹɺ͋Β͔ ͡Ίఆٛͨ͠ॲཧ(ϫʔΫϑϩʔ)ͷ࣮ߦ͕Մೳ • .github/work fl ows/ʹymlͰఆٛ 1)1ΧϯϑΝϨϯεԭೄ

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

No content

Slide 42

Slide 42 text

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ͱ͍͏ׅΓͰ ఆٛࡁΈͷૢ࡞͕࣮ߦՄೳ

Slide 43

Slide 43 text

Action(ΞΫγϣϯ) • ActionͰఆٛ͞Εͨૢ࡞͕࣮ߦՄೳ • ϚʔέοτϓϨΠεʹެ։͞Ε͍ͯΔͷͰ 
 ୳ͯ͠ΈΔͱྑ͍ • ؀ڥߏஙपΓͰݴ͏ͱجຊతͳ΋ͷ͸Ұ௨Γ ͋Γͦ͏Ͱ͢ 1)1ΧϯϑΝϨϯεԭೄ

Slide 44

Slide 44 text

No content

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

No content

Slide 47

Slide 47 text

ϫʔΫϑϩʔ্ͷॲཧͰ S3ʹίϯςϯπΛΞοϓϩʔυ͢Δ 1)1ΧϯϑΝϨϯεԭೄ

Slide 48

Slide 48 text

S3΁ͷΞοϓϩʔυ • AWS CLI • $ aws s3 cp {ϑΝΠϧύε} s3://{όέοτ໊} • ಛʹԿ΋ͤͣͱ΋ϫʔΫϑϩʔ্Ͱར༻Մೳ • ͨͩ͠ɺར༻ʹ͋ͨΓೝূपΓͰઃఆ͕ඞཁ 1)1ΧϯϑΝϨϯεԭೄ

Slide 49

Slide 49 text

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ΧϯϑΝϨϯεԭೄ

Slide 50

Slide 50 text

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ΧϯϑΝϨϯεԭೄ

Slide 51

Slide 51 text

γʔΫϨοτ • ${{ secrets.AWS_ACCESS_KEY_ID }}ͳͲ • ॏཁͳ৘ใΛγʔΫϨοτͱͯ͠ઃఆ͓ͯ͠ ͘͜ͱͰɺද্ࣔϚεΫ͞ΕΔܗͰ 
 ϫʔΫϑϩʔ্Ͱར༻Ͱ͖ΔΑ͏ʹͳΓ·͢ 1)1ΧϯϑΝϨϯεԭೄ

Slide 52

Slide 52 text

No content

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

͝੩ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ʂ