[{"data":1,"prerenderedAt":7887},["ShallowReactive",2],{"tag-laravel-2":3},{"count":4,"content":5},16,[6,2440,3880,5548,6059,7037],{"id":7,"title":8,"body":9,"category":2425,"createdAt":2427,"description":2428,"extension":2429,"index":238,"meta":2430,"navigation":262,"path":2431,"publish":262,"seo":2432,"series":2433,"seriesTitle":2434,"stem":2435,"tag":2436,"thumbnail":2437,"updatedAt":2438,"__hash__":2439},"series\u002Fseries\u002Fvue-laravel-app-1.md","Vue SPA x Laravelでつくる実務パチモンアプリその１：アプリの概要と環境構築",{"type":10,"value":11,"toc":2401},"minimark",[12,16,35,47,50,70,73,77,80,84,87,91,122,125,162,165,176,179,182,187,190,193,204,218,225,304,318,323,327,330,384,387,390,393,411,469,476,480,483,841,858,867,925,928,936,950,959,965,980,983,986,990,997,1003,1006,1010,1013,1017,1023,1046,1052,1184,1195,1198,1223,1230,1236,1240,1247,1253,1256,1317,1320,1459,1466,1469,1594,1677,1680,2015,2022,2029,2032,2051,2226,2235,2247,2284,2287,2326,2332,2345,2356,2359,2372,2375,2384,2387,2390,2394,2397],[13,14,15],"p",{},"こんにちはjunです。仕事で中規模プロジェクトのディレクターとしてアサインされて、半年ほどブログを書けませんでした。ようやく最近時間が出てきたので新しいシリーズ記事を作成しようと思いました。とにかく最近１年はシステムばっかり作っていました。それぞのれプロジェクトでは",[17,18,19,23,26,29,32],"ul",{},[20,21,22],"li",{},"CRUDなAPI",[20,24,25],{},"管理画面の構築",[20,27,28],{},"ユーザー権限によるアクセス制限",[20,30,31],{},"ログイン・ログアウト",[20,33,34],{},"ジョブ・キューを用いた長い処理",[13,36,37,38,42,43,46],{},"など使用するシステムの構築を多く行い、いろいろ学ぶことできました。その中で「CRUDなAPI」を考えた「管理画面」の構築をすることが多くありました。バックエンドは",[39,40,41],"code",{},"Laravel","を、フロントエンドは",[39,44,45],{},"Vue.js","を使用していました。Vue.jsのおかげで高速にリッチなUIを構築し、システムを開発することができました。",[13,48,49],{},"この知見を共有し、まとめるためにも今回は",[17,51,52,55,58,61,64,67],{},[20,53,54],{},"管理画面はVue.js SPA",[20,56,57],{},"web APIによるCRUD操作",[20,59,60],{},"ルーティング設計",[20,62,63],{},"バリデーション",[20,65,66],{},"MVCによるアプリケーション設計",[20,68,69],{},"laravel\u002Fpermissionを用いたユーザー権限",[13,71,72],{},"以上の様な機能を持ったデモアプリを作ろうと思います。Laravelを用いたシステム開発と、Vue.jsによる管理画面フロントエンド開発を解説していきます。",[74,75,76],"h2",{"id":76},"作るアプリの概要",[13,78,79],{},"今回のアプリはn○teみたいなブログサービスを作ります。CRUDや管理画面の構築をメインに行いたいのでシンプルにいきます。",[81,82,83],"h3",{"id":83},"機能概要",[13,85,86],{},"ざっくりとした機能概要は以下のとおりです。",[88,89,90],"h4",{"id":90},"ユーザー系の特徴",[17,92,93,96,99,102,105,108],{},[20,94,95],{},"利用する際にはユーザーとしてアカウントを作る（今回は全てゲストとする）。",[20,97,98],{},"ログインはアドレスとパスワードを使用する。",[20,100,101],{},"アプリを利用する一般ユーザーと管理を行う管理ユーザーが存在する。",[20,103,104],{},"管理ユーザーは特定の管理用ルートから入る。",[20,106,107],{},"退会可能。退会時は関連するデータは削除される。",[20,109,110,111],{},"プロフィールでは以下の項目を持つ\n",[17,112,113,116,119],{},[20,114,115],{},"ユーザー名（仮名）",[20,117,118],{},"プロフィール文",[20,120,121],{},"アバター写真",[88,123,124],{"id":124},"ブログ機能",[17,126,127,130,147,150,153,156,159],{},[20,128,129],{},"ブログを作成することができ、公開することができる。",[20,131,132,133],{},"ブログは以下の項目を持つ\n",[17,134,135,138,141,144],{},[20,136,137],{},"タイトル",[20,139,140],{},"内容（リッチテキスト）",[20,142,143],{},"タグ（任意）",[20,145,146],{},"公開日時",[20,148,149],{},"下書きとして保存することができる。更新時も下書き可能。",[20,151,152],{},"任意数のタグを添付できる。タグはユーザーが新しく作成できる。",[20,154,155],{},"削除可能",[20,157,158],{},"画像の添付が可能。",[20,160,161],{},"アップロードした画像は管理できる。",[88,163,164],{"id":164},"一覧検索機能",[17,166,167,170,173],{},[20,168,169],{},"サイトトップは投稿された記事が新着順に並ぶ。",[20,171,172],{},"任意のタグを選んで一覧表示することが可能。",[20,174,175],{},"部分一致検索も一応可能とする。",[81,177,178],{"id":178},"アプリケーションアーキテクチャー",[13,180,181],{},"バックエンドにはLaravel9、管理画面はVue.js(2系)を使用して構築します。なおVue.jsはSPA構成とします。記事の一覧や詳細画面などの公開側はLaravelのレンダリングで表示します。",[183,184],"image-render",{":src":185,":center":186},"'vue_laravel_app\u002Farchitecture.jpg'","true",[13,188,189],{},"デザインに関してはセンスが皆無なので管理画面・公開側ともにBootstrapを使用します。",[74,191,192],{"id":192},"環境構築",[13,194,195,196,203],{},"ではまずは環境構築を行っていきましょう。環境構築にはDockerを使用してLAMP環境を作ります。",[197,198,202],"a",{"href":199,"rel":200},"https:\u002F\u002Fwww.twilio.com\u002Fblog\u002Fget-started-docker-laravel-jp",[201],"nofollow","こちらの記事","を参考にし、",[17,205,206,209,212,215],{},[20,207,208],{},"mysql5.7",[20,210,211],{},"phpmyadmin",[20,213,214],{},"apache2.4",[20,216,217],{},"php8.1",[13,219,220,221,224],{},"以上の構成を作成したいと思います。ディレクトリを作成して",[39,222,223],{},"docker-compose.yaml","を作成します。",[226,227,232],"pre",{"className":228,"code":229,"language":230,"meta":231,"style":231},"language-bash shiki shiki-themes material-theme-ocean","mkdir ~\u002Flaravel_vue\ncd  ~\u002Flaravel_vue\n\ntouch docker-compose.yaml\nmkdir html\nmkdir web_1\ncd web_1\ntouch Dockerfile\n","bash","",[39,233,234,247,257,264,273,281,289,296],{"__ignoreMap":231},[235,236,239,243],"span",{"class":237,"line":238},"line",1,[235,240,242],{"class":241},"s5Dmg","mkdir",[235,244,246],{"class":245},"sfyAc"," ~\u002Flaravel_vue\n",[235,248,250,254],{"class":237,"line":249},2,[235,251,253],{"class":252},"sdLwU","cd",[235,255,256],{"class":245},"  ~\u002Flaravel_vue\n",[235,258,260],{"class":237,"line":259},3,[235,261,263],{"emptyLinePlaceholder":262},true,"\n",[235,265,267,270],{"class":237,"line":266},4,[235,268,269],{"class":241},"touch",[235,271,272],{"class":245}," docker-compose.yaml\n",[235,274,276,278],{"class":237,"line":275},5,[235,277,242],{"class":241},[235,279,280],{"class":245}," html\n",[235,282,284,286],{"class":237,"line":283},6,[235,285,242],{"class":241},[235,287,288],{"class":245}," web_1\n",[235,290,292,294],{"class":237,"line":291},7,[235,293,253],{"class":252},[235,295,288],{"class":245},[235,297,299,301],{"class":237,"line":298},8,[235,300,269],{"class":241},[235,302,303],{"class":245}," Dockerfile\n",[13,305,306,309,310,313,314,317],{},[39,307,308],{},"html\u002F","という名前で作成したディレクトリにLaravelのソースが作成され、Dockerコンテナにマウントされる様にします。",[39,311,312],{},"web_1\u002F","ディレクトリにはphpとapacheが構築できる",[39,315,316],{},"Dockerfile","を作成してビルドします。",[13,319,320,322],{},[39,321,223],{},"でDBとapache+phpと連結したいと思います。",[81,324,326],{"id":325},"dockerfile設定","Dockerfile設定",[13,328,329],{},"まずはapacheとphpのイメージをDockerfileを作成します。",[226,331,336],{"className":332,"code":333,"filename":334,"language":335,"meta":231,"style":231},"language-dockerfie shiki shiki-themes material-theme-ocean","FROM php:8.1-apache\nRUN apt update \\\n        && apt install -y g++ libicu-dev libpq-dev libzip-dev zip zlib1g-dev \\\n        && mv \u002Fetc\u002Fapache2\u002Fmods-available\u002Frewrite.load \u002Fetc\u002Fapache2\u002Fmods-enabled\nRUN docker-php-ext-install pdo pdo_mysql\nWORKDIR \u002Fvar\u002Fwww\u002Fhtml\nRUN curl -sS https:\u002F\u002Fgetcomposer.org\u002Finstaller | php -- --install-dir=\u002Fusr\u002Flocal\u002Fbin --filename=composer\nRUN curl -fsSL https:\u002F\u002Fdeb.nodesource.com\u002Fsetup_lts.x | bash - \\\n    && apt-get install -y nodejs\n","web_1\u002FDockerfile","dockerfie",[39,337,338,343,348,353,358,363,368,373,378],{"__ignoreMap":231},[235,339,340],{"class":237,"line":238},[235,341,342],{},"FROM php:8.1-apache\n",[235,344,345],{"class":237,"line":249},[235,346,347],{},"RUN apt update \\\n",[235,349,350],{"class":237,"line":259},[235,351,352],{},"        && apt install -y g++ libicu-dev libpq-dev libzip-dev zip zlib1g-dev \\\n",[235,354,355],{"class":237,"line":266},[235,356,357],{},"        && mv \u002Fetc\u002Fapache2\u002Fmods-available\u002Frewrite.load \u002Fetc\u002Fapache2\u002Fmods-enabled\n",[235,359,360],{"class":237,"line":275},[235,361,362],{},"RUN docker-php-ext-install pdo pdo_mysql\n",[235,364,365],{"class":237,"line":283},[235,366,367],{},"WORKDIR \u002Fvar\u002Fwww\u002Fhtml\n",[235,369,370],{"class":237,"line":291},[235,371,372],{},"RUN curl -sS https:\u002F\u002Fgetcomposer.org\u002Finstaller | php -- --install-dir=\u002Fusr\u002Flocal\u002Fbin --filename=composer\n",[235,374,375],{"class":237,"line":298},[235,376,377],{},"RUN curl -fsSL https:\u002F\u002Fdeb.nodesource.com\u002Fsetup_lts.x | bash - \\\n",[235,379,381],{"class":237,"line":380},9,[235,382,383],{},"    && apt-get install -y nodejs\n",[13,385,386],{},"このファイルではphpとapacheが入った環境にてLaravelの依存PHPモジュールのインストールとcomposerをインストールする内容を記述しています。そしてLaravelのアセットビルドにはnode.jsを使用するのでそのインストールも記述しています。",[81,388,389],{"id":389},"apacheの設定ファイルを作成",[13,391,392],{},"Laravelのドキュメントルートを設定するためにapacheの設定ファイル作成して、マウントできる様にします。",[226,394,396],{"className":228,"code":395,"language":230,"meta":231,"style":231},"cd web_1\ntouch default.conf\n",[39,397,398,404],{"__ignoreMap":231},[235,399,400,402],{"class":237,"line":238},[235,401,253],{"class":252},[235,403,288],{"class":245},[235,405,406,408],{"class":237,"line":249},[235,407,269],{"class":241},[235,409,410],{"class":245}," default.conf\n",[226,412,417],{"className":413,"code":414,"filename":415,"language":416,"meta":231,"style":231},"language-conf shiki shiki-themes material-theme-ocean","\u003CVirtualHost *:80>\n    ServerName laravel_docker\n    DocumentRoot \u002Fvar\u002Fwww\u002Fhtml\u002Fpublic\n\n    \u003CDirectory \"\u002Fvar\u002Fwww\u002Fhtml\u002Fpublic\">\n        AllowOverride All\n    \u003C\u002FDirectory>\n    ErrorLog ${APACHE_LOG_DIR}\u002Ferror.log\n    CustomLog ${APACHE_LOG_DIR}\u002Faccess.log combined\n\u003C\u002FVirtualHost>\n","default.conf","conf",[39,418,419,424,429,434,438,443,448,453,458,463],{"__ignoreMap":231},[235,420,421],{"class":237,"line":238},[235,422,423],{},"\u003CVirtualHost *:80>\n",[235,425,426],{"class":237,"line":249},[235,427,428],{},"    ServerName laravel_docker\n",[235,430,431],{"class":237,"line":259},[235,432,433],{},"    DocumentRoot \u002Fvar\u002Fwww\u002Fhtml\u002Fpublic\n",[235,435,436],{"class":237,"line":266},[235,437,263],{"emptyLinePlaceholder":262},[235,439,440],{"class":237,"line":275},[235,441,442],{},"    \u003CDirectory \"\u002Fvar\u002Fwww\u002Fhtml\u002Fpublic\">\n",[235,444,445],{"class":237,"line":283},[235,446,447],{},"        AllowOverride All\n",[235,449,450],{"class":237,"line":291},[235,451,452],{},"    \u003C\u002FDirectory>\n",[235,454,455],{"class":237,"line":298},[235,456,457],{},"    ErrorLog ${APACHE_LOG_DIR}\u002Ferror.log\n",[235,459,460],{"class":237,"line":380},[235,461,462],{},"    CustomLog ${APACHE_LOG_DIR}\u002Faccess.log combined\n",[235,464,466],{"class":237,"line":465},10,[235,467,468],{},"\u003C\u002FVirtualHost>\n",[13,470,471,472,475],{},"このファイルはコンテナの",[39,473,474],{},"\u002Fetc\u002Fapache2\u002Fsites-enabled\u002F000-default.conf","にマウントされます。",[81,477,479],{"id":478},"docker-composeyamlを作成","docker-compose.yamlを作成",[13,481,482],{},"次にdocker-comose.yamlを作成してDBとphpmyadmin",[226,484,489],{"className":485,"code":486,"filename":487,"language":488,"meta":231,"style":231},"language-yaml shiki shiki-themes material-theme-ocean","version: '3'\nservices: \n  web_1:\n    build: .\u002Fweb_1\n    depends_on: \n      - db\n    volumes: \n      - .\u002Fhtml\u002F:\u002Fvar\u002Fwww\u002Fhtml\u002F\n      - .\u002Fweb_1\u002Fdefault.conf:\u002Fetc\u002Fapache2\u002Fsites-enabled\u002F000-default.conf\n    ports: \n      - \"8005:80\"\n  phpmyadmin:\n    image: phpmyadmin\n    restart: always\n    ports:\n      - \"8080:80\"\n    environment:\n     - PMA_ARBITRARY=1\n     - PMA_HOST=db:3306\n     - PMA_USER=root\n     - PMA_PASSWORD=rootroot\n  db:\n    image: mysql:5.7\n    command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci\n    environment:\n      MYSQL_DATABASE: preform\n      MYSQL_USER: test\n      MYSQL_PASSWORD: testtest\n      MYSQL_ROOT_PASSWORD: rootroot\n      TZ: Asia\u002FTokyo\n    ports: \n      - \"3306:3306\"\n    volumes: \n      - vue_laravel:\u002Fvar\u002Flib\u002Fmysql\nvolumes: \n  vue_laravel: {}\n","docker-comose.yaml","yaml",[39,490,491,510,521,529,539,548,556,565,572,579,588,602,610,621,632,639,650,658,667,675,683,691,699,709,720,727,738,749,760,771,782,791,803,812,820,830],{"__ignoreMap":231},[235,492,493,497,501,504,507],{"class":237,"line":238},[235,494,496],{"class":495},"s-wAU","version",[235,498,500],{"class":499},"sAklC",":",[235,502,503],{"class":499}," '",[235,505,506],{"class":245},"3",[235,508,509],{"class":499},"'\n",[235,511,512,515,517],{"class":237,"line":249},[235,513,514],{"class":495},"services",[235,516,500],{"class":499},[235,518,520],{"class":519},"s0W1g"," \n",[235,522,523,526],{"class":237,"line":259},[235,524,525],{"class":495},"  web_1",[235,527,528],{"class":499},":\n",[235,530,531,534,536],{"class":237,"line":266},[235,532,533],{"class":495},"    build",[235,535,500],{"class":499},[235,537,538],{"class":245}," .\u002Fweb_1\n",[235,540,541,544,546],{"class":237,"line":275},[235,542,543],{"class":495},"    depends_on",[235,545,500],{"class":499},[235,547,520],{"class":519},[235,549,550,553],{"class":237,"line":283},[235,551,552],{"class":499},"      -",[235,554,555],{"class":245}," db\n",[235,557,558,561,563],{"class":237,"line":291},[235,559,560],{"class":495},"    volumes",[235,562,500],{"class":499},[235,564,520],{"class":519},[235,566,567,569],{"class":237,"line":298},[235,568,552],{"class":499},[235,570,571],{"class":245}," .\u002Fhtml\u002F:\u002Fvar\u002Fwww\u002Fhtml\u002F\n",[235,573,574,576],{"class":237,"line":380},[235,575,552],{"class":499},[235,577,578],{"class":245}," .\u002Fweb_1\u002Fdefault.conf:\u002Fetc\u002Fapache2\u002Fsites-enabled\u002F000-default.conf\n",[235,580,581,584,586],{"class":237,"line":465},[235,582,583],{"class":495},"    ports",[235,585,500],{"class":499},[235,587,520],{"class":519},[235,589,591,593,596,599],{"class":237,"line":590},11,[235,592,552],{"class":499},[235,594,595],{"class":499}," \"",[235,597,598],{"class":245},"8005:80",[235,600,601],{"class":499},"\"\n",[235,603,605,608],{"class":237,"line":604},12,[235,606,607],{"class":495},"  phpmyadmin",[235,609,528],{"class":499},[235,611,613,616,618],{"class":237,"line":612},13,[235,614,615],{"class":495},"    image",[235,617,500],{"class":499},[235,619,620],{"class":245}," phpmyadmin\n",[235,622,624,627,629],{"class":237,"line":623},14,[235,625,626],{"class":495},"    restart",[235,628,500],{"class":499},[235,630,631],{"class":245}," always\n",[235,633,635,637],{"class":237,"line":634},15,[235,636,583],{"class":495},[235,638,528],{"class":499},[235,640,641,643,645,648],{"class":237,"line":4},[235,642,552],{"class":499},[235,644,595],{"class":499},[235,646,647],{"class":245},"8080:80",[235,649,601],{"class":499},[235,651,653,656],{"class":237,"line":652},17,[235,654,655],{"class":495},"    environment",[235,657,528],{"class":499},[235,659,661,664],{"class":237,"line":660},18,[235,662,663],{"class":499},"     -",[235,665,666],{"class":245}," PMA_ARBITRARY=1\n",[235,668,670,672],{"class":237,"line":669},19,[235,671,663],{"class":499},[235,673,674],{"class":245}," PMA_HOST=db:3306\n",[235,676,678,680],{"class":237,"line":677},20,[235,679,663],{"class":499},[235,681,682],{"class":245}," PMA_USER=root\n",[235,684,686,688],{"class":237,"line":685},21,[235,687,663],{"class":499},[235,689,690],{"class":245}," PMA_PASSWORD=rootroot\n",[235,692,694,697],{"class":237,"line":693},22,[235,695,696],{"class":495},"  db",[235,698,528],{"class":499},[235,700,702,704,706],{"class":237,"line":701},23,[235,703,615],{"class":495},[235,705,500],{"class":499},[235,707,708],{"class":245}," mysql:5.7\n",[235,710,712,715,717],{"class":237,"line":711},24,[235,713,714],{"class":495},"    command",[235,716,500],{"class":499},[235,718,719],{"class":245}," mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci\n",[235,721,723,725],{"class":237,"line":722},25,[235,724,655],{"class":495},[235,726,528],{"class":499},[235,728,730,733,735],{"class":237,"line":729},26,[235,731,732],{"class":495},"      MYSQL_DATABASE",[235,734,500],{"class":499},[235,736,737],{"class":245}," preform\n",[235,739,741,744,746],{"class":237,"line":740},27,[235,742,743],{"class":495},"      MYSQL_USER",[235,745,500],{"class":499},[235,747,748],{"class":245}," test\n",[235,750,752,755,757],{"class":237,"line":751},28,[235,753,754],{"class":495},"      MYSQL_PASSWORD",[235,756,500],{"class":499},[235,758,759],{"class":245}," testtest\n",[235,761,763,766,768],{"class":237,"line":762},29,[235,764,765],{"class":495},"      MYSQL_ROOT_PASSWORD",[235,767,500],{"class":499},[235,769,770],{"class":245}," rootroot\n",[235,772,774,777,779],{"class":237,"line":773},30,[235,775,776],{"class":495},"      TZ",[235,778,500],{"class":499},[235,780,781],{"class":245}," Asia\u002FTokyo\n",[235,783,785,787,789],{"class":237,"line":784},31,[235,786,583],{"class":495},[235,788,500],{"class":499},[235,790,520],{"class":519},[235,792,794,796,798,801],{"class":237,"line":793},32,[235,795,552],{"class":499},[235,797,595],{"class":499},[235,799,800],{"class":245},"3306:3306",[235,802,601],{"class":499},[235,804,806,808,810],{"class":237,"line":805},33,[235,807,560],{"class":495},[235,809,500],{"class":499},[235,811,520],{"class":519},[235,813,815,817],{"class":237,"line":814},34,[235,816,552],{"class":499},[235,818,819],{"class":245}," vue_laravel:\u002Fvar\u002Flib\u002Fmysql\n",[235,821,823,826,828],{"class":237,"line":822},35,[235,824,825],{"class":495},"volumes",[235,827,500],{"class":499},[235,829,520],{"class":519},[235,831,833,836,838],{"class":237,"line":832},36,[235,834,835],{"class":495},"  vue_laravel",[235,837,500],{"class":499},[235,839,840],{"class":499}," {}\n",[13,842,843,846,847,849,850,853,854,857],{},[39,844,845],{},"web_1","サービスでapacheとphpの環境をビルドします。そして",[39,848,308],{},"をコンテナ内の",[39,851,852],{},"\u002Fvar\u002Fwww\u002Fhtml\u002F","にマウントされます。ubuntuのapacheは",[39,855,856],{},"\u002Fetc\u002Fapache2\u002Fsites-enabled\u002F","配下にconfファイルを設置することで設定を追加したりオーバーライドできます。",[13,859,860,862,863,866],{},[39,861,211],{},"サービスでphpmyadminを起動してDBの内容を編集しやすくします。",[39,864,865],{},"db","サービスではmysqlの環境を作成します。ボリュームの設定をして永続化を行います。以上の設定を行ったらコンテナを立ち上げます。",[226,868,870],{"className":228,"code":869,"language":230,"meta":231,"style":231},"docker-compose up -d\n\nCreating laravel_vue_phpmyadmin_1 ... done\nCreating laravel_vue_db_1         ... done\nCreating laravel_vue_web_1_1      ... done\n",[39,871,872,883,887,901,913],{"__ignoreMap":231},[235,873,874,877,880],{"class":237,"line":238},[235,875,876],{"class":241},"docker-compose",[235,878,879],{"class":245}," up",[235,881,882],{"class":245}," -d\n",[235,884,885],{"class":237,"line":249},[235,886,263],{"emptyLinePlaceholder":262},[235,888,889,892,895,898],{"class":237,"line":259},[235,890,891],{"class":241},"Creating",[235,893,894],{"class":245}," laravel_vue_phpmyadmin_1",[235,896,897],{"class":245}," ...",[235,899,900],{"class":245}," done\n",[235,902,903,905,908,911],{"class":237,"line":266},[235,904,891],{"class":241},[235,906,907],{"class":245}," laravel_vue_db_1",[235,909,910],{"class":245},"         ...",[235,912,900],{"class":245},[235,914,915,917,920,923],{"class":237,"line":275},[235,916,891],{"class":241},[235,918,919],{"class":245}," laravel_vue_web_1_1",[235,921,922],{"class":245},"      ...",[235,924,900],{"class":245},[13,926,927],{},"そしてコンテナ内に入ってLaravelをインストールしていきます。",[226,929,934],{"className":930,"code":932,"language":933},[931],"language-text","docker exec -it laravel_vue_web_1_1 \u002Fbin\u002Fbash\n\nroot@a790844c74d6:\u002Fvar\u002Fwww\u002Fhtml# composer \nroot@a790844c74d6:\u002Fvar\u002Fwww\u002Fhtml# composer create-project laravel\u002Flaravel .\u002F\n","text",[39,935,932],{"__ignoreMap":231},[13,937,938,939,942,943,946,947,949],{},"これで",[39,940,941],{},"\u002Fvar\u002Fwww\u002Fhtml","配下にLaravelプロジェクトが入ります。コンテナの",[39,944,945],{}," \u002Fvar\u002Fwww\u002Fhtml","はホストマシンの",[39,948,308],{},"に同期されます。",[13,951,952,954,955,958],{},[39,953,308],{},"配下にソースが生成されたのを確認しましたら、",[39,956,957],{},".env","ファイルにDBの接続設定を記述します。",[226,960,963],{"className":961,"code":962,"filename":957,"language":933,"meta":231},[931],"DB_CONNECTION=mysql\nDB_HOST=db\nDB_PORT=3306\nDB_DATABASE=laravel\nDB_USERNAME=root\nDB_PASSWORD=rootroot\n",[39,964,962],{"__ignoreMap":231},[13,966,967,968,971,972,975,976,979],{},"phpmyadminとかで",[39,969,970],{},"laravel","データベースは作成しておいてください。",[39,973,974],{},"DB_HOST","はdockerのDBのサービス名を入力します。設定したらブラウザで",[39,977,978],{},"localhost:8005","にアクセスします。以下の様なウェルカムページが表示されれば成功です。",[183,981],{":src":982,":center":186},"'vue_laravel_app\u002Flaravel_welcome.png'",[13,984,985],{},"（特に断りがない限り、以降ではDockerコンテナ内の操作とします。）",[74,987,989],{"id":988},"laravelとvueセットアップ","LaravelとVueセットアップ",[13,991,992,993,996],{},"それではアセットと認証機能のセットアップをしましょう。今回は",[39,994,995],{},"laravel\u002Fbreeze","をスターターキットとして利用します。",[226,998,1001],{"className":999,"code":1000,"language":933},[931],"composer require laravel\u002Fbreeze --dev\n\nphp artisan breeze:install\nBreeze scaffolding installed successfully.\nPlease execute the \"npm install && npm run dev\" command to build your assets.\n\nnpm install\nnpm run dev\nphp artisan migrate\nMigration table created successfully.\nMigrating: 2014_10_12_000000_create_users_table\nMigrated:  2014_10_12_000000_create_users_table (23.69ms)\nMigrating: 2014_10_12_100000_create_password_resets_table\nMigrated:  2014_10_12_100000_create_password_resets_table (10.45ms)\nMigrating: 2019_08_19_000000_create_failed_jobs_table\nMigrated:  2019_08_19_000000_create_failed_jobs_table (10.60ms)\nMigrating: 2019_12_14_000001_create_personal_access_tokens_table\nMigrated:  2019_12_14_000001_create_personal_access_tokens_table (14.54ms)\n",[39,1002,1000],{"__ignoreMap":231},[13,1004,1005],{},"マイグレーションが完了したので登録などができる様になっているはずです。ひとまずLaravelのセットアップは以上となります。",[74,1007,1009],{"id":1008},"とりあえずspa構成ができるかチェック","とりあえずSPA構成ができるかチェック",[13,1011,1012],{},"次は管理画面のフロントエンドのセットアップをしていきます。管理画面はvue.jsで作成し、公開側コンテンツはLaravelのサーバーサイドレンダリングをします。ここではSPA構成のセットアップをします。",[81,1014,1016],{"id":1015},"spaのビューとルートの設定","SPAのビューとルートの設定",[13,1018,1019,1022],{},[39,1020,1021],{},"\u002Fsystem","配下を管理画面として構築していきます。以下の様なビューテンプレートとルートを作成します。",[226,1024,1029],{"className":1025,"code":1026,"filename":1027,"language":1028,"meta":231,"style":231},"language-php shiki shiki-themes material-theme-ocean","Route::get('\u002Fsystem\u002F{path?}', function () {\n    return view('admin');\n})->where('path', '.*')->name('admin');\n","routes\u002Fweb.php","php",[39,1030,1031,1036,1041],{"__ignoreMap":231},[235,1032,1033],{"class":237,"line":238},[235,1034,1035],{},"Route::get('\u002Fsystem\u002F{path?}', function () {\n",[235,1037,1038],{"class":237,"line":249},[235,1039,1040],{},"    return view('admin');\n",[235,1042,1043],{"class":237,"line":259},[235,1044,1045],{},"})->where('path', '.*')->name('admin');\n",[13,1047,1048,1049,1051],{},"このルートは",[39,1050,1021],{},"配下すべてのルートに対してadminビューを返すことを示しています。",[226,1053,1056],{"className":1025,"code":1054,"filename":1055,"language":1028,"meta":231,"style":231},"\u003C!DOCTYPE html>\n\u003Chtml lang=\"{{ str_replace('_', '-', app()->getLocale()) }}\">\n    \u003Chead>\n        \u003Cmeta charset=\"utf-8\">\n        \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n        \u003Cmeta name=\"csrf-token\" content=\"{{ csrf_token() }}\">\n\n        \u003Ctitle>{{ config('app.name', 'Laravel') }}\u003C\u002Ftitle>\n\n        \u003C!-- Fonts -->\n        \u003Clink rel=\"stylesheet\" href=\"https:\u002F\u002Ffonts.googleapis.com\u002Fcss2?family=Nunito:wght@400;600;700&display=swap\">\n\n        \u003C!-- Styles -->\n        \u003Clink rel=\"stylesheet\" href=\"{{ asset('css\u002Fapp.css') }}\">\n\n    \u003C\u002Fhead>\n    \u003Cbody>\n        \u003Cdiv class=\"font-sans text-gray-900 antialiased\">\n            \u003Cdiv id=\"app\">\n                \u003Crouter-view\u002F>\n            \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n    \u003C\u002Fbody>\n    \u003C!-- Scripts -->\n    \u003Cscript src=\"{{ asset('js\u002Fadmin\u002Findex.js') }}\" defer>\u003C\u002Fscript>\n\u003C\u002Fhtml>\n","resources\u002Fview\u002Fadmin.blade.php",[39,1057,1058,1063,1068,1073,1078,1083,1088,1092,1097,1101,1106,1111,1115,1120,1125,1129,1134,1139,1144,1149,1154,1159,1164,1169,1174,1179],{"__ignoreMap":231},[235,1059,1060],{"class":237,"line":238},[235,1061,1062],{},"\u003C!DOCTYPE html>\n",[235,1064,1065],{"class":237,"line":249},[235,1066,1067],{},"\u003Chtml lang=\"{{ str_replace('_', '-', app()->getLocale()) }}\">\n",[235,1069,1070],{"class":237,"line":259},[235,1071,1072],{},"    \u003Chead>\n",[235,1074,1075],{"class":237,"line":266},[235,1076,1077],{},"        \u003Cmeta charset=\"utf-8\">\n",[235,1079,1080],{"class":237,"line":275},[235,1081,1082],{},"        \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n",[235,1084,1085],{"class":237,"line":283},[235,1086,1087],{},"        \u003Cmeta name=\"csrf-token\" content=\"{{ csrf_token() }}\">\n",[235,1089,1090],{"class":237,"line":291},[235,1091,263],{"emptyLinePlaceholder":262},[235,1093,1094],{"class":237,"line":298},[235,1095,1096],{},"        \u003Ctitle>{{ config('app.name', 'Laravel') }}\u003C\u002Ftitle>\n",[235,1098,1099],{"class":237,"line":380},[235,1100,263],{"emptyLinePlaceholder":262},[235,1102,1103],{"class":237,"line":465},[235,1104,1105],{},"        \u003C!-- Fonts -->\n",[235,1107,1108],{"class":237,"line":590},[235,1109,1110],{},"        \u003Clink rel=\"stylesheet\" href=\"https:\u002F\u002Ffonts.googleapis.com\u002Fcss2?family=Nunito:wght@400;600;700&display=swap\">\n",[235,1112,1113],{"class":237,"line":604},[235,1114,263],{"emptyLinePlaceholder":262},[235,1116,1117],{"class":237,"line":612},[235,1118,1119],{},"        \u003C!-- Styles -->\n",[235,1121,1122],{"class":237,"line":623},[235,1123,1124],{},"        \u003Clink rel=\"stylesheet\" href=\"{{ asset('css\u002Fapp.css') }}\">\n",[235,1126,1127],{"class":237,"line":634},[235,1128,263],{"emptyLinePlaceholder":262},[235,1130,1131],{"class":237,"line":4},[235,1132,1133],{},"    \u003C\u002Fhead>\n",[235,1135,1136],{"class":237,"line":652},[235,1137,1138],{},"    \u003Cbody>\n",[235,1140,1141],{"class":237,"line":660},[235,1142,1143],{},"        \u003Cdiv class=\"font-sans text-gray-900 antialiased\">\n",[235,1145,1146],{"class":237,"line":669},[235,1147,1148],{},"            \u003Cdiv id=\"app\">\n",[235,1150,1151],{"class":237,"line":677},[235,1152,1153],{},"                \u003Crouter-view\u002F>\n",[235,1155,1156],{"class":237,"line":685},[235,1157,1158],{},"            \u003C\u002Fdiv>\n",[235,1160,1161],{"class":237,"line":693},[235,1162,1163],{},"        \u003C\u002Fdiv>\n",[235,1165,1166],{"class":237,"line":701},[235,1167,1168],{},"    \u003C\u002Fbody>\n",[235,1170,1171],{"class":237,"line":711},[235,1172,1173],{},"    \u003C!-- Scripts -->\n",[235,1175,1176],{"class":237,"line":722},[235,1177,1178],{},"    \u003Cscript src=\"{{ asset('js\u002Fadmin\u002Findex.js') }}\" defer>\u003C\u002Fscript>\n",[235,1180,1181],{"class":237,"line":729},[235,1182,1183],{},"\u003C\u002Fhtml>\n",[13,1185,1186,1187,1190,1191,1194],{},"テンプレートは上記の様にして",[39,1188,1189],{},"\u003Cdiv id=\"app\">\u003Crouter-view\u002F>\u003C\u002Fdiv>","を設置して、vueのエントリーポイントと、vueのビルドファイルを読み込む様にします。実際はヘッダーなどを分けたほうがいいですが、今はとりあえずこんな感じでOKです。\n次に",[39,1192,1193],{},"'js\u002Fadmin\u002Findex.js","で読み込ませるjsファイルを作成していきます。",[81,1196,1197],{"id":1197},"必要なライブラリをインストール",[13,1199,1200,1201,1204,1205,1208,1209,1212,1213,1215,1216,1208,1219,1222],{},"一昔前の",[39,1202,1203],{},"laravel\u002Fui","の時は",[39,1206,1207],{},"vue","と",[39,1210,1211],{},"bootstrap","の設定がインストール時に自動設定されますが、今回の",[39,1214,995],{},"は",[39,1217,1218],{},"alpinejs",[39,1220,1221],{},"tailwindcss","が入っているので、vue一式を自分でインストールする必要があります。まずはVue・Vuex・Vue-Routerをインストールしましょう。",[1224,1225,1229],"div",{"className":1226},[1227,1228],"alert","alert-danger","\nこの記事を作成した2021年3月現在、Vue3がリリースされておりバージョンを指定しないと、それぞれ最新版がインストールされる可能性があります。この記事はVue2を用いて解説しますのでバージョンを指定してインストールしています。\n",[226,1231,1234],{"className":1232,"code":1233,"language":933},[931],"npm install vue@2 vue-loader@15 vue-template-compiler@2 --save-dev\nnpm install vuex@3 vue-router@3\n",[39,1235,1233],{"__ignoreMap":231},[81,1237,1239],{"id":1238},"spa用のjsファイルを作成する","SPA用のJSファイルを作成する",[13,1241,1242,1243,1246],{},"それでは",[39,1244,1245],{},"resources\u002Fjs","配下にSPAのファイルを作成を行います。以下の様にファイルとディレクトリを作成してください。",[226,1248,1251],{"className":1249,"code":1250,"language":933},[931],"js\n├── admin\n│   ├── index.js\n│   ├── router.js\n│   └── store.js\n└── vue\n    └── page\n        ├── Home.vue\n        └── Profile.vue\n",[39,1252,1250],{"__ignoreMap":231},[13,1254,1255],{},"store.jsとrouter.jsは以下の様にします。",[226,1257,1262],{"className":1258,"code":1259,"filename":1260,"language":1261,"meta":231,"style":231},"language-js shiki shiki-themes material-theme-ocean","export default {\n    state:{},\n    getters: {},\n    mutations: {},\n    actions: {}\n}\n","store.js","js",[39,1263,1264,1276,1284,1294,1303,1312],{"__ignoreMap":231},[235,1265,1266,1270,1273],{"class":237,"line":238},[235,1267,1269],{"class":1268},"s6cf3","export",[235,1271,1272],{"class":1268}," default",[235,1274,1275],{"class":499}," {\n",[235,1277,1278,1281],{"class":237,"line":249},[235,1279,1280],{"class":495},"    state",[235,1282,1283],{"class":499},":{},\n",[235,1285,1286,1289,1291],{"class":237,"line":259},[235,1287,1288],{"class":495},"    getters",[235,1290,500],{"class":499},[235,1292,1293],{"class":499}," {},\n",[235,1295,1296,1299,1301],{"class":237,"line":266},[235,1297,1298],{"class":495},"    mutations",[235,1300,500],{"class":499},[235,1302,1293],{"class":499},[235,1304,1305,1308,1310],{"class":237,"line":275},[235,1306,1307],{"class":495},"    actions",[235,1309,500],{"class":499},[235,1311,840],{"class":499},[235,1313,1314],{"class":237,"line":283},[235,1315,1316],{"class":499},"}\n",[13,1318,1319],{},"storeはひとまず必要な型だけ準備します。",[226,1321,1324],{"className":1258,"code":1322,"filename":1323,"language":1261,"meta":231,"style":231},"import Home from '~js\u002Fvue\u002Fpage\u002FHome.vue';\nimport Profile from '~js\u002Fvue\u002Fpage\u002FProfile.vue';\n\nconst routes = [\n    { path: '\u002Fsystem', component: Home },\n    { path: '\u002Fsystem\u002Fprofile', component: Profile },\n]\n\nexport default routes;\n","router.js",[39,1325,1326,1348,1366,1370,1385,1414,1439,1444,1448],{"__ignoreMap":231},[235,1327,1328,1331,1334,1337,1339,1342,1345],{"class":237,"line":238},[235,1329,1330],{"class":1268},"import",[235,1332,1333],{"class":519}," Home ",[235,1335,1336],{"class":1268},"from",[235,1338,503],{"class":499},[235,1340,1341],{"class":245},"~js\u002Fvue\u002Fpage\u002FHome.vue",[235,1343,1344],{"class":499},"'",[235,1346,1347],{"class":499},";\n",[235,1349,1350,1352,1355,1357,1359,1362,1364],{"class":237,"line":249},[235,1351,1330],{"class":1268},[235,1353,1354],{"class":519}," Profile ",[235,1356,1336],{"class":1268},[235,1358,503],{"class":499},[235,1360,1361],{"class":245},"~js\u002Fvue\u002Fpage\u002FProfile.vue",[235,1363,1344],{"class":499},[235,1365,1347],{"class":499},[235,1367,1368],{"class":237,"line":259},[235,1369,263],{"emptyLinePlaceholder":262},[235,1371,1372,1376,1379,1382],{"class":237,"line":266},[235,1373,1375],{"class":1374},"sJ14y","const",[235,1377,1378],{"class":519}," routes ",[235,1380,1381],{"class":499},"=",[235,1383,1384],{"class":519}," [\n",[235,1386,1387,1390,1393,1395,1397,1399,1401,1404,1407,1409,1411],{"class":237,"line":275},[235,1388,1389],{"class":499},"    {",[235,1391,1392],{"class":495}," path",[235,1394,500],{"class":499},[235,1396,503],{"class":499},[235,1398,1021],{"class":245},[235,1400,1344],{"class":499},[235,1402,1403],{"class":499},",",[235,1405,1406],{"class":495}," component",[235,1408,500],{"class":499},[235,1410,1333],{"class":519},[235,1412,1413],{"class":499},"},\n",[235,1415,1416,1418,1420,1422,1424,1427,1429,1431,1433,1435,1437],{"class":237,"line":283},[235,1417,1389],{"class":499},[235,1419,1392],{"class":495},[235,1421,500],{"class":499},[235,1423,503],{"class":499},[235,1425,1426],{"class":245},"\u002Fsystem\u002Fprofile",[235,1428,1344],{"class":499},[235,1430,1403],{"class":499},[235,1432,1406],{"class":495},[235,1434,500],{"class":499},[235,1436,1354],{"class":519},[235,1438,1413],{"class":499},[235,1440,1441],{"class":237,"line":291},[235,1442,1443],{"class":519},"]\n",[235,1445,1446],{"class":237,"line":298},[235,1447,263],{"emptyLinePlaceholder":262},[235,1449,1450,1452,1454,1457],{"class":237,"line":380},[235,1451,1269],{"class":1268},[235,1453,1272],{"class":1268},[235,1455,1456],{"class":519}," routes",[235,1458,1347],{"class":499},[13,1460,1461,1462,1465],{},"router.jsでは指定のパスに対してどのコンポーネントを出すかを定義します。",[39,1463,1464],{},"~js","という記述は後で解説しますので、ひとまず記述してください。",[13,1467,1468],{},"router.jsで参照しているコンポーネントは以下の様にしておきます。",[226,1470,1474],{"className":1471,"code":1472,"filename":1473,"language":1207,"meta":231,"style":231},"language-vue shiki shiki-themes material-theme-ocean","\u003Ctemplate>\n    \u003Cdiv>\n        home\n        \u003Crouter-link to=\"\u002Fsystem\u002Fprofile\">profile\u003C\u002Frouter-link>\n    \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\u003Cscript>\nexport default {\n    name:'home'\n}\n\u003C\u002Fscript>\n","Home.vue",[39,1475,1476,1487,1496,1501,1534,1543,1551,1560,1568,1582,1586],{"__ignoreMap":231},[235,1477,1478,1481,1484],{"class":237,"line":238},[235,1479,1480],{"class":499},"\u003C",[235,1482,1483],{"class":495},"template",[235,1485,1486],{"class":499},">\n",[235,1488,1489,1492,1494],{"class":237,"line":249},[235,1490,1491],{"class":499},"    \u003C",[235,1493,1224],{"class":495},[235,1495,1486],{"class":499},[235,1497,1498],{"class":237,"line":259},[235,1499,1500],{"class":519},"        home\n",[235,1502,1503,1506,1509,1512,1514,1517,1519,1521,1524,1527,1530,1532],{"class":237,"line":266},[235,1504,1505],{"class":499},"        \u003C",[235,1507,1508],{"class":495},"router-link",[235,1510,1511],{"class":1374}," to",[235,1513,1381],{"class":499},[235,1515,1516],{"class":499},"\"",[235,1518,1426],{"class":245},[235,1520,1516],{"class":499},[235,1522,1523],{"class":499},">",[235,1525,1526],{"class":519},"profile",[235,1528,1529],{"class":499},"\u003C\u002F",[235,1531,1508],{"class":495},[235,1533,1486],{"class":499},[235,1535,1536,1539,1541],{"class":237,"line":275},[235,1537,1538],{"class":499},"    \u003C\u002F",[235,1540,1224],{"class":495},[235,1542,1486],{"class":499},[235,1544,1545,1547,1549],{"class":237,"line":283},[235,1546,1529],{"class":499},[235,1548,1483],{"class":495},[235,1550,1486],{"class":499},[235,1552,1553,1555,1558],{"class":237,"line":291},[235,1554,1480],{"class":499},[235,1556,1557],{"class":495},"script",[235,1559,1486],{"class":499},[235,1561,1562,1564,1566],{"class":237,"line":298},[235,1563,1269],{"class":1268},[235,1565,1272],{"class":1268},[235,1567,1275],{"class":499},[235,1569,1570,1573,1575,1577,1580],{"class":237,"line":380},[235,1571,1572],{"class":495},"    name",[235,1574,500],{"class":499},[235,1576,1344],{"class":499},[235,1578,1579],{"class":245},"home",[235,1581,509],{"class":499},[235,1583,1584],{"class":237,"line":465},[235,1585,1316],{"class":499},[235,1587,1588,1590,1592],{"class":237,"line":590},[235,1589,1529],{"class":499},[235,1591,1557],{"class":495},[235,1593,1486],{"class":499},[226,1595,1598],{"className":1471,"code":1596,"filename":1597,"language":1207,"meta":231,"style":231},"\u003Ctemplate>\n    \u003Cdiv>\n        profile\n    \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\u003Cscript>\nexport default {\n    name:'profile'\n}\n\u003C\u002Fscript>\n","Profile.vue",[39,1599,1600,1608,1616,1621,1629,1637,1645,1653,1665,1669],{"__ignoreMap":231},[235,1601,1602,1604,1606],{"class":237,"line":238},[235,1603,1480],{"class":499},[235,1605,1483],{"class":495},[235,1607,1486],{"class":499},[235,1609,1610,1612,1614],{"class":237,"line":249},[235,1611,1491],{"class":499},[235,1613,1224],{"class":495},[235,1615,1486],{"class":499},[235,1617,1618],{"class":237,"line":259},[235,1619,1620],{"class":519},"        profile\n",[235,1622,1623,1625,1627],{"class":237,"line":266},[235,1624,1538],{"class":499},[235,1626,1224],{"class":495},[235,1628,1486],{"class":499},[235,1630,1631,1633,1635],{"class":237,"line":275},[235,1632,1529],{"class":499},[235,1634,1483],{"class":495},[235,1636,1486],{"class":499},[235,1638,1639,1641,1643],{"class":237,"line":283},[235,1640,1480],{"class":499},[235,1642,1557],{"class":495},[235,1644,1486],{"class":499},[235,1646,1647,1649,1651],{"class":237,"line":291},[235,1648,1269],{"class":1268},[235,1650,1272],{"class":1268},[235,1652,1275],{"class":499},[235,1654,1655,1657,1659,1661,1663],{"class":237,"line":298},[235,1656,1572],{"class":495},[235,1658,500],{"class":499},[235,1660,1344],{"class":499},[235,1662,1526],{"class":245},[235,1664,509],{"class":499},[235,1666,1667],{"class":237,"line":380},[235,1668,1316],{"class":499},[235,1670,1671,1673,1675],{"class":237,"line":465},[235,1672,1529],{"class":499},[235,1674,1557],{"class":495},[235,1676,1486],{"class":499},[13,1678,1679],{},"index.jsは以下の様に記述します。",[226,1681,1684],{"className":1258,"code":1682,"filename":1683,"language":1261,"meta":231,"style":231},"window.axios = require('axios');\nwindow.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';\n\nimport Vue from 'vue';\n\nimport Vuex from 'vuex';\nimport index from '.\u002Fstore';\nVue.use(Vuex);\nconst store = new Vuex.Store(index);\n\nimport VueRouter from 'vue-router'\nimport routes from '.\u002Frouter';\nVue.use(VueRouter)\nconst router =  new VueRouter({mode: 'history',routes})\n\nconst app = new Vue({\n    store,\n    router,\n}).$mount(\"#app\");\n","index.js",[39,1685,1686,1717,1761,1765,1782,1786,1804,1822,1837,1862,1866,1882,1899,1910,1953,1957,1976,1984,1991],{"__ignoreMap":231},[235,1687,1688,1691,1694,1697,1699,1702,1705,1707,1710,1712,1715],{"class":237,"line":238},[235,1689,1690],{"class":519},"window",[235,1692,1693],{"class":499},".",[235,1695,1696],{"class":519},"axios ",[235,1698,1381],{"class":499},[235,1700,1701],{"class":252}," require",[235,1703,1704],{"class":519},"(",[235,1706,1344],{"class":499},[235,1708,1709],{"class":245},"axios",[235,1711,1344],{"class":499},[235,1713,1714],{"class":519},")",[235,1716,1347],{"class":499},[235,1718,1719,1721,1723,1725,1727,1730,1732,1735,1737,1740,1742,1745,1747,1750,1752,1754,1757,1759],{"class":237,"line":249},[235,1720,1690],{"class":519},[235,1722,1693],{"class":499},[235,1724,1709],{"class":519},[235,1726,1693],{"class":499},[235,1728,1729],{"class":519},"defaults",[235,1731,1693],{"class":499},[235,1733,1734],{"class":519},"headers",[235,1736,1693],{"class":499},[235,1738,1739],{"class":519},"common[",[235,1741,1344],{"class":499},[235,1743,1744],{"class":245},"X-Requested-With",[235,1746,1344],{"class":499},[235,1748,1749],{"class":519},"] ",[235,1751,1381],{"class":499},[235,1753,503],{"class":499},[235,1755,1756],{"class":245},"XMLHttpRequest",[235,1758,1344],{"class":499},[235,1760,1347],{"class":499},[235,1762,1763],{"class":237,"line":259},[235,1764,263],{"emptyLinePlaceholder":262},[235,1766,1767,1769,1772,1774,1776,1778,1780],{"class":237,"line":266},[235,1768,1330],{"class":1268},[235,1770,1771],{"class":519}," Vue ",[235,1773,1336],{"class":1268},[235,1775,503],{"class":499},[235,1777,1207],{"class":245},[235,1779,1344],{"class":499},[235,1781,1347],{"class":499},[235,1783,1784],{"class":237,"line":275},[235,1785,263],{"emptyLinePlaceholder":262},[235,1787,1788,1790,1793,1795,1797,1800,1802],{"class":237,"line":283},[235,1789,1330],{"class":1268},[235,1791,1792],{"class":519}," Vuex ",[235,1794,1336],{"class":1268},[235,1796,503],{"class":499},[235,1798,1799],{"class":245},"vuex",[235,1801,1344],{"class":499},[235,1803,1347],{"class":499},[235,1805,1806,1808,1811,1813,1815,1818,1820],{"class":237,"line":291},[235,1807,1330],{"class":1268},[235,1809,1810],{"class":519}," index ",[235,1812,1336],{"class":1268},[235,1814,503],{"class":499},[235,1816,1817],{"class":245},".\u002Fstore",[235,1819,1344],{"class":499},[235,1821,1347],{"class":499},[235,1823,1824,1827,1829,1832,1835],{"class":237,"line":298},[235,1825,1826],{"class":519},"Vue",[235,1828,1693],{"class":499},[235,1830,1831],{"class":252},"use",[235,1833,1834],{"class":519},"(Vuex)",[235,1836,1347],{"class":499},[235,1838,1839,1841,1844,1846,1849,1852,1854,1857,1860],{"class":237,"line":380},[235,1840,1375],{"class":1374},[235,1842,1843],{"class":519}," store ",[235,1845,1381],{"class":499},[235,1847,1848],{"class":499}," new",[235,1850,1851],{"class":519}," Vuex",[235,1853,1693],{"class":499},[235,1855,1856],{"class":252},"Store",[235,1858,1859],{"class":519},"(index)",[235,1861,1347],{"class":499},[235,1863,1864],{"class":237,"line":465},[235,1865,263],{"emptyLinePlaceholder":262},[235,1867,1868,1870,1873,1875,1877,1880],{"class":237,"line":590},[235,1869,1330],{"class":1268},[235,1871,1872],{"class":519}," VueRouter ",[235,1874,1336],{"class":1268},[235,1876,503],{"class":499},[235,1878,1879],{"class":245},"vue-router",[235,1881,509],{"class":499},[235,1883,1884,1886,1888,1890,1892,1895,1897],{"class":237,"line":604},[235,1885,1330],{"class":1268},[235,1887,1378],{"class":519},[235,1889,1336],{"class":1268},[235,1891,503],{"class":499},[235,1893,1894],{"class":245},".\u002Frouter",[235,1896,1344],{"class":499},[235,1898,1347],{"class":499},[235,1900,1901,1903,1905,1907],{"class":237,"line":612},[235,1902,1826],{"class":519},[235,1904,1693],{"class":499},[235,1906,1831],{"class":252},[235,1908,1909],{"class":519},"(VueRouter)\n",[235,1911,1912,1914,1917,1919,1922,1925,1927,1930,1933,1935,1937,1940,1942,1944,1947,1950],{"class":237,"line":623},[235,1913,1375],{"class":1374},[235,1915,1916],{"class":519}," router ",[235,1918,1381],{"class":499},[235,1920,1921],{"class":499},"  new",[235,1923,1924],{"class":252}," VueRouter",[235,1926,1704],{"class":519},[235,1928,1929],{"class":499},"{",[235,1931,1932],{"class":495},"mode",[235,1934,500],{"class":499},[235,1936,503],{"class":499},[235,1938,1939],{"class":245},"history",[235,1941,1344],{"class":499},[235,1943,1403],{"class":499},[235,1945,1946],{"class":519},"routes",[235,1948,1949],{"class":499},"}",[235,1951,1952],{"class":519},")\n",[235,1954,1955],{"class":237,"line":634},[235,1956,263],{"emptyLinePlaceholder":262},[235,1958,1959,1961,1964,1966,1968,1971,1973],{"class":237,"line":4},[235,1960,1375],{"class":1374},[235,1962,1963],{"class":519}," app ",[235,1965,1381],{"class":499},[235,1967,1848],{"class":499},[235,1969,1970],{"class":252}," Vue",[235,1972,1704],{"class":519},[235,1974,1975],{"class":499},"{\n",[235,1977,1978,1981],{"class":237,"line":652},[235,1979,1980],{"class":519},"    store",[235,1982,1983],{"class":499},",\n",[235,1985,1986,1989],{"class":237,"line":660},[235,1987,1988],{"class":519},"    router",[235,1990,1983],{"class":499},[235,1992,1993,1995,1997,1999,2002,2004,2006,2009,2011,2013],{"class":237,"line":669},[235,1994,1949],{"class":499},[235,1996,1714],{"class":519},[235,1998,1693],{"class":499},[235,2000,2001],{"class":252},"$mount",[235,2003,1704],{"class":519},[235,2005,1516],{"class":499},[235,2007,2008],{"class":245},"#app",[235,2010,1516],{"class":499},[235,2012,1714],{"class":519},[235,2014,1347],{"class":499},[13,2016,2017,2018,2021],{},"axiosはLaravelインストール時に入っているのでそのまま利用しています。このファイルではVuexとVue-routerの連携を行い、最終的に",[39,2019,2020],{},"\u003Cdiv id=\"app\">\u003C\u002Fdiv>","にレンダリングされる様にします。",[13,2023,2024,2025,2028],{},"上記のファイルを作成した後、",[39,2026,2027],{},"webpac.mix.js","を修正します。",[81,2030,2031],{"id":2031},"mixファイルの作成",[13,2033,2034,2035,2038,2039,2042,2043,2046,2047,2050],{},"このようなVueのプロジェクトwebpackを使用しますがLaravelは簡単な記述で",[39,2036,2037],{},"resources","配下を簡単に",[39,2040,2041],{},"public\u002F","配下に出力してくれます。またvueやsassのコンパイル設定を",[39,2044,2045],{},"webpack.config.js","の様に書く必要がありません。どのファイルをコンパイル対象にするかなどは",[39,2048,2049],{},"webpack.mix.js","に記載します。以下の様に修正します。",[226,2052,2054],{"className":1258,"code":2053,"filename":2045,"language":1261,"meta":231,"style":231},"const mix = require('laravel-mix');\nconst path = require('path');\n\nmix.webpackConfig({\n    resolve: {\n      alias: {\n        '~js': path.resolve('.\u002Fresources\u002Fjs'),\n      }\n    }\n});\nmix.js('resources\u002Fjs\u002Fadmin\u002Findex.js', 'public\u002Fjs\u002Fadmin').vue();\n",[39,2055,2056,2080,2104,2108,2122,2131,2140,2171,2176,2181,2189],{"__ignoreMap":231},[235,2057,2058,2060,2063,2065,2067,2069,2071,2074,2076,2078],{"class":237,"line":238},[235,2059,1375],{"class":1374},[235,2061,2062],{"class":519}," mix ",[235,2064,1381],{"class":499},[235,2066,1701],{"class":252},[235,2068,1704],{"class":519},[235,2070,1344],{"class":499},[235,2072,2073],{"class":245},"laravel-mix",[235,2075,1344],{"class":499},[235,2077,1714],{"class":519},[235,2079,1347],{"class":499},[235,2081,2082,2084,2087,2089,2091,2093,2095,2098,2100,2102],{"class":237,"line":249},[235,2083,1375],{"class":1374},[235,2085,2086],{"class":519}," path ",[235,2088,1381],{"class":499},[235,2090,1701],{"class":252},[235,2092,1704],{"class":519},[235,2094,1344],{"class":499},[235,2096,2097],{"class":245},"path",[235,2099,1344],{"class":499},[235,2101,1714],{"class":519},[235,2103,1347],{"class":499},[235,2105,2106],{"class":237,"line":259},[235,2107,263],{"emptyLinePlaceholder":262},[235,2109,2110,2113,2115,2118,2120],{"class":237,"line":266},[235,2111,2112],{"class":519},"mix",[235,2114,1693],{"class":499},[235,2116,2117],{"class":252},"webpackConfig",[235,2119,1704],{"class":519},[235,2121,1975],{"class":499},[235,2123,2124,2127,2129],{"class":237,"line":275},[235,2125,2126],{"class":495},"    resolve",[235,2128,500],{"class":499},[235,2130,1275],{"class":499},[235,2132,2133,2136,2138],{"class":237,"line":283},[235,2134,2135],{"class":495},"      alias",[235,2137,500],{"class":499},[235,2139,1275],{"class":499},[235,2141,2142,2145,2147,2149,2151,2153,2155,2158,2160,2162,2165,2167,2169],{"class":237,"line":291},[235,2143,2144],{"class":499},"        '",[235,2146,1464],{"class":495},[235,2148,1344],{"class":499},[235,2150,500],{"class":499},[235,2152,1392],{"class":519},[235,2154,1693],{"class":499},[235,2156,2157],{"class":252},"resolve",[235,2159,1704],{"class":519},[235,2161,1344],{"class":499},[235,2163,2164],{"class":245},".\u002Fresources\u002Fjs",[235,2166,1344],{"class":499},[235,2168,1714],{"class":519},[235,2170,1983],{"class":499},[235,2172,2173],{"class":237,"line":298},[235,2174,2175],{"class":499},"      }\n",[235,2177,2178],{"class":237,"line":380},[235,2179,2180],{"class":499},"    }\n",[235,2182,2183,2185,2187],{"class":237,"line":465},[235,2184,1949],{"class":499},[235,2186,1714],{"class":519},[235,2188,1347],{"class":499},[235,2190,2191,2193,2195,2197,2199,2201,2204,2206,2208,2210,2213,2215,2217,2219,2221,2224],{"class":237,"line":590},[235,2192,2112],{"class":519},[235,2194,1693],{"class":499},[235,2196,1261],{"class":252},[235,2198,1704],{"class":519},[235,2200,1344],{"class":499},[235,2202,2203],{"class":245},"resources\u002Fjs\u002Fadmin\u002Findex.js",[235,2205,1344],{"class":499},[235,2207,1403],{"class":499},[235,2209,503],{"class":499},[235,2211,2212],{"class":245},"public\u002Fjs\u002Fadmin",[235,2214,1344],{"class":499},[235,2216,1714],{"class":519},[235,2218,1693],{"class":499},[235,2220,1207],{"class":252},[235,2222,2223],{"class":519},"()",[235,2225,1347],{"class":499},[13,2227,2228,2231,2232,2234],{},[39,2229,2230],{},"mix.webpackConfig","はのように",[39,2233,2045],{},"に書く様な他の細かいwebpackの設定を定義でき、ここではエイリアスを定義しています。",[13,2236,2237,2240,2241,2243,2244,2246],{},[39,2238,2239],{},"'~js': path.resolve('.\u002Fresources\u002Fjs')","という記述は",[39,2242,1464],{},"というパスの記載があった場合は「",[39,2245,1245],{},"までの絶対ルート」であるとwebpackに支持しています。この様にエイリアスを定義することで相対パスによるファイルの参照がなくなります。router.jsで以下の様に定義していましたが、",[226,2248,2250],{"className":1258,"code":2249,"filename":1323,"language":1261,"meta":231,"style":231},"import Home from '~js\u002Fvue\u002Fpage\u002FHome.vue';\nimport Profile from '~js\u002Fvue\u002Fpage\u002FProfile.vue';\n",[39,2251,2252,2268],{"__ignoreMap":231},[235,2253,2254,2256,2258,2260,2262,2264,2266],{"class":237,"line":238},[235,2255,1330],{"class":1268},[235,2257,1333],{"class":519},[235,2259,1336],{"class":1268},[235,2261,503],{"class":499},[235,2263,1341],{"class":245},[235,2265,1344],{"class":499},[235,2267,1347],{"class":499},[235,2269,2270,2272,2274,2276,2278,2280,2282],{"class":237,"line":249},[235,2271,1330],{"class":1268},[235,2273,1354],{"class":519},[235,2275,1336],{"class":1268},[235,2277,503],{"class":499},[235,2279,1361],{"class":245},[235,2281,1344],{"class":499},[235,2283,1347],{"class":499},[13,2285,2286],{},"もしエイリアスが使えない場合は",[226,2288,2290],{"className":1258,"code":2289,"filename":1323,"language":1261,"meta":231,"style":231},"import Home from '..\u002Fvue\u002Fpage\u002FHome.vue';\nimport Profile from '..\u002Fvue\u002Fpage\u002FProfile.vue';\n",[39,2291,2292,2309],{"__ignoreMap":231},[235,2293,2294,2296,2298,2300,2302,2305,2307],{"class":237,"line":238},[235,2295,1330],{"class":1268},[235,2297,1333],{"class":519},[235,2299,1336],{"class":1268},[235,2301,503],{"class":499},[235,2303,2304],{"class":245},"..\u002Fvue\u002Fpage\u002FHome.vue",[235,2306,1344],{"class":499},[235,2308,1347],{"class":499},[235,2310,2311,2313,2315,2317,2319,2322,2324],{"class":237,"line":249},[235,2312,1330],{"class":1268},[235,2314,1354],{"class":519},[235,2316,1336],{"class":1268},[235,2318,503],{"class":499},[235,2320,2321],{"class":245},"..\u002Fvue\u002Fpage\u002FProfile.vue",[235,2323,1344],{"class":499},[235,2325,1347],{"class":499},[13,2327,2328,2329,2331],{},"このように相対パスになってしまい、もし構成を変えようとした時に相対関係の修正が必要となります。その対策としてエイリアスを定めておくと今後の管理がしやすいです。",[39,2330,2239],{},"はjsディレクトリを参照する様にしています。",[13,2333,2334,2337,2338,2340,2341,2344],{},[39,2335,2336],{},"mix.js('resources\u002Fjs\u002Fadmin\u002Findex.js', 'public\u002Fjs\u002Fadmin').vue();"," にて",[39,2339,1683],{},"をソースとして",[39,2342,2343],{},"'public\u002Fjs\u002Fadmin'","配下に出力し、vueコンパイルを行う様に定義してます。",[13,2346,2347,2348,2351,2352,2355],{},"問題なければ",[39,2349,2350],{},"npm run prod","をプロジェクト ルートでコマンドを叩いてコンパイルを行います。完了後に",[39,2353,2354],{},"public\u002Fjs\u002Fadmin\u002Findex.js","というものが出力されていることを確認してください。",[81,2357,2358],{"id":2358},"反映の確認",[13,2360,2361,2362,2365,2366,2368,2369,2371],{},"jsファイルが出力されましたら",[39,2363,2364],{},"http:\u002F\u002Flocalhost:8005\u002Fsystem","にアクセスします。以下の様に表示され、",[39,2367,1526],{},"というリンクをクリックした際にURLが変わり、",[39,2370,1597],{},"に書かれた内容が表示されればSPAはできています。",[183,2373],{":src":2374,":center":186},"'vue_laravel_app\u002Fhomevue.png'",[13,2376,2377,2379,2380,2383],{},[39,2378,1526],{},"というリンクをクリック後 or ",[39,2381,2382],{},"http:\u002F\u002Flocalhost:8005\u002Fsystem\u002Fprofile","にアクセスしますと以下の様になります。",[183,2385],{":src":2386,":center":186},"'vue_laravel_app\u002Fprofilevue.png'",[13,2388,2389],{},"これでSPAが設定できたことを確認できました。",[74,2391,2393],{"id":2392},"次回は","次回は..",[13,2395,2396],{},"今回は環境構築とLaravel、Vue SPAのセットアップまでとなります。来週はweb apiを通じたSPAでの認証と簡単なプロフィールの更新機能を作成してみます。",[2398,2399,2400],"style",{},"html pre.shiki code .s5Dmg, html code.shiki .s5Dmg{--shiki-default:#FFCB6B}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .s6cf3, html code.shiki .s6cf3{--shiki-default:#89DDFF;--shiki-default-font-style:italic}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}",{"title":231,"searchDepth":259,"depth":259,"links":2402},[2403,2411,2416,2417,2424],{"id":76,"depth":249,"text":76,"children":2404},[2405,2410],{"id":83,"depth":259,"text":83,"children":2406},[2407,2408,2409],{"id":90,"depth":266,"text":90},{"id":124,"depth":266,"text":124},{"id":164,"depth":266,"text":164},{"id":178,"depth":259,"text":178},{"id":192,"depth":249,"text":192,"children":2412},[2413,2414,2415],{"id":325,"depth":259,"text":326},{"id":389,"depth":259,"text":389},{"id":478,"depth":259,"text":479},{"id":988,"depth":249,"text":989},{"id":1008,"depth":249,"text":1009,"children":2418},[2419,2420,2421,2422,2423],{"id":1015,"depth":259,"text":1016},{"id":1197,"depth":259,"text":1197},{"id":1238,"depth":259,"text":1239},{"id":2031,"depth":259,"text":2031},{"id":2358,"depth":259,"text":2358},{"id":2392,"depth":249,"text":2393},[2426],"devstack","2022-03-02","アプリの解説とシリーズの概要","md",{},"\u002Fseries\u002Fvue-laravel-app-1",{"title":8,"description":2428},"vue_laravel_app","Vue SPA x Laravelでつくる実務パチモンアプリ","series\u002Fvue-laravel-app-1",[970,1028,1261,1207],"vue_laravel_app\u002Fseries.png",null,"QS_7_9yqTbYmh1Gt9XAVtEnkXBXd1wzlct80a3xdm_U",{"id":2441,"title":2442,"body":2443,"category":3868,"createdAt":3869,"description":2428,"extension":2429,"index":238,"meta":3870,"navigation":262,"path":3871,"publish":262,"seo":3872,"series":3873,"seriesTitle":3874,"stem":3875,"tag":3876,"thumbnail":3878,"updatedAt":2438,"__hash__":3879},"series\u002Fseries\u002Flaravel-to-django-1.md","Laravel使いがDjangoでwebアプリを作るよその１：アプリの概要と環境構築",{"type":10,"value":2444,"toc":3848},[2445,2448,2451,2454,2465,2468,2471,2474,2494,2497,2500,2519,2521,2527,2568,2575,2578,2581,2622,2625,2627,2632,2905,2910,2920,2934,2941,2958,2981,2986,2992,3007,3011,3014,3017,3019,3041,3043,3072,3074,3082,3085,3088,3092,3095,3098,3109,3170,3173,3229,3242,3245,3248,3323,3339,3342,3355,3636,3639,3657,3660,3667,3673,3680,3686,3696,3704,3715,3734,3740,3749,3752,3803,3810,3839,3842,3845],[13,2446,2447],{},"こんにちはjunです。最近Laravelでシステムを作る機会が何回かあったので、Laravelによる構築がかなり得意になってきました。しかし会社の方で「pythonを使用した機械学習や統計の機能をwebアプリとして使用できるアプリを開発したい」という企画がでてきました。",[13,2449,2450],{},"pythonで機械学習や統計の処理（モデル）を作成し、UIやロジックの部分をwebフレームワークで作成します。できたらwebフレームワークの処理部分にて統計ロジックをimportして、データの引き渡しを行いたいと思いました。しかしLaravelはPHPなので連携が難しいです。",[13,2452,2453],{},"そのため「Python製のwebフレームワークであるdjangoを利用できないか？」と思い、Djangoの勉強をスタートしました。チュートリアルはひとまず終えましたが、Laravelをやっていたおかげで",[17,2455,2456,2459,2462],{},[20,2457,2458],{},"どんな機能があると便利か？",[20,2460,2461],{},"Laravelのこの機能がDjangoのこの記述にあたるのか？",[20,2463,2464],{},"実務上必要となるこの機能を実装するには何を使えばいいのか？",[13,2466,2467],{},"という視点を用いて学習することができました。このシリーズでは「Laravelを使っていたPHP開発者がDjangoとPythonを使用してアプリを作る」ことを通じてDjangoの学習をしたいと思っています。記事内ではDjangoにおける実装とLaravelにおける実装をリンクしながら解説していきたいと思います。",[13,2469,2470],{},"利用するDjangoのバージョンは3.2で、Laravelは8とします。第１回のこの記事は環境構築とアプリの概要、Djangoの開発コンセプトやディレクトリ説明がメインとなります。",[13,2472,2473],{},"なおシリーズでは以下の内容を押さえていることを前提としています",[17,2475,2476,2479,2482,2485,2488,2491],{},[20,2477,2478],{},"Laravelの実務開発経験があるまたは、完成したアプリを作ったことがある。",[20,2480,2481],{},"Laravelのコンセプトやディレクトリ構成を知っている。",[20,2483,2484],{},"PythonがPCにインストールされ、基本的な記述を理解している。",[20,2486,2487],{},"PHPを触ったことが多いが、Pythonはそれほどない。",[20,2489,2490],{},"Djangoのチュートリアルは行っており、必須の内容は知っている。",[20,2492,2493],{},"MVCの概念を知っている、使える。",[13,2495,2496],{},"それでは早速始めていきましょう。",[13,2498,2499],{},"参考資料",[17,2501,2502,2509,2512],{},[20,2503,2504],{},[197,2505,2508],{"href":2506,"rel":2507},"https:\u002F\u002Fdocs.djangoproject.com\u002Fja\u002F3.2",[201],"Django ドキュメント3.2",[20,2510,2511],{},"書籍『現場で使える Django の教科書』",[20,2513,2514],{},[197,2515,2518],{"href":2516,"rel":2517},"https:\u002F\u002Fdocs.docker.jp\u002Fcompose\u002Fdjango.html",[201],"クィックスタート: Compose と Django - Docker ドキュメント",[74,2520,192],{"id":192},[13,2522,2523,2524,2526],{},"今回の環境構築はdockerを用いて作成しようと思います。適当なディレクトリ を作成し、",[39,2525,223],{},"とビルドファイルを作成します。",[226,2528,2530],{"className":228,"code":2529,"language":230,"meta":231,"style":231},"mkdir laravel_django\ncd laravel_django\n\ntouch Dockerfile\ntouch docker-compose.yaml\nmkdir source\n",[39,2531,2532,2539,2545,2549,2555,2561],{"__ignoreMap":231},[235,2533,2534,2536],{"class":237,"line":238},[235,2535,242],{"class":241},[235,2537,2538],{"class":245}," laravel_django\n",[235,2540,2541,2543],{"class":237,"line":249},[235,2542,253],{"class":252},[235,2544,2538],{"class":245},[235,2546,2547],{"class":237,"line":259},[235,2548,263],{"emptyLinePlaceholder":262},[235,2550,2551,2553],{"class":237,"line":266},[235,2552,269],{"class":241},[235,2554,303],{"class":245},[235,2556,2557,2559],{"class":237,"line":275},[235,2558,269],{"class":241},[235,2560,272],{"class":245},[235,2562,2563,2565],{"class":237,"line":283},[235,2564,242],{"class":241},[235,2566,2567],{"class":245}," source\n",[13,2569,2570,2571,2574],{},"まずはDockerfileでDjangoが稼働するpythonの環境を作成します。なお、この環境ではApacheやnginxといったwebサーバーとpythonとの連携設定は行わないものとします。コンテナ内で",[39,2572,2573],{},"run serve","を行います。",[81,2576,316],{"id":2577},"dockerfile",[13,2579,2580],{},"Dockerfileを以下の様に記述します。",[226,2582,2585],{"className":2583,"code":2584,"language":316,"meta":231,"style":231},"language-Dockerfile shiki shiki-themes material-theme-ocean","FROM python:3\nENV PYTHONUNBUFFERED 1\nRUN mkdir \u002Fcode\nWORKDIR \u002Fcode\nADD requirements.txt \u002Fcode\u002F\nRUN pip install -r requirements.txt\nADD . \u002Fcode\u002F\n",[39,2586,2587,2592,2597,2602,2607,2612,2617],{"__ignoreMap":231},[235,2588,2589],{"class":237,"line":238},[235,2590,2591],{},"FROM python:3\n",[235,2593,2594],{"class":237,"line":249},[235,2595,2596],{},"ENV PYTHONUNBUFFERED 1\n",[235,2598,2599],{"class":237,"line":259},[235,2600,2601],{},"RUN mkdir \u002Fcode\n",[235,2603,2604],{"class":237,"line":266},[235,2605,2606],{},"WORKDIR \u002Fcode\n",[235,2608,2609],{"class":237,"line":275},[235,2610,2611],{},"ADD requirements.txt \u002Fcode\u002F\n",[235,2613,2614],{"class":237,"line":283},[235,2615,2616],{},"RUN pip install -r requirements.txt\n",[235,2618,2619],{"class":237,"line":291},[235,2620,2621],{},"ADD . \u002Fcode\u002F\n",[13,2623,2624],{},"python3 イメージをもとにpythonが動く環境を用意します。",[81,2626,876],{"id":876},[13,2628,2629,2631],{},[39,2630,223],{},"は以下の様に記述します。",[226,2633,2635],{"className":485,"code":2634,"filename":223,"language":488,"meta":231,"style":231},"version: '3'\n\nservices:\n  db:\n    image: mysql:5.7\n    command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci\n    environment:\n      MYSQL_DATABASE: preform\n      MYSQL_USER: test\n      MYSQL_PASSWORD: testtest\n      MYSQL_ROOT_PASSWORD: rootroot\n      TZ: Asia\u002FTokyo\n    ports: \n      - \"3306:3306\"\n    volumes: \n      - djangodemo:\u002Fvar\u002Flib\u002Fmysql\n  phpmyadmin:\n    image: phpmyadmin\n    ports:\n      - \"8080:80\"\n    environment:\n     - PMA_ARBITRARY=1\n     - PMA_HOST=db:3306\n     - PMA_USER=root\n     - PMA_PASSWORD=rootroot\n  web:\n    build: .\n    command: python3 manage.py runserver 0.0.0.0:8000\n    volumes:\n      - .\u002Fsource:\u002Fcode\n    ports:\n      - \"8000:8000\"\n    depends_on:\n      - db\nvolumes: \n  djangodemo: {}\n",[39,2636,2637,2649,2653,2659,2665,2673,2681,2687,2695,2703,2711,2719,2727,2735,2745,2753,2760,2766,2774,2780,2790,2796,2802,2808,2814,2820,2827,2837,2846,2852,2859,2865,2876,2882,2888,2896],{"__ignoreMap":231},[235,2638,2639,2641,2643,2645,2647],{"class":237,"line":238},[235,2640,496],{"class":495},[235,2642,500],{"class":499},[235,2644,503],{"class":499},[235,2646,506],{"class":245},[235,2648,509],{"class":499},[235,2650,2651],{"class":237,"line":249},[235,2652,263],{"emptyLinePlaceholder":262},[235,2654,2655,2657],{"class":237,"line":259},[235,2656,514],{"class":495},[235,2658,528],{"class":499},[235,2660,2661,2663],{"class":237,"line":266},[235,2662,696],{"class":495},[235,2664,528],{"class":499},[235,2666,2667,2669,2671],{"class":237,"line":275},[235,2668,615],{"class":495},[235,2670,500],{"class":499},[235,2672,708],{"class":245},[235,2674,2675,2677,2679],{"class":237,"line":283},[235,2676,714],{"class":495},[235,2678,500],{"class":499},[235,2680,719],{"class":245},[235,2682,2683,2685],{"class":237,"line":291},[235,2684,655],{"class":495},[235,2686,528],{"class":499},[235,2688,2689,2691,2693],{"class":237,"line":298},[235,2690,732],{"class":495},[235,2692,500],{"class":499},[235,2694,737],{"class":245},[235,2696,2697,2699,2701],{"class":237,"line":380},[235,2698,743],{"class":495},[235,2700,500],{"class":499},[235,2702,748],{"class":245},[235,2704,2705,2707,2709],{"class":237,"line":465},[235,2706,754],{"class":495},[235,2708,500],{"class":499},[235,2710,759],{"class":245},[235,2712,2713,2715,2717],{"class":237,"line":590},[235,2714,765],{"class":495},[235,2716,500],{"class":499},[235,2718,770],{"class":245},[235,2720,2721,2723,2725],{"class":237,"line":604},[235,2722,776],{"class":495},[235,2724,500],{"class":499},[235,2726,781],{"class":245},[235,2728,2729,2731,2733],{"class":237,"line":612},[235,2730,583],{"class":495},[235,2732,500],{"class":499},[235,2734,520],{"class":519},[235,2736,2737,2739,2741,2743],{"class":237,"line":623},[235,2738,552],{"class":499},[235,2740,595],{"class":499},[235,2742,800],{"class":245},[235,2744,601],{"class":499},[235,2746,2747,2749,2751],{"class":237,"line":634},[235,2748,560],{"class":495},[235,2750,500],{"class":499},[235,2752,520],{"class":519},[235,2754,2755,2757],{"class":237,"line":4},[235,2756,552],{"class":499},[235,2758,2759],{"class":245}," djangodemo:\u002Fvar\u002Flib\u002Fmysql\n",[235,2761,2762,2764],{"class":237,"line":652},[235,2763,607],{"class":495},[235,2765,528],{"class":499},[235,2767,2768,2770,2772],{"class":237,"line":660},[235,2769,615],{"class":495},[235,2771,500],{"class":499},[235,2773,620],{"class":245},[235,2775,2776,2778],{"class":237,"line":669},[235,2777,583],{"class":495},[235,2779,528],{"class":499},[235,2781,2782,2784,2786,2788],{"class":237,"line":677},[235,2783,552],{"class":499},[235,2785,595],{"class":499},[235,2787,647],{"class":245},[235,2789,601],{"class":499},[235,2791,2792,2794],{"class":237,"line":685},[235,2793,655],{"class":495},[235,2795,528],{"class":499},[235,2797,2798,2800],{"class":237,"line":693},[235,2799,663],{"class":499},[235,2801,666],{"class":245},[235,2803,2804,2806],{"class":237,"line":701},[235,2805,663],{"class":499},[235,2807,674],{"class":245},[235,2809,2810,2812],{"class":237,"line":711},[235,2811,663],{"class":499},[235,2813,682],{"class":245},[235,2815,2816,2818],{"class":237,"line":722},[235,2817,663],{"class":499},[235,2819,690],{"class":245},[235,2821,2822,2825],{"class":237,"line":729},[235,2823,2824],{"class":495},"  web",[235,2826,528],{"class":499},[235,2828,2829,2831,2833],{"class":237,"line":740},[235,2830,533],{"class":495},[235,2832,500],{"class":499},[235,2834,2836],{"class":2835},"sx098"," .\n",[235,2838,2839,2841,2843],{"class":237,"line":751},[235,2840,714],{"class":495},[235,2842,500],{"class":499},[235,2844,2845],{"class":245}," python3 manage.py runserver 0.0.0.0:8000\n",[235,2847,2848,2850],{"class":237,"line":762},[235,2849,560],{"class":495},[235,2851,528],{"class":499},[235,2853,2854,2856],{"class":237,"line":773},[235,2855,552],{"class":499},[235,2857,2858],{"class":245}," .\u002Fsource:\u002Fcode\n",[235,2860,2861,2863],{"class":237,"line":784},[235,2862,583],{"class":495},[235,2864,528],{"class":499},[235,2866,2867,2869,2871,2874],{"class":237,"line":793},[235,2868,552],{"class":499},[235,2870,595],{"class":499},[235,2872,2873],{"class":245},"8000:8000",[235,2875,601],{"class":499},[235,2877,2878,2880],{"class":237,"line":805},[235,2879,543],{"class":495},[235,2881,528],{"class":499},[235,2883,2884,2886],{"class":237,"line":814},[235,2885,552],{"class":499},[235,2887,555],{"class":245},[235,2889,2890,2892,2894],{"class":237,"line":822},[235,2891,825],{"class":495},[235,2893,500],{"class":499},[235,2895,520],{"class":519},[235,2897,2898,2901,2903],{"class":237,"line":832},[235,2899,2900],{"class":495},"  djangodemo",[235,2902,500],{"class":499},[235,2904,840],{"class":499},[13,2906,2907,2909],{},[39,2908,865],{},"ではデータベースを定義しています。DjangoはSQLiteを使用した簡易的なDBが用意されています。ただし実務ではmysqlなどのDBサーバーを使用することが多いので、今回はmysqlを使用できる様にします。",[1224,2911,2914,2915],{"className":2912},[1227,2913],"alert-info","\nDjangoでmysqlを使用するためにはpythonモジュールのmysqlclientが必要です。この環境にはデフォルトでないので注意してください。他のDBサーバーを使用する場合は適宜ドライバをインストールしてください。\n",[197,2916,2919],{"target":2917,"href":2918},"_blank","https:\u002F\u002Fdocs.djangoproject.com\u002Fja\u002F3.2\u002Ftopics\u002Finstall\u002F#get-your-database-running","本家ドキュメントを参照",[13,2921,2922,2923,2926,2927,2929,2930,2933],{},"DB内部をすぐに確認できる様にphpmyadminを入れておきます。（好みなのでなくてもいいです。）\nそして",[39,2924,2925],{},"web","では同階層にある",[39,2928,316],{},"をビルドして実行できる様になります。立ち上げ時に",[39,2931,2932],{},"python3 manage.py runserver 0.0.0.0:8000","をしてwebサーバを立ち上げる設定にしています。",[13,2935,2936,2937,2940],{},"このコンテナを起動する前に ",[39,2938,2939],{},"requirements.txt"," をDockerfileと同じ階層に生成しておき、以下の内容を記述します。",[226,2942,2946],{"className":2943,"code":2944,"filename":2939,"language":2945,"meta":231,"style":231},"language-txt shiki shiki-themes material-theme-ocean","Django==3.2\nmysqlclient==2.1.0\n","txt",[39,2947,2948,2953],{"__ignoreMap":231},[235,2949,2950],{"class":237,"line":238},[235,2951,2952],{},"Django==3.2\n",[235,2954,2955],{"class":237,"line":249},[235,2956,2957],{},"mysqlclient==2.1.0\n",[13,2959,2960,2962,2963,2966,2967,2970,2971,2973,2974,2976,2977,2980],{},[39,2961,2939],{}," はpythonライブラリ管理ツールpipの設定ファイルです。npmの",[39,2964,2965],{},"package.json","やcomposerの",[39,2968,2969],{},"composer.json","といったものと同じです。pythonのモジュール管理はpipを使用することが多く、",[39,2972,2939],{}," は環境ないの依存するモジュールとそのバージョンを記述して配置しておきます。そして",[39,2975,2939],{}," がある階層で ",[39,2978,2979],{},"pip install","をすることで自動的に依存関係をインストールしてくれます。Django3.2とmysqlclientを記述しておきます。",[13,2982,2983,2985],{},[39,2984,2939],{},"を記述したら以下のコマンドを実行してDjangoのソースを作成します。",[226,2987,2990],{"className":2988,"code":2989,"language":933},[931],"docker-compose run web django-admin.py startproject djangodemo .\n",[39,2991,2989],{"__ignoreMap":231},[13,2993,2994,2995,2998,2999,3002,3003,3006],{},"すると",[39,2996,2997],{},"source\u002F","配下にDjangoのソースが生成されるはずです。\nそして準備が整ったら",[39,3000,3001],{},"docker-compose up -d --build"," を行って構築を行います。問題なくいった場合はブラウザにて ",[39,3004,3005],{},"localhost:8000"," を閲覧すると、Djangoのウェルカムページが表示されるはずです。",[183,3008],{":src":3009,":width":3010},"'laralve_to_django\u002Fwelcome.png'","'100%'",[74,3012,3013],{"id":3013},"アプリ概要",[13,3015,3016],{},"今回作成するアプリはn○teみたいなブログサービスを作成しようと思います。",[88,3018,90],{"id":90},[17,3020,3021,3023,3025,3027,3029,3031],{},[20,3022,95],{},[20,3024,98],{},[20,3026,101],{},[20,3028,104],{},[20,3030,107],{},[20,3032,110,3033],{},[17,3034,3035,3037,3039],{},[20,3036,115],{},[20,3038,118],{},[20,3040,121],{},[88,3042,124],{"id":124},[17,3044,3045,3047,3059,3061,3063,3065,3067,3069],{},[20,3046,129],{},[20,3048,132,3049],{},[17,3050,3051,3053,3055,3057],{},[20,3052,137],{},[20,3054,140],{},[20,3056,143],{},[20,3058,146],{},[20,3060,149],{},[20,3062,152],{},[20,3064,155],{},[20,3066,158],{},[20,3068,161],{},[20,3070,3071],{},"公開側からコメント可能",[88,3073,164],{"id":164},[17,3075,3076,3078,3080],{},[20,3077,169],{},[20,3079,172],{},[20,3081,175],{},[13,3083,3084],{},"一通りユーザーエンティティから、記事のCURD、リレーションの練習はできると思います。すべてのビューはpython側でレンダリングを行い、Vue.jsなどは今回使用しません。違う機会にrest apiを使用した構成を作ってみたいとおおいます。",[13,3086,3087],{},"デザインに関してはいつものbootstrapを使用します。",[74,3089,3091],{"id":3090},"djangoの大まかな解説","Djangoの大まかな解説",[13,3093,3094],{},"詳細なロジックなどは次の記事で解説したいと思います。今回はDjangoのディレクトリ構成やLaravelと似ているとこやリンクしている中核的な機能を解説したいと思います。",[81,3096,3097],{"id":3097},"コマンドによる制御",[13,3099,3100,3101,3104,3105,3108],{},"Laravelでは ",[39,3102,3103],{},"php artisan"," を使用することでコントローラーを作ったり、マイグレーションを実行したりできます。Djangoにも似た様なものがあります。使用する時はソーストップの ",[39,3106,3107],{},"manage.py"," がある箇所で実行します。例えば以下の様な機能があります。",[226,3110,3112],{"className":228,"code":3111,"language":230,"meta":231,"style":231},"# 簡易webサーバーを起動\npython3 manage.py runserve\n\n# マイグレーションを実行\npython3 manage.py migrate\n\n# アプリのテンプレートを作成\npython3 manage.py startapp APP_NAME\n\n",[39,3113,3114,3120,3131,3135,3140,3149,3153,3158],{"__ignoreMap":231},[235,3115,3116],{"class":237,"line":238},[235,3117,3119],{"class":3118},"sC9rS","# 簡易webサーバーを起動\n",[235,3121,3122,3125,3128],{"class":237,"line":249},[235,3123,3124],{"class":241},"python3",[235,3126,3127],{"class":245}," manage.py",[235,3129,3130],{"class":245}," runserve\n",[235,3132,3133],{"class":237,"line":259},[235,3134,263],{"emptyLinePlaceholder":262},[235,3136,3137],{"class":237,"line":266},[235,3138,3139],{"class":3118},"# マイグレーションを実行\n",[235,3141,3142,3144,3146],{"class":237,"line":275},[235,3143,3124],{"class":241},[235,3145,3127],{"class":245},[235,3147,3148],{"class":245}," migrate\n",[235,3150,3151],{"class":237,"line":283},[235,3152,263],{"emptyLinePlaceholder":262},[235,3154,3155],{"class":237,"line":291},[235,3156,3157],{"class":3118},"# アプリのテンプレートを作成\n",[235,3159,3160,3162,3164,3167],{"class":237,"line":298},[235,3161,3124],{"class":241},[235,3163,3127],{"class":245},[235,3165,3166],{"class":245}," startapp",[235,3168,3169],{"class":245}," APP_NAME\n",[13,3171,3172],{},"Laravelで例えると",[226,3174,3176],{"className":228,"code":3175,"language":230,"meta":231,"style":231},"# 簡易webサーバーを起動\nphp artisan serve\n\n# マイグレーションを実行\nphp artisan migrate\n\n# コントローラーのテンプレートを作成\nphp artisan make:controller CONTROLLER_NAME\n\n",[39,3177,3178,3182,3192,3196,3200,3208,3212,3217],{"__ignoreMap":231},[235,3179,3180],{"class":237,"line":238},[235,3181,3119],{"class":3118},[235,3183,3184,3186,3189],{"class":237,"line":249},[235,3185,1028],{"class":241},[235,3187,3188],{"class":245}," artisan",[235,3190,3191],{"class":245}," serve\n",[235,3193,3194],{"class":237,"line":259},[235,3195,263],{"emptyLinePlaceholder":262},[235,3197,3198],{"class":237,"line":266},[235,3199,3139],{"class":3118},[235,3201,3202,3204,3206],{"class":237,"line":275},[235,3203,1028],{"class":241},[235,3205,3188],{"class":245},[235,3207,3148],{"class":245},[235,3209,3210],{"class":237,"line":283},[235,3211,263],{"emptyLinePlaceholder":262},[235,3213,3214],{"class":237,"line":291},[235,3215,3216],{"class":3118},"# コントローラーのテンプレートを作成\n",[235,3218,3219,3221,3223,3226],{"class":237,"line":298},[235,3220,1028],{"class":241},[235,3222,3188],{"class":245},[235,3224,3225],{"class":245}," make:controller",[235,3227,3228],{"class":245}," CONTROLLER_NAME\n",[13,3230,3231,3232,3235,3236,3241],{},"以上の様になります。この ",[39,3233,3234],{},"python3 manage.py","で実行できる内容は",[197,3237,3240],{"href":3238,"rel":3239},"https:\u002F\u002Fdocs.djangoproject.com\u002Fja\u002F3.2\u002Fref\u002Fdjango-admin\u002F",[201],"こちらのドキュメント","で知ることができます。",[81,3243,3244],{"id":3244},"ディレクトリとファイルの構成",[13,3246,3247],{},"初期のファイル構成は以下の様になっています。",[226,3249,3251],{"className":228,"code":3250,"language":230,"meta":231,"style":231},"├── db.sqlite3\n├── djangodemo\n│   ├── __init__.py\n│   ├── asgi.py\n│   ├── settings.py\n│   ├── urls.py\n│   └── wsgi.py\n├── manage.py\n",[39,3252,3253,3261,3268,3279,3288,3297,3306,3316],{"__ignoreMap":231},[235,3254,3255,3258],{"class":237,"line":238},[235,3256,3257],{"class":241},"├──",[235,3259,3260],{"class":245}," db.sqlite3\n",[235,3262,3263,3265],{"class":237,"line":249},[235,3264,3257],{"class":241},[235,3266,3267],{"class":245}," djangodemo\n",[235,3269,3270,3273,3276],{"class":237,"line":259},[235,3271,3272],{"class":241},"│",[235,3274,3275],{"class":245},"   ├──",[235,3277,3278],{"class":245}," __init__.py\n",[235,3280,3281,3283,3285],{"class":237,"line":266},[235,3282,3272],{"class":241},[235,3284,3275],{"class":245},[235,3286,3287],{"class":245}," asgi.py\n",[235,3289,3290,3292,3294],{"class":237,"line":275},[235,3291,3272],{"class":241},[235,3293,3275],{"class":245},[235,3295,3296],{"class":245}," settings.py\n",[235,3298,3299,3301,3303],{"class":237,"line":283},[235,3300,3272],{"class":241},[235,3302,3275],{"class":245},[235,3304,3305],{"class":245}," urls.py\n",[235,3307,3308,3310,3313],{"class":237,"line":291},[235,3309,3272],{"class":241},[235,3311,3312],{"class":245},"   └──",[235,3314,3315],{"class":245}," wsgi.py\n",[235,3317,3318,3320],{"class":237,"line":298},[235,3319,3257],{"class":241},[235,3321,3322],{"class":245}," manage.py\n",[13,3324,3325,3328,3329,3331,3332,1208,3335,3338],{},[39,3326,3327],{},"djangodemo\u002F"," はDjangoのアプリを最初に作成した時に生成されるディレクトリです。",[39,3330,3327],{}," では特に ",[39,3333,3334],{},"settings.py",[39,3336,3337],{},"urls.py","が重要です。",[88,3340,3341],{"id":3341},"設定ファイル",[13,3343,3344,3346,3347,3350,3351,3354],{},[39,3345,3334],{}," はLaravelでいう",[39,3348,3349],{},"config\u002F","ディレクトリ配下のファイルたちを一つにまとめた様なものになっています。特に",[39,3352,3353],{},"config\u002Fapp.php","の記述が近いかもしれません。一部抜粋して解説します。",[226,3356,3360],{"className":3357,"code":3358,"filename":3334,"language":3359,"meta":231,"style":231},"language-python shiki shiki-themes material-theme-ocean","BASE_DIR = Path(__file__).resolve().parent.parent\n\nSECRET_KEY = 'hogehoge'\n\n# SECURITY WARNING: don't run with debug turned on in production!\n# Laravelのapp.debugと同じ\nDEBUG = True\n\n# Application definition\n# Djangoが利用可能なアプリ（モジュール）Laravelにはないかも。\nINSTALLED_APPS = [\n    'django.contrib.admin',\n    'django.contrib.auth',\n    'django.contrib.contenttypes',\n    'django.contrib.sessions',\n    'django.contrib.messages',\n    'django.contrib.staticfiles',\n]\n\n# laravel の app\u002FHttp\u002FKernel.php のmiddlewareみたいなもの\nMIDDLEWARE = [\n    'django.middleware.security.SecurityMiddleware',\n    'django.contrib.sessions.middleware.SessionMiddleware',\n    'django.middleware.common.CommonMiddleware',\n    'django.middleware.csrf.CsrfViewMiddleware',\n    'django.contrib.auth.middleware.AuthenticationMiddleware',\n    'django.contrib.messages.middleware.MessageMiddleware',\n    'django.middleware.clickjacking.XFrameOptionsMiddleware',\n]\n\n# Laravelのroutesみたいなもの。\nROOT_URLCONF = 'djangodemo.urls'\n\n# Laravelのビューテンプレートが resource\u002Fviewであることを決めている様なことと同じ。DjangoないのTemplateの読み込み先を定義している\nTEMPLATES = [\n    # ...\n]\n\nWSGI_APPLICATION = 'djangodemo.wsgi.application'\n\n# Laravelのconfig\u002Fdatabase.phpと同じ。使用するDBドライバやアクセス情報を記述。\nDATABASES = {\n    'default': {\n        'ENGINE': 'django.db.backends.mysql',\n        'NAME': 'preform',\n        'USER': 'root',\n        'PASSWORD': 'rootroot',\n        'HOST': 'db',\n        'PORT': '3306',\n    }\n}\n\n# 静的ファイルを配置する箇所。Laravelのstorageディレクトリ を定義している様なもの。\nSTATIC_URL = '\u002Fstatic\u002F'\n\n","python",[39,3361,3362,3367,3371,3376,3380,3385,3390,3395,3399,3404,3409,3414,3419,3424,3429,3434,3439,3444,3448,3452,3457,3462,3467,3472,3477,3482,3487,3492,3497,3501,3505,3510,3515,3519,3524,3529,3534,3539,3544,3550,3555,3561,3567,3573,3579,3585,3591,3597,3603,3609,3614,3619,3624,3630],{"__ignoreMap":231},[235,3363,3364],{"class":237,"line":238},[235,3365,3366],{},"BASE_DIR = Path(__file__).resolve().parent.parent\n",[235,3368,3369],{"class":237,"line":249},[235,3370,263],{"emptyLinePlaceholder":262},[235,3372,3373],{"class":237,"line":259},[235,3374,3375],{},"SECRET_KEY = 'hogehoge'\n",[235,3377,3378],{"class":237,"line":266},[235,3379,263],{"emptyLinePlaceholder":262},[235,3381,3382],{"class":237,"line":275},[235,3383,3384],{},"# SECURITY WARNING: don't run with debug turned on in production!\n",[235,3386,3387],{"class":237,"line":283},[235,3388,3389],{},"# Laravelのapp.debugと同じ\n",[235,3391,3392],{"class":237,"line":291},[235,3393,3394],{},"DEBUG = True\n",[235,3396,3397],{"class":237,"line":298},[235,3398,263],{"emptyLinePlaceholder":262},[235,3400,3401],{"class":237,"line":380},[235,3402,3403],{},"# Application definition\n",[235,3405,3406],{"class":237,"line":465},[235,3407,3408],{},"# Djangoが利用可能なアプリ（モジュール）Laravelにはないかも。\n",[235,3410,3411],{"class":237,"line":590},[235,3412,3413],{},"INSTALLED_APPS = [\n",[235,3415,3416],{"class":237,"line":604},[235,3417,3418],{},"    'django.contrib.admin',\n",[235,3420,3421],{"class":237,"line":612},[235,3422,3423],{},"    'django.contrib.auth',\n",[235,3425,3426],{"class":237,"line":623},[235,3427,3428],{},"    'django.contrib.contenttypes',\n",[235,3430,3431],{"class":237,"line":634},[235,3432,3433],{},"    'django.contrib.sessions',\n",[235,3435,3436],{"class":237,"line":4},[235,3437,3438],{},"    'django.contrib.messages',\n",[235,3440,3441],{"class":237,"line":652},[235,3442,3443],{},"    'django.contrib.staticfiles',\n",[235,3445,3446],{"class":237,"line":660},[235,3447,1443],{},[235,3449,3450],{"class":237,"line":669},[235,3451,263],{"emptyLinePlaceholder":262},[235,3453,3454],{"class":237,"line":677},[235,3455,3456],{},"# laravel の app\u002FHttp\u002FKernel.php のmiddlewareみたいなもの\n",[235,3458,3459],{"class":237,"line":685},[235,3460,3461],{},"MIDDLEWARE = [\n",[235,3463,3464],{"class":237,"line":693},[235,3465,3466],{},"    'django.middleware.security.SecurityMiddleware',\n",[235,3468,3469],{"class":237,"line":701},[235,3470,3471],{},"    'django.contrib.sessions.middleware.SessionMiddleware',\n",[235,3473,3474],{"class":237,"line":711},[235,3475,3476],{},"    'django.middleware.common.CommonMiddleware',\n",[235,3478,3479],{"class":237,"line":722},[235,3480,3481],{},"    'django.middleware.csrf.CsrfViewMiddleware',\n",[235,3483,3484],{"class":237,"line":729},[235,3485,3486],{},"    'django.contrib.auth.middleware.AuthenticationMiddleware',\n",[235,3488,3489],{"class":237,"line":740},[235,3490,3491],{},"    'django.contrib.messages.middleware.MessageMiddleware',\n",[235,3493,3494],{"class":237,"line":751},[235,3495,3496],{},"    'django.middleware.clickjacking.XFrameOptionsMiddleware',\n",[235,3498,3499],{"class":237,"line":762},[235,3500,1443],{},[235,3502,3503],{"class":237,"line":773},[235,3504,263],{"emptyLinePlaceholder":262},[235,3506,3507],{"class":237,"line":784},[235,3508,3509],{},"# Laravelのroutesみたいなもの。\n",[235,3511,3512],{"class":237,"line":793},[235,3513,3514],{},"ROOT_URLCONF = 'djangodemo.urls'\n",[235,3516,3517],{"class":237,"line":805},[235,3518,263],{"emptyLinePlaceholder":262},[235,3520,3521],{"class":237,"line":814},[235,3522,3523],{},"# Laravelのビューテンプレートが resource\u002Fviewであることを決めている様なことと同じ。DjangoないのTemplateの読み込み先を定義している\n",[235,3525,3526],{"class":237,"line":822},[235,3527,3528],{},"TEMPLATES = [\n",[235,3530,3531],{"class":237,"line":832},[235,3532,3533],{},"    # ...\n",[235,3535,3537],{"class":237,"line":3536},37,[235,3538,1443],{},[235,3540,3542],{"class":237,"line":3541},38,[235,3543,263],{"emptyLinePlaceholder":262},[235,3545,3547],{"class":237,"line":3546},39,[235,3548,3549],{},"WSGI_APPLICATION = 'djangodemo.wsgi.application'\n",[235,3551,3553],{"class":237,"line":3552},40,[235,3554,263],{"emptyLinePlaceholder":262},[235,3556,3558],{"class":237,"line":3557},41,[235,3559,3560],{},"# Laravelのconfig\u002Fdatabase.phpと同じ。使用するDBドライバやアクセス情報を記述。\n",[235,3562,3564],{"class":237,"line":3563},42,[235,3565,3566],{},"DATABASES = {\n",[235,3568,3570],{"class":237,"line":3569},43,[235,3571,3572],{},"    'default': {\n",[235,3574,3576],{"class":237,"line":3575},44,[235,3577,3578],{},"        'ENGINE': 'django.db.backends.mysql',\n",[235,3580,3582],{"class":237,"line":3581},45,[235,3583,3584],{},"        'NAME': 'preform',\n",[235,3586,3588],{"class":237,"line":3587},46,[235,3589,3590],{},"        'USER': 'root',\n",[235,3592,3594],{"class":237,"line":3593},47,[235,3595,3596],{},"        'PASSWORD': 'rootroot',\n",[235,3598,3600],{"class":237,"line":3599},48,[235,3601,3602],{},"        'HOST': 'db',\n",[235,3604,3606],{"class":237,"line":3605},49,[235,3607,3608],{},"        'PORT': '3306',\n",[235,3610,3612],{"class":237,"line":3611},50,[235,3613,2180],{},[235,3615,3617],{"class":237,"line":3616},51,[235,3618,1316],{},[235,3620,3622],{"class":237,"line":3621},52,[235,3623,263],{"emptyLinePlaceholder":262},[235,3625,3627],{"class":237,"line":3626},53,[235,3628,3629],{},"# 静的ファイルを配置する箇所。Laravelのstorageディレクトリ を定義している様なもの。\n",[235,3631,3633],{"class":237,"line":3632},54,[235,3634,3635],{},"STATIC_URL = '\u002Fstatic\u002F'\n",[88,3637,3638],{"id":3638},"ルーティング",[13,3640,3641,3642,3644,3645,3648,3649,3652,3653,3656],{},"次は",[39,3643,3337],{},"です。これはLaravelでいうところの ",[39,3646,3647],{},"routes\u002F","と内容的には一緒です。ここでHTTPリクエストに対する処理を結びつけています。Urlディスパッチャと言われています。Laravelでは",[39,3650,3651],{},"web.php","や",[39,3654,3655],{},"api.php","などいくらか分かれていますが、Djangoではデフォルトで一つです。詳細な記述は次回説明します。",[88,3658,3659],{"id":3659},"アプリ側のディレクトリ",[13,3661,3662,3663,3666],{},"次にアプリを作成します。Djangoは機能ごとにディレクトリを分割しアプリ(app)と呼んでいます。例としてユーザーのアカウント設定や認証を行う機能を作成するために、",[39,3664,3665],{},"account","というアプリを作成します。",[226,3668,3671],{"className":3669,"code":3670,"language":933},[931],"python3 manage.py startapp account\n",[39,3672,3670],{"__ignoreMap":231},[13,3674,3675,3676,3679],{},"ディレクトリ が一つ増えて、",[39,3677,3678],{},"account\u002F","というのが作成されたはずです。アプリ内には初期では以下の通りテンプレートが作成されます。",[226,3681,3684],{"className":3682,"code":3683,"language":933},[931],"├── account\n│   ├── __init__.py\n│   ├── admin.py\n│   ├── apps.py\n│   ├── migrations\n│   │   └── __init__.py\n│   ├── models.py\n│   ├── tests.py\n│   └── views.py\n├── db.sqlite3\n├── djangodemo\n├── manage.py\n",[39,3685,3683],{"__ignoreMap":231},[13,3687,3688,3689,3692,3693,3695],{},"Laravelは",[39,3690,3691],{},"app\u002F","配下にコントローラーやモデル、ジョブなどのクラスファイルを定義します。独立した機能であっても",[39,3694,3691],{},"配下に配置してクラス名などで判別する感じです。",[13,3697,3698,3699,3703],{},"しかしDjangoは",[3700,3701,3702],"strong",{},"機能ごとにディレクトリ を作成し、その機能に関連するモデル、ビュー、テストの設定を記述","します。ここがLaravelとすこし違うところです。",[13,3705,3706,3707,3710,3711,3714],{},"アプリを",[39,3708,3709],{},"setting.py","の",[39,3712,3713],{},"INSTALLED_APPS","に記述することで、",[226,3716,3718],{"className":3357,"code":3717,"language":3359,"meta":231,"style":231},"from account.model import Account\n\n# account\u002Fmodel.py のAccountクラスを使用する\n",[39,3719,3720,3725,3729],{"__ignoreMap":231},[235,3721,3722],{"class":237,"line":238},[235,3723,3724],{},"from account.model import Account\n",[235,3726,3727],{"class":237,"line":249},[235,3728,263],{"emptyLinePlaceholder":262},[235,3730,3731],{"class":237,"line":259},[235,3732,3733],{},"# account\u002Fmodel.py のAccountクラスを使用する\n",[13,3735,3736,3737,3739],{},"などそのアプリ内機能をモジュールとして使用できます。Laravel（PHP）風に解説すると、適切な名前空間を定義して任意のファイルで",[39,3738,1831],{},"する感じです。",[226,3741,3743],{"className":1025,"code":3742,"language":1028,"meta":231,"style":231},"use App\\Models\\Account\n",[39,3744,3745],{"__ignoreMap":231},[235,3746,3747],{"class":237,"line":238},[235,3748,3742],{},[13,3750,3751],{},"それぞれのファイルを説明します。",[17,3753,3754,3760,3769,3778,3787,3793],{},[20,3755,3756,3759],{},[39,3757,3758],{},"admin.py",":Djangoが提供する管理者画面でモデルの内容をどう表記するかを定義します。",[20,3761,3762,3765,3766,3768],{},[39,3763,3764],{},"apps.py",":アプリの設定ファイル。アプリの名前やデフォルトのプライマリーキーなどを設定でき、",[39,3767,3713],{},"に必要",[20,3770,3771,3774,3775],{},[39,3772,3773],{},"models.py",": アプリのモデルファイル。テーブルや使用するフィールドの定義などを行う。Laravelでいう",[39,3776,3777],{},"app\u002Fmodels",[20,3779,3780,3783,3784],{},[39,3781,3782],{},"tets.py",": アプリのテストファイル。テストコードを記述する。Laravelでいう",[39,3785,3786],{},"tests\u002F",[20,3788,3789,3792],{},[39,3790,3791],{},"views.py",": アプリのビューファイル。DjangoではビューはLaravelでいうControllerみたいな働きをする。",[20,3794,3795,3798,3799,3802],{},[39,3796,3797],{},"migrations\u002F",": DBに対する変更、マイグレーションファイルが格納されます。Laravelでいう、",[39,3800,3801],{},"database\u002Fmigrations","です。",[13,3804,3805,3806,3809],{},"のこっている ",[39,3807,3808],{},"__init__.py","ですがこれはpythonがディレクトリ をパッケージとして認識して、importできる様にするために必要なファイルです。python2ではこのファイルがないとimportが動作しません。python3からは不要ですが、後方互換性のために作っておいて損はありません。",[226,3811,3813],{"className":3357,"code":3812,"language":3359,"meta":231,"style":231},"from account.models\n\"\"\"\naccountディレクトリ（パッケージ）のmodels.py（モジュール）を読み込むということが,\n__init__.pyがあるとできる。\n\"\"\"\n",[39,3814,3815,3820,3825,3830,3835],{"__ignoreMap":231},[235,3816,3817],{"class":237,"line":238},[235,3818,3819],{},"from account.models\n",[235,3821,3822],{"class":237,"line":249},[235,3823,3824],{},"\"\"\"\n",[235,3826,3827],{"class":237,"line":259},[235,3828,3829],{},"accountディレクトリ（パッケージ）のmodels.py（モジュール）を読み込むということが,\n",[235,3831,3832],{"class":237,"line":266},[235,3833,3834],{},"__init__.pyがあるとできる。\n",[235,3836,3837],{"class":237,"line":275},[235,3838,3824],{},[74,3840,3841],{"id":3841},"今回はとりあえずここまで",[13,3843,3844],{},"ひとまずこの回ではディレクトリやファイルの説明までとしておきます。次回はユーザーモデルを使用した認証の実装とLaravelと比較したCRUDの一部（記事の作成）を実装します。",[2398,3846,3847],{},"html pre.shiki code .s5Dmg, html code.shiki .s5Dmg{--shiki-default:#FFCB6B}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .sx098, html code.shiki .sx098{--shiki-default:#F78C6C}html pre.shiki code .sC9rS, html code.shiki .sC9rS{--shiki-default:#464B5D;--shiki-default-font-style:italic}",{"title":231,"searchDepth":259,"depth":259,"links":3849},[3850,3854,3859,3867],{"id":192,"depth":249,"text":192,"children":3851},[3852,3853],{"id":2577,"depth":259,"text":316},{"id":876,"depth":259,"text":876},{"id":3013,"depth":249,"text":3013,"children":3855},[3856,3857,3858],{"id":90,"depth":266,"text":90},{"id":124,"depth":266,"text":124},{"id":164,"depth":266,"text":164},{"id":3090,"depth":249,"text":3091,"children":3860},[3861,3862],{"id":3097,"depth":259,"text":3097},{"id":3244,"depth":259,"text":3244,"children":3863},[3864,3865,3866],{"id":3341,"depth":266,"text":3341},{"id":3638,"depth":266,"text":3638},{"id":3659,"depth":266,"text":3659},{"id":3841,"depth":249,"text":3841},[2426],"2022-02-12",{},"\u002Fseries\u002Flaravel-to-django-1",{"title":2442,"description":2428},"laravel_to_django","Laravel使いがDjangoでwebアプリを作るよ","series\u002Flaravel-to-django-1",[3877,3359,970,1028],"django","laralve_to_django\u002Fthumbnail.png","k4TYlI4xXfHM5YMh4HtklCzEfD1cyqgLf2FCDeypoBY",{"id":3881,"title":3882,"body":3883,"category":5537,"createdAt":5538,"description":5539,"extension":2429,"index":2438,"meta":5540,"navigation":262,"path":5541,"publish":262,"seo":5542,"series":2438,"seriesTitle":2438,"stem":5543,"tag":5544,"thumbnail":5546,"updatedAt":2438,"__hash__":5547},"articles\u002Farticles\u002Fimplement-recaptcha.md","reCAPTCHAのフロントエンド実装とバックエンド実装（PHP・Laravel）をスクラッチで行う方法",{"type":10,"value":3884,"toc":5523},[3885,3888,3891,3894,3897,3901,3904,3907,3910,3913,3917,3926,3929,3936,3939,3942,3953,3956,3959,4139,4142,4153,4162,4166,4169,4408,4421,4424,4427,4903,4914,4936,4939,4942,4945,4948,4959,4962,5148,5159,5183,5199,5202,5241,5252,5255,5258,5303,5310,5313,5321,5346,5354,5363,5371,5377,5385,5388,5508,5511,5514,5517,5520],[13,3886,3887],{},"こんにちはjunです。皆さんはフォームを作成する時にBot・スパム対策を行っていますか？フォームというのは少し知識があれば、簡単にbot的に送信することができます。",[13,3889,3890],{},"curlでPOSTすることもあれば、javascriptを実行して機械的にフォームを送信することがきるので、スパム（嫌がらせ）やサーバーへの過剰負荷の原因となります。",[13,3892,3893],{},"この様な機械的な操作を防ぐために、よく「ロボットではありません」「画像に表示されている文字を入力してください」みたいなbotでは簡単に処理できないものを用意します。",[13,3895,3896],{},"しかしこの様な機能は自分で実装するのは大変です。そんな時に便利なのがreCAPTCHAです。",[74,3898,3900],{"id":3899},"recaptchaとは","reCAPTCHAとは？",[13,3902,3903],{},"reCAPTCHAはGoogleが無料で提供しているBot対策ツールです。現在v3までリリースされており、v2は画像を選択させたりチェックを入れるといったユーザーのアクションでbotかどうかを検証します。v3はその様なアクションを必要とせず、必要なスクリプトを入れるだけで検証ができます。これからの実装の場合はv3を入れることをお勧めします。",[74,3905,3906],{"id":3906},"実装内容",[13,3908,3909],{},"今回の解説ではv3でのフロントエンドの実装とバックエンドの実装を解説していきます。そして利用するreCAPTCHAはエンタープライズではなく、無料のものを利用します。バックエンドにはLaravel6(php)を用いて説明します。バックエンドは基本的にやることはどの言語・フレームワークでも特に差異はありません。reCAPTCHAを使う時にライブラリを使用することもありますが、いうてそれほど難しくないので今回はライブラリを使用しないスクラッチで実装します。",[13,3911,3912],{},"それでは解説を始めます。",[74,3914,3916],{"id":3915},"recaptchaのキーを手に入れる","reCAPTCHAのキーを手に入れる",[13,3918,3919,3920,3925],{},"reCAPTCHAはGooglenのAPIを使用することでbotか検証を行います。reCAPTCHAを利用するためにGoogleアカウントとreCAPTCHAのキーを登録します。",[197,3921,3924],{"href":3922,"rel":3923},"https:\u002F\u002Fwww.google.com\u002Frecaptcha\u002Fadmin\u002Fcreate",[201],"reCAPTCHAの登録ページ","にて保護対象のドメインを設定します。",[183,3927],{":src":3928,":width":3010},"'_mix\u002Fsch-2021-05-21 23.16.06.png'",[13,3930,3931,3932,3935],{},"ラベルは管理用の名前、タイプはv3を使用します。保護対象のドメインを登録します。ドメインは複数設定できます。この時、本番のドメインと",[39,3933,3934],{},"localhost","を登録しておくと開発・本番で利用できます。",[13,3937,3938],{},"設定を終わって「保存」しますと、キーが表示されますので保存しておきます。",[183,3940],{":src":3941,":width":3010},"'_mix\u002Frecaptcha-key.jpeg'",[13,3943,3944,3948,3949,3952],{},[3945,3946,3947],"em",{},"このサイトキーは、ユーザー表示するHTMLコードで利用します。"," という方のキーはタグで利用し、正直みられても問題ありません。フロント側のキーはドメインと合わせて保護対処のサイトであるかのチェックのためにあるだけだからです。逆にバックの方である ",[3945,3950,3951],{},"このサイトキーは、サイトとreCAPTCHA間の通信で利用します。"," は漏れてはいけません。",[74,3954,3955],{"id":3955},"フロントエンドの実装",[13,3957,3958],{},"それではフロントエンドを実装していきます。かなり簡略化して書いています。以下の様なフォームがあったとしましょう。",[226,3960,3964],{"className":3961,"code":3962,"language":3963,"meta":231,"style":231},"language-html shiki shiki-themes material-theme-ocean","\u003C!DOCTYPE html>\n\u003Chtml>\n    \u003Chead>\n      \u003C!---省略-->\n    \u003C\u002Fhead>\n\n    \u003Cbody>\n        \u003Cform method=\"post\">\n            \u003Cinput type=\"text\" name=\"test\" value=\"\">\n            \u003Cinput type=\"submit\" value=\"送信\">\n        \u003C\u002Fform>\n    \u003C\u002Fbody>\n\u003C\u002Fhtml>\n","html",[39,3965,3966,3979,3987,3996,4001,4009,4013,4022,4043,4084,4114,4123,4131],{"__ignoreMap":231},[235,3967,3968,3971,3974,3977],{"class":237,"line":238},[235,3969,3970],{"class":499},"\u003C!",[235,3972,3973],{"class":495},"DOCTYPE",[235,3975,3976],{"class":1374}," html",[235,3978,1486],{"class":499},[235,3980,3981,3983,3985],{"class":237,"line":249},[235,3982,1480],{"class":499},[235,3984,3963],{"class":495},[235,3986,1486],{"class":499},[235,3988,3989,3991,3994],{"class":237,"line":259},[235,3990,1491],{"class":499},[235,3992,3993],{"class":495},"head",[235,3995,1486],{"class":499},[235,3997,3998],{"class":237,"line":266},[235,3999,4000],{"class":3118},"      \u003C!---省略-->\n",[235,4002,4003,4005,4007],{"class":237,"line":275},[235,4004,1538],{"class":499},[235,4006,3993],{"class":495},[235,4008,1486],{"class":499},[235,4010,4011],{"class":237,"line":283},[235,4012,263],{"emptyLinePlaceholder":262},[235,4014,4015,4017,4020],{"class":237,"line":291},[235,4016,1491],{"class":499},[235,4018,4019],{"class":495},"body",[235,4021,1486],{"class":499},[235,4023,4024,4026,4029,4032,4034,4036,4039,4041],{"class":237,"line":298},[235,4025,1505],{"class":499},[235,4027,4028],{"class":495},"form",[235,4030,4031],{"class":1374}," method",[235,4033,1381],{"class":499},[235,4035,1516],{"class":499},[235,4037,4038],{"class":245},"post",[235,4040,1516],{"class":499},[235,4042,1486],{"class":499},[235,4044,4045,4048,4051,4054,4056,4058,4060,4062,4065,4067,4069,4072,4074,4077,4079,4082],{"class":237,"line":380},[235,4046,4047],{"class":499},"            \u003C",[235,4049,4050],{"class":495},"input",[235,4052,4053],{"class":1374}," type",[235,4055,1381],{"class":499},[235,4057,1516],{"class":499},[235,4059,933],{"class":245},[235,4061,1516],{"class":499},[235,4063,4064],{"class":1374}," name",[235,4066,1381],{"class":499},[235,4068,1516],{"class":499},[235,4070,4071],{"class":245},"test",[235,4073,1516],{"class":499},[235,4075,4076],{"class":1374}," value",[235,4078,1381],{"class":499},[235,4080,4081],{"class":499},"\"\"",[235,4083,1486],{"class":499},[235,4085,4086,4088,4090,4092,4094,4096,4099,4101,4103,4105,4107,4110,4112],{"class":237,"line":465},[235,4087,4047],{"class":499},[235,4089,4050],{"class":495},[235,4091,4053],{"class":1374},[235,4093,1381],{"class":499},[235,4095,1516],{"class":499},[235,4097,4098],{"class":245},"submit",[235,4100,1516],{"class":499},[235,4102,4076],{"class":1374},[235,4104,1381],{"class":499},[235,4106,1516],{"class":499},[235,4108,4109],{"class":245},"送信",[235,4111,1516],{"class":499},[235,4113,1486],{"class":499},[235,4115,4116,4119,4121],{"class":237,"line":590},[235,4117,4118],{"class":499},"        \u003C\u002F",[235,4120,4028],{"class":495},[235,4122,1486],{"class":499},[235,4124,4125,4127,4129],{"class":237,"line":604},[235,4126,1538],{"class":499},[235,4128,4019],{"class":495},[235,4130,1486],{"class":499},[235,4132,4133,4135,4137],{"class":237,"line":612},[235,4134,1529],{"class":499},[235,4136,3963],{"class":495},[235,4138,1486],{"class":499},[13,4140,4141],{},"reCAPTCHAのフロントエンド 実装では",[17,4143,4144,4147,4150],{},[20,4145,4146],{},"reCAPTCHAのソースを読み込む",[20,4148,4149],{},"送信ボタンを押したらreCAPTCHAと通信してトークンを手に入れるスクリプトを書く",[20,4151,4152],{},"reCAPTCHAのトークンをフォーム内容と一緒にバックエンドに送信する。",[13,4154,4155,4156,4161],{},"以上の実装を必要とします。",[197,4157,4160],{"href":4158,"rel":4159},"https:\u002F\u002Fdevelopers.google.com\u002Frecaptcha\u002Fdocs\u002Fv3#programmatically_invoke_the_challenge",[201],"本家のドキュメント","を参考にして進めていきましょう。",[81,4163,4165],{"id":4164},"recaptchaのスクリプト読み込みとhtml調整","reCAPTCHAのスクリプト読み込みとHTML調整",[13,4167,4168],{},"まずはreCAPTCHAを有効にするためのスクリプトを読み込みます。そして一部フォームを編集します。",[226,4170,4172],{"className":3961,"code":4171,"language":3963,"meta":231,"style":231},"\u003C!DOCTYPE html>\n\u003Chtml>\n    \u003Chead>\n      \u003C!---省略-->\n        \u003Cscript src=\"https:\u002F\u002Fwww.google.com\u002Frecaptcha\u002Fapi.js?render=YOUR_FRONT_KEY\">\u003C\u002Fscript>\u003C!---追加-->\n    \u003C\u002Fhead>\n\n    \u003Cbody>\n        \u003Cform method=\"post\" id=\"test-form\">\u003C!---追加-->\n            \u003Cinput type=\"text\" name=\"test\" value=\"\">\n            \u003Cinput type=\"hidden\" name=\"recaptcha\" value=\"\">\u003C!---追加-->\n            \u003Cinput type=\"submit\" value=\"送信\">\n        \u003C\u002Fform>\n    \u003C\u002Fbody>\n\u003C\u002Fhtml>\n",[39,4173,4174,4184,4192,4200,4204,4232,4240,4244,4252,4284,4318,4356,4384,4392,4400],{"__ignoreMap":231},[235,4175,4176,4178,4180,4182],{"class":237,"line":238},[235,4177,3970],{"class":499},[235,4179,3973],{"class":495},[235,4181,3976],{"class":1374},[235,4183,1486],{"class":499},[235,4185,4186,4188,4190],{"class":237,"line":249},[235,4187,1480],{"class":499},[235,4189,3963],{"class":495},[235,4191,1486],{"class":499},[235,4193,4194,4196,4198],{"class":237,"line":259},[235,4195,1491],{"class":499},[235,4197,3993],{"class":495},[235,4199,1486],{"class":499},[235,4201,4202],{"class":237,"line":266},[235,4203,4000],{"class":3118},[235,4205,4206,4208,4210,4213,4215,4217,4220,4222,4225,4227,4229],{"class":237,"line":275},[235,4207,1505],{"class":499},[235,4209,1557],{"class":495},[235,4211,4212],{"class":1374}," src",[235,4214,1381],{"class":499},[235,4216,1516],{"class":499},[235,4218,4219],{"class":245},"https:\u002F\u002Fwww.google.com\u002Frecaptcha\u002Fapi.js?render=YOUR_FRONT_KEY",[235,4221,1516],{"class":499},[235,4223,4224],{"class":499},">\u003C\u002F",[235,4226,1557],{"class":495},[235,4228,1523],{"class":499},[235,4230,4231],{"class":3118},"\u003C!---追加-->\n",[235,4233,4234,4236,4238],{"class":237,"line":283},[235,4235,1538],{"class":499},[235,4237,3993],{"class":495},[235,4239,1486],{"class":499},[235,4241,4242],{"class":237,"line":291},[235,4243,263],{"emptyLinePlaceholder":262},[235,4245,4246,4248,4250],{"class":237,"line":298},[235,4247,1491],{"class":499},[235,4249,4019],{"class":495},[235,4251,1486],{"class":499},[235,4253,4254,4256,4258,4260,4262,4264,4266,4268,4271,4273,4275,4278,4280,4282],{"class":237,"line":380},[235,4255,1505],{"class":499},[235,4257,4028],{"class":495},[235,4259,4031],{"class":1374},[235,4261,1381],{"class":499},[235,4263,1516],{"class":499},[235,4265,4038],{"class":245},[235,4267,1516],{"class":499},[235,4269,4270],{"class":1374}," id",[235,4272,1381],{"class":499},[235,4274,1516],{"class":499},[235,4276,4277],{"class":245},"test-form",[235,4279,1516],{"class":499},[235,4281,1523],{"class":499},[235,4283,4231],{"class":3118},[235,4285,4286,4288,4290,4292,4294,4296,4298,4300,4302,4304,4306,4308,4310,4312,4314,4316],{"class":237,"line":465},[235,4287,4047],{"class":499},[235,4289,4050],{"class":495},[235,4291,4053],{"class":1374},[235,4293,1381],{"class":499},[235,4295,1516],{"class":499},[235,4297,933],{"class":245},[235,4299,1516],{"class":499},[235,4301,4064],{"class":1374},[235,4303,1381],{"class":499},[235,4305,1516],{"class":499},[235,4307,4071],{"class":245},[235,4309,1516],{"class":499},[235,4311,4076],{"class":1374},[235,4313,1381],{"class":499},[235,4315,4081],{"class":499},[235,4317,1486],{"class":499},[235,4319,4320,4322,4324,4326,4328,4330,4333,4335,4337,4339,4341,4344,4346,4348,4350,4352,4354],{"class":237,"line":590},[235,4321,4047],{"class":499},[235,4323,4050],{"class":495},[235,4325,4053],{"class":1374},[235,4327,1381],{"class":499},[235,4329,1516],{"class":499},[235,4331,4332],{"class":245},"hidden",[235,4334,1516],{"class":499},[235,4336,4064],{"class":1374},[235,4338,1381],{"class":499},[235,4340,1516],{"class":499},[235,4342,4343],{"class":245},"recaptcha",[235,4345,1516],{"class":499},[235,4347,4076],{"class":1374},[235,4349,1381],{"class":499},[235,4351,4081],{"class":499},[235,4353,1523],{"class":499},[235,4355,4231],{"class":3118},[235,4357,4358,4360,4362,4364,4366,4368,4370,4372,4374,4376,4378,4380,4382],{"class":237,"line":604},[235,4359,4047],{"class":499},[235,4361,4050],{"class":495},[235,4363,4053],{"class":1374},[235,4365,1381],{"class":499},[235,4367,1516],{"class":499},[235,4369,4098],{"class":245},[235,4371,1516],{"class":499},[235,4373,4076],{"class":1374},[235,4375,1381],{"class":499},[235,4377,1516],{"class":499},[235,4379,4109],{"class":245},[235,4381,1516],{"class":499},[235,4383,1486],{"class":499},[235,4385,4386,4388,4390],{"class":237,"line":612},[235,4387,4118],{"class":499},[235,4389,4028],{"class":495},[235,4391,1486],{"class":499},[235,4393,4394,4396,4398],{"class":237,"line":623},[235,4395,1538],{"class":499},[235,4397,4019],{"class":495},[235,4399,1486],{"class":499},[235,4401,4402,4404,4406],{"class":237,"line":634},[235,4403,1529],{"class":499},[235,4405,3963],{"class":495},[235,4407,1486],{"class":499},[13,4409,4410,3710,4413,4416,4417,4420],{},[39,4411,4412],{},"\u003Cscript src=\"https:\u002F\u002Fwww.google.com\u002Frecaptcha\u002Fapi.js?render=YOUR_FRONT_KEY\">\u003C\u002Fscript>",[39,4414,4415],{},"YOUR_FRONT_KEY","に先ほど取得したフロント用のキーを入れます。\n",[39,4418,4419],{},"\u003Cinput type=\"hidden\" name=\"recaptcha\" value=\"\">","にはreCAPTCHAのトークンを挿入してバックエンドに送ります。HTMLフォームであればこの様にしますが、axiosなどの場合はトークンの値をjsを用いて送信するので、このHTMLは要りません。",[81,4422,4423],{"id":4423},"トークンを取得するスクリプトを記述",[13,4425,4426],{},"「送信ボタン」を押した時にreCAPTCHAにAPIを飛ばしてbotかどうかの判定用トークンを取得します。以下の様なスクリプトを記述します。",[226,4428,4430],{"className":3961,"code":4429,"language":3963,"meta":231,"style":231},"\u003C!DOCTYPE html>\n\u003Chtml>\n    \u003Chead>\n      \u003C!---省略-->\n      \u003Cscript src=\"https:\u002F\u002Fwww.google.com\u002Frecaptcha\u002Fapi.js?render=YOUR_FRONT_KEY\">\u003C\u002Fscript>\n    \u003C\u002Fhead>\n\n    \u003Cbody>\n        \u003Cform method=\"post\" id=\"test-form\">\u003C!---追加-->\n            \u003Cinput type=\"text\" name=\"test\" value=\"\">\n            \u003Cinput type=\"hidden\" name=\"recaptcha\" value=\"\">\u003C!---追加-->\n            \u003Cinput type=\"submit\" value=\"送信\">\n        \u003C\u002Fform>\n    \u003C\u002Fbody>\n\n    \u003Cscript>\n        function checkCaptcha(e) {\n            e.preventDefault();\n            grecaptcha.ready(function() {\n                grecaptcha.execute('YOUR_FRONT_KEY', {action: 'submit'}).then(function(token) {\n                    document.getElementById(\"recaptcha\").value=token;\n                    document.getElementById(\"test-form\").submit();\n                });\n            });\n        }\n        document.getElementById(\"test-form\").addEventListener('submit', checkCaptcha);\n        \u003C\u002Fscript>\n\u003C\u002Fhtml>\n",[39,4431,4432,4442,4450,4458,4462,4485,4493,4497,4505,4535,4569,4605,4633,4641,4649,4653,4661,4679,4693,4712,4768,4799,4825,4834,4843,4848,4887,4895],{"__ignoreMap":231},[235,4433,4434,4436,4438,4440],{"class":237,"line":238},[235,4435,3970],{"class":499},[235,4437,3973],{"class":495},[235,4439,3976],{"class":1374},[235,4441,1486],{"class":499},[235,4443,4444,4446,4448],{"class":237,"line":249},[235,4445,1480],{"class":499},[235,4447,3963],{"class":495},[235,4449,1486],{"class":499},[235,4451,4452,4454,4456],{"class":237,"line":259},[235,4453,1491],{"class":499},[235,4455,3993],{"class":495},[235,4457,1486],{"class":499},[235,4459,4460],{"class":237,"line":266},[235,4461,4000],{"class":3118},[235,4463,4464,4467,4469,4471,4473,4475,4477,4479,4481,4483],{"class":237,"line":275},[235,4465,4466],{"class":499},"      \u003C",[235,4468,1557],{"class":495},[235,4470,4212],{"class":1374},[235,4472,1381],{"class":499},[235,4474,1516],{"class":499},[235,4476,4219],{"class":245},[235,4478,1516],{"class":499},[235,4480,4224],{"class":499},[235,4482,1557],{"class":495},[235,4484,1486],{"class":499},[235,4486,4487,4489,4491],{"class":237,"line":283},[235,4488,1538],{"class":499},[235,4490,3993],{"class":495},[235,4492,1486],{"class":499},[235,4494,4495],{"class":237,"line":291},[235,4496,263],{"emptyLinePlaceholder":262},[235,4498,4499,4501,4503],{"class":237,"line":298},[235,4500,1491],{"class":499},[235,4502,4019],{"class":495},[235,4504,1486],{"class":499},[235,4506,4507,4509,4511,4513,4515,4517,4519,4521,4523,4525,4527,4529,4531,4533],{"class":237,"line":380},[235,4508,1505],{"class":499},[235,4510,4028],{"class":495},[235,4512,4031],{"class":1374},[235,4514,1381],{"class":499},[235,4516,1516],{"class":499},[235,4518,4038],{"class":245},[235,4520,1516],{"class":499},[235,4522,4270],{"class":1374},[235,4524,1381],{"class":499},[235,4526,1516],{"class":499},[235,4528,4277],{"class":245},[235,4530,1516],{"class":499},[235,4532,1523],{"class":499},[235,4534,4231],{"class":3118},[235,4536,4537,4539,4541,4543,4545,4547,4549,4551,4553,4555,4557,4559,4561,4563,4565,4567],{"class":237,"line":465},[235,4538,4047],{"class":499},[235,4540,4050],{"class":495},[235,4542,4053],{"class":1374},[235,4544,1381],{"class":499},[235,4546,1516],{"class":499},[235,4548,933],{"class":245},[235,4550,1516],{"class":499},[235,4552,4064],{"class":1374},[235,4554,1381],{"class":499},[235,4556,1516],{"class":499},[235,4558,4071],{"class":245},[235,4560,1516],{"class":499},[235,4562,4076],{"class":1374},[235,4564,1381],{"class":499},[235,4566,4081],{"class":499},[235,4568,1486],{"class":499},[235,4570,4571,4573,4575,4577,4579,4581,4583,4585,4587,4589,4591,4593,4595,4597,4599,4601,4603],{"class":237,"line":590},[235,4572,4047],{"class":499},[235,4574,4050],{"class":495},[235,4576,4053],{"class":1374},[235,4578,1381],{"class":499},[235,4580,1516],{"class":499},[235,4582,4332],{"class":245},[235,4584,1516],{"class":499},[235,4586,4064],{"class":1374},[235,4588,1381],{"class":499},[235,4590,1516],{"class":499},[235,4592,4343],{"class":245},[235,4594,1516],{"class":499},[235,4596,4076],{"class":1374},[235,4598,1381],{"class":499},[235,4600,4081],{"class":499},[235,4602,1523],{"class":499},[235,4604,4231],{"class":3118},[235,4606,4607,4609,4611,4613,4615,4617,4619,4621,4623,4625,4627,4629,4631],{"class":237,"line":604},[235,4608,4047],{"class":499},[235,4610,4050],{"class":495},[235,4612,4053],{"class":1374},[235,4614,1381],{"class":499},[235,4616,1516],{"class":499},[235,4618,4098],{"class":245},[235,4620,1516],{"class":499},[235,4622,4076],{"class":1374},[235,4624,1381],{"class":499},[235,4626,1516],{"class":499},[235,4628,4109],{"class":245},[235,4630,1516],{"class":499},[235,4632,1486],{"class":499},[235,4634,4635,4637,4639],{"class":237,"line":612},[235,4636,4118],{"class":499},[235,4638,4028],{"class":495},[235,4640,1486],{"class":499},[235,4642,4643,4645,4647],{"class":237,"line":623},[235,4644,1538],{"class":499},[235,4646,4019],{"class":495},[235,4648,1486],{"class":499},[235,4650,4651],{"class":237,"line":634},[235,4652,263],{"emptyLinePlaceholder":262},[235,4654,4655,4657,4659],{"class":237,"line":4},[235,4656,1491],{"class":499},[235,4658,1557],{"class":495},[235,4660,1486],{"class":499},[235,4662,4663,4666,4669,4671,4675,4677],{"class":237,"line":652},[235,4664,4665],{"class":1374},"        function",[235,4667,4668],{"class":252}," checkCaptcha",[235,4670,1704],{"class":499},[235,4672,4674],{"class":4673},"s7ZW3","e",[235,4676,1714],{"class":499},[235,4678,1275],{"class":499},[235,4680,4681,4684,4686,4689,4691],{"class":237,"line":660},[235,4682,4683],{"class":519},"            e",[235,4685,1693],{"class":499},[235,4687,4688],{"class":252},"preventDefault",[235,4690,2223],{"class":495},[235,4692,1347],{"class":499},[235,4694,4695,4698,4700,4703,4705,4708,4710],{"class":237,"line":669},[235,4696,4697],{"class":519},"            grecaptcha",[235,4699,1693],{"class":499},[235,4701,4702],{"class":252},"ready",[235,4704,1704],{"class":495},[235,4706,4707],{"class":1374},"function",[235,4709,2223],{"class":499},[235,4711,1275],{"class":499},[235,4713,4714,4717,4719,4722,4724,4726,4728,4730,4732,4735,4738,4740,4742,4744,4746,4748,4750,4752,4755,4757,4759,4761,4764,4766],{"class":237,"line":677},[235,4715,4716],{"class":519},"                grecaptcha",[235,4718,1693],{"class":499},[235,4720,4721],{"class":252},"execute",[235,4723,1704],{"class":495},[235,4725,1344],{"class":499},[235,4727,4415],{"class":245},[235,4729,1344],{"class":499},[235,4731,1403],{"class":499},[235,4733,4734],{"class":499}," {",[235,4736,4737],{"class":495},"action",[235,4739,500],{"class":499},[235,4741,503],{"class":499},[235,4743,4098],{"class":245},[235,4745,1344],{"class":499},[235,4747,1949],{"class":499},[235,4749,1714],{"class":495},[235,4751,1693],{"class":499},[235,4753,4754],{"class":252},"then",[235,4756,1704],{"class":495},[235,4758,4707],{"class":1374},[235,4760,1704],{"class":499},[235,4762,4763],{"class":4673},"token",[235,4765,1714],{"class":499},[235,4767,1275],{"class":499},[235,4769,4770,4773,4775,4778,4780,4782,4784,4786,4788,4790,4793,4795,4797],{"class":237,"line":685},[235,4771,4772],{"class":519},"                    document",[235,4774,1693],{"class":499},[235,4776,4777],{"class":252},"getElementById",[235,4779,1704],{"class":495},[235,4781,1516],{"class":499},[235,4783,4343],{"class":245},[235,4785,1516],{"class":499},[235,4787,1714],{"class":495},[235,4789,1693],{"class":499},[235,4791,4792],{"class":519},"value",[235,4794,1381],{"class":499},[235,4796,4763],{"class":519},[235,4798,1347],{"class":499},[235,4800,4801,4803,4805,4807,4809,4811,4813,4815,4817,4819,4821,4823],{"class":237,"line":693},[235,4802,4772],{"class":519},[235,4804,1693],{"class":499},[235,4806,4777],{"class":252},[235,4808,1704],{"class":495},[235,4810,1516],{"class":499},[235,4812,4277],{"class":245},[235,4814,1516],{"class":499},[235,4816,1714],{"class":495},[235,4818,1693],{"class":499},[235,4820,4098],{"class":252},[235,4822,2223],{"class":495},[235,4824,1347],{"class":499},[235,4826,4827,4830,4832],{"class":237,"line":701},[235,4828,4829],{"class":499},"                }",[235,4831,1714],{"class":495},[235,4833,1347],{"class":499},[235,4835,4836,4839,4841],{"class":237,"line":711},[235,4837,4838],{"class":499},"            }",[235,4840,1714],{"class":495},[235,4842,1347],{"class":499},[235,4844,4845],{"class":237,"line":722},[235,4846,4847],{"class":499},"        }\n",[235,4849,4850,4853,4855,4857,4859,4861,4863,4865,4867,4869,4872,4874,4876,4878,4880,4882,4885],{"class":237,"line":729},[235,4851,4852],{"class":519},"        document",[235,4854,1693],{"class":499},[235,4856,4777],{"class":252},[235,4858,1704],{"class":519},[235,4860,1516],{"class":499},[235,4862,4277],{"class":245},[235,4864,1516],{"class":499},[235,4866,1714],{"class":519},[235,4868,1693],{"class":499},[235,4870,4871],{"class":252},"addEventListener",[235,4873,1704],{"class":519},[235,4875,1344],{"class":499},[235,4877,4098],{"class":245},[235,4879,1344],{"class":499},[235,4881,1403],{"class":499},[235,4883,4884],{"class":519}," checkCaptcha)",[235,4886,1347],{"class":499},[235,4888,4889,4891,4893],{"class":237,"line":740},[235,4890,4118],{"class":499},[235,4892,1557],{"class":495},[235,4894,1486],{"class":499},[235,4896,4897,4899,4901],{"class":237,"line":751},[235,4898,1529],{"class":499},[235,4900,3963],{"class":495},[235,4902,1486],{"class":499},[13,4904,4905,4906,4909,4910,4913],{},"フォームの送信ボタンが押された時（submitイベント発火時）に",[39,4907,4908],{},"checkCaptcha","の関数が実行される様に設定します。",[39,4911,4912],{},"e.preventDefault();","を使用してそのままフォームが送信されない様にします。",[13,4915,4916,4917,4920,4921,4924,4925,4928,4929,4931,4932,4935],{},"reCAPTCHAのスクリプトによって",[39,4918,4919],{},"grecaptcha","というオブジェクトが使用できる様になり、その中の",[39,4922,4923],{},"grecaptcha.execute()","にてAPIを実行します。第一引数にフロントのキー、第二引数にアクションを入力します。Promiseなので",[39,4926,4927],{},"then(token)","内のコールバックでトークンを",[39,4930,4419],{},"に突っ込みます。そしてフォームを",[39,4933,4934],{},"submit()","にて送信します。",[13,4937,4938],{},"これでフロントの実装は完了です。フロントでの動きをみてreCAPTCHAはbotかどうかを判断し、この送信を一意なトークンで保存しているのです。トークンはバックエンドでの検証で利用します。",[74,4940,4941],{"id":4941},"バックエンドの実装",[13,4943,4944],{},"それではバックエンドの実装をすすめます。Laravelのコントローラーでの記述を想定しています。バリデーションなどは各自設定してください。",[13,4946,4947],{},"バックエンドで行うことは",[17,4949,4950,4953,4956],{},[20,4951,4952],{},"フロントからきたトークンをreCAPTCHAのAPIに送信",[20,4954,4955],{},"reCAPTCHAの結果を取得する",[20,4957,4958],{},"結果（スコア）を用いてbotかの判断をする",[13,4960,4961],{},"以上となります。コードは以下の通りです。",[226,4963,4965],{"className":1025,"code":4964,"language":1028,"meta":231,"style":231},"class Controller extends BaseController\n{\n    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;\n\n    public function checkRecaptcha(Request $request){\n        try {\n            $client = new \\GuzzleHttp\\Client([\n                'headers' => [\n                    'Content-Type' => 'application\u002Fjson',\n                ],\n            ]);\n    \n            $promise = $client->postAsync('https:\u002F\u002Fwww.google.com\u002Frecaptcha\u002Fapi\u002Fsiteverify',\n            [\n                'form_params' =>[\n                    'secret'=>env('RECAPTCHA_SERVER_KEY'),\n                    'response'=>$request->recaptcha\n                ]\n            ]);\n    \n            $res = Promise\\Utils::settle($promise)->wait();\n            $isFulfilled = isset($res[0]['value']);\n            if(!$isFulfilled) throw new \\Exception('RECAPTCHA SERVER returns error');\n    \n            $result = json_decode($res[0]['value']->getBody()->getContents(),true);\n            \n            if(isset($result['error-codes'])){\n                if($result['error-codes'][0] === 'timeout-or-duplicate') return false;\n                throw new \\Exception('RECAPTCHA SERVER returns error:'.$result['error-codes'][0]);\n            }\n\n            return $result['score'] > 0.5 && $result['success'];\n        }catch (\\Exception $e) {\n            report($e);\n            return false;\n        }\n    }\n}\n",[39,4966,4967,4972,4976,4981,4985,4990,4995,5000,5005,5010,5015,5020,5025,5030,5035,5040,5045,5050,5055,5059,5063,5068,5073,5078,5082,5087,5092,5097,5102,5107,5112,5116,5121,5126,5131,5136,5140,5144],{"__ignoreMap":231},[235,4968,4969],{"class":237,"line":238},[235,4970,4971],{},"class Controller extends BaseController\n",[235,4973,4974],{"class":237,"line":249},[235,4975,1975],{},[235,4977,4978],{"class":237,"line":259},[235,4979,4980],{},"    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;\n",[235,4982,4983],{"class":237,"line":266},[235,4984,263],{"emptyLinePlaceholder":262},[235,4986,4987],{"class":237,"line":275},[235,4988,4989],{},"    public function checkRecaptcha(Request $request){\n",[235,4991,4992],{"class":237,"line":283},[235,4993,4994],{},"        try {\n",[235,4996,4997],{"class":237,"line":291},[235,4998,4999],{},"            $client = new \\GuzzleHttp\\Client([\n",[235,5001,5002],{"class":237,"line":298},[235,5003,5004],{},"                'headers' => [\n",[235,5006,5007],{"class":237,"line":380},[235,5008,5009],{},"                    'Content-Type' => 'application\u002Fjson',\n",[235,5011,5012],{"class":237,"line":465},[235,5013,5014],{},"                ],\n",[235,5016,5017],{"class":237,"line":590},[235,5018,5019],{},"            ]);\n",[235,5021,5022],{"class":237,"line":604},[235,5023,5024],{},"    \n",[235,5026,5027],{"class":237,"line":612},[235,5028,5029],{},"            $promise = $client->postAsync('https:\u002F\u002Fwww.google.com\u002Frecaptcha\u002Fapi\u002Fsiteverify',\n",[235,5031,5032],{"class":237,"line":623},[235,5033,5034],{},"            [\n",[235,5036,5037],{"class":237,"line":634},[235,5038,5039],{},"                'form_params' =>[\n",[235,5041,5042],{"class":237,"line":4},[235,5043,5044],{},"                    'secret'=>env('RECAPTCHA_SERVER_KEY'),\n",[235,5046,5047],{"class":237,"line":652},[235,5048,5049],{},"                    'response'=>$request->recaptcha\n",[235,5051,5052],{"class":237,"line":660},[235,5053,5054],{},"                ]\n",[235,5056,5057],{"class":237,"line":669},[235,5058,5019],{},[235,5060,5061],{"class":237,"line":677},[235,5062,5024],{},[235,5064,5065],{"class":237,"line":685},[235,5066,5067],{},"            $res = Promise\\Utils::settle($promise)->wait();\n",[235,5069,5070],{"class":237,"line":693},[235,5071,5072],{},"            $isFulfilled = isset($res[0]['value']);\n",[235,5074,5075],{"class":237,"line":701},[235,5076,5077],{},"            if(!$isFulfilled) throw new \\Exception('RECAPTCHA SERVER returns error');\n",[235,5079,5080],{"class":237,"line":711},[235,5081,5024],{},[235,5083,5084],{"class":237,"line":722},[235,5085,5086],{},"            $result = json_decode($res[0]['value']->getBody()->getContents(),true);\n",[235,5088,5089],{"class":237,"line":729},[235,5090,5091],{},"            \n",[235,5093,5094],{"class":237,"line":740},[235,5095,5096],{},"            if(isset($result['error-codes'])){\n",[235,5098,5099],{"class":237,"line":751},[235,5100,5101],{},"                if($result['error-codes'][0] === 'timeout-or-duplicate') return false;\n",[235,5103,5104],{"class":237,"line":762},[235,5105,5106],{},"                throw new \\Exception('RECAPTCHA SERVER returns error:'.$result['error-codes'][0]);\n",[235,5108,5109],{"class":237,"line":773},[235,5110,5111],{},"            }\n",[235,5113,5114],{"class":237,"line":784},[235,5115,263],{"emptyLinePlaceholder":262},[235,5117,5118],{"class":237,"line":793},[235,5119,5120],{},"            return $result['score'] > 0.5 && $result['success'];\n",[235,5122,5123],{"class":237,"line":805},[235,5124,5125],{},"        }catch (\\Exception $e) {\n",[235,5127,5128],{"class":237,"line":814},[235,5129,5130],{},"            report($e);\n",[235,5132,5133],{"class":237,"line":822},[235,5134,5135],{},"            return false;\n",[235,5137,5138],{"class":237,"line":832},[235,5139,4847],{},[235,5141,5142],{"class":237,"line":3536},[235,5143,2180],{},[235,5145,5146],{"class":237,"line":3541},[235,5147,1316],{},[13,5149,5150,5151,5154,5155,5158],{},"recaptchaとのAPI通信には",[39,5152,5153],{},"Guzzle","を使用していますが、とにかくAPI通信ができれば大丈夫です。APIは",[39,5156,5157],{},"https:\u002F\u002Fwww.google.com\u002Frecaptcha\u002Fapi\u002Fsiteverify","にPOSTを送信します。POSTには以下の値が必要です、",[226,5160,5162],{"className":1025,"code":5161,"language":1028,"meta":231,"style":231},"'form_params' =>[\n    'secret'=>env('RECAPTCHA_SERVER_KEY'),\n    'response'=>$request->recaptcha\n]\n",[39,5163,5164,5169,5174,5179],{"__ignoreMap":231},[235,5165,5166],{"class":237,"line":238},[235,5167,5168],{},"'form_params' =>[\n",[235,5170,5171],{"class":237,"line":249},[235,5172,5173],{},"    'secret'=>env('RECAPTCHA_SERVER_KEY'),\n",[235,5175,5176],{"class":237,"line":259},[235,5177,5178],{},"    'response'=>$request->recaptcha\n",[235,5180,5181],{"class":237,"line":266},[235,5182,1443],{},[13,5184,5185,5188,5189,1215,5192,5194,5195,5198],{},[39,5186,5187],{},"env('RECAPTCHA_SERVER_KEY')","はバックエンドで使用するrecaptchaキーです。",[39,5190,5191],{},"$request->recaptcha",[39,5193,4419],{},"で挿入されたフロントで取得したrecaptchaのトークンです。このトークンとキーを合わせて、 ",[3700,5196,5197],{},"保護対象のサーバーであり、検証を行うフォーム送信","　を判別しています。",[13,5200,5201],{},"通信が成功すると以下の様なレスポンスが戻ります。",[226,5203,5205],{"className":1025,"code":5204,"language":1028,"meta":231,"style":231},"[\n  \"success\"=> true, \n  \"score\"=> 0.8,\n  \"action\"=> string,\n  \"challenge_ts\"=> timestamp,\n  \"hostname\"=> string,\n]\n",[39,5206,5207,5212,5217,5222,5227,5232,5237],{"__ignoreMap":231},[235,5208,5209],{"class":237,"line":238},[235,5210,5211],{},"[\n",[235,5213,5214],{"class":237,"line":249},[235,5215,5216],{},"  \"success\"=> true, \n",[235,5218,5219],{"class":237,"line":259},[235,5220,5221],{},"  \"score\"=> 0.8,\n",[235,5223,5224],{"class":237,"line":266},[235,5225,5226],{},"  \"action\"=> string,\n",[235,5228,5229],{"class":237,"line":275},[235,5230,5231],{},"  \"challenge_ts\"=> timestamp,\n",[235,5233,5234],{"class":237,"line":283},[235,5235,5236],{},"  \"hostname\"=> string,\n",[235,5238,5239],{"class":237,"line":291},[235,5240,1443],{},[13,5242,5243,5244,5247,5248,5251],{},"一番重要なのは",[39,5245,5246],{},"\"score\"=> 0.8","です。このスコアは入力したリクエストがbotか人間かのスコアを示しており、1に近いほど人間が入力しています。逆に0.1あたりはbotの入力です。どこまで厳しくするかはお任せしますが、私は0.5以上であれば人間のリクエストであるとしています。",[39,5249,5250],{},"return $result['score'] > 0.5"," としてfalseであればリクエストを拒否したり、エラーを返す様にします。フォーム系で汎用的に使用できる様に私はサービスプロバイダにしています。",[81,5253,5254],{"id":5254},"エラー処理",[13,5256,5257],{},"エラーの場合は以下の様なレスポンスがきます。（例です）",[226,5259,5261],{"className":1025,"code":5260,"language":1028,"meta":231,"style":231},"[\n  \"success\"=> false, \n  \"action\"=> string,\n  \"challenge_ts\"=> timestamp,\n  \"hostname\"=> string,\n  \"error-codes\": [\n      0=>'timeout-or-duplicate'\n  ] \n]\n",[39,5262,5263,5267,5272,5276,5280,5284,5289,5294,5299],{"__ignoreMap":231},[235,5264,5265],{"class":237,"line":238},[235,5266,5211],{},[235,5268,5269],{"class":237,"line":249},[235,5270,5271],{},"  \"success\"=> false, \n",[235,5273,5274],{"class":237,"line":259},[235,5275,5226],{},[235,5277,5278],{"class":237,"line":266},[235,5279,5231],{},[235,5281,5282],{"class":237,"line":275},[235,5283,5236],{},[235,5285,5286],{"class":237,"line":283},[235,5287,5288],{},"  \"error-codes\": [\n",[235,5290,5291],{"class":237,"line":291},[235,5292,5293],{},"      0=>'timeout-or-duplicate'\n",[235,5295,5296],{"class":237,"line":298},[235,5297,5298],{},"  ] \n",[235,5300,5301],{"class":237,"line":380},[235,5302,1443],{},[13,5304,5305,5306,5309],{},"このエラーはAPIの通信が失敗したり、必要なパラメーターが不足していたりなどのエラーです。 ",[3700,5307,5308],{},"リクエストがBotである"," という意味ではないので注意。Botかの判定はあくまで成功時に取得するscoreで判定します。",[81,5311,5312],{"id":5312},"エラーの説明",[13,5314,5315,5318,5320],{},[3700,5316,5317],{},"missing-input-secret",[39,5319,5187],{},"のようなサーバー側のrecaptchaのキーを忘れている。",[226,5322,5324],{"className":1025,"code":5323,"language":1028,"meta":231,"style":231},"'form_params' =>[\n    'secret'=>env('RECAPTCHA_SERVER_KEY'), \u002F\u002F このへん\n    'response'=>$request->recaptcha\n]\n",[39,5325,5326,5330,5338,5342],{"__ignoreMap":231},[235,5327,5328],{"class":237,"line":238},[235,5329,5168],{},[235,5331,5332,5335],{"class":237,"line":249},[235,5333,5334],{},"    'secret'=>env('RECAPTCHA_SERVER_KEY'),",[235,5336,5337],{}," \u002F\u002F このへん\n",[235,5339,5340],{"class":237,"line":259},[235,5341,5178],{},[235,5343,5344],{"class":237,"line":266},[235,5345,1443],{},[13,5347,5348,5351,5353],{},[3700,5349,5350],{},"invalid-input-secret",[39,5352,5187],{},"が不正。間違っているキーを使用している。キーが正しいか、保護対象のドメインとして登録しているかを確認。",[13,5355,5356,5359,5362],{},[3700,5357,5358],{},"missing-input-response",[39,5360,5361],{},"'response'=>$request->recaptcha","を忘れている、空文字。",[13,5364,5365,5368,5370],{},[3700,5366,5367],{},"invalid-input-response",[39,5369,5361],{},"の値が不正。型などを確認。",[13,5372,5373,5376],{},[3700,5374,5375],{},"bad-request","\nPOSTで送っているかを確認。",[13,5378,5379,5382,5384],{},[3700,5380,5381],{},"timeout-or-duplicate",[39,5383,5361],{},"の値を二回送っているか、フロントのトークンが２分以上経過した。",[13,5386,5387],{},"フロントで取得したトークンはバックエンドでのこの検証を行うともう一度利用することができません。またこのトークンは",[226,5389,5393],{"className":5390,"code":5391,"language":5392,"meta":231,"style":231},"language-javascript shiki shiki-themes material-theme-ocean","grecaptcha.execute('YOUR_FRONT_KEY', {action: 'submit'}).then(function(token) {\n    document.getElementById(\"recaptcha\").value=token;\n    document.getElementById(\"test-form\").submit();\n});\n","javascript",[39,5394,5395,5445,5474,5500],{"__ignoreMap":231},[235,5396,5397,5399,5401,5403,5405,5407,5409,5411,5413,5415,5417,5419,5421,5423,5425,5427,5429,5431,5433,5435,5437,5439,5441,5443],{"class":237,"line":238},[235,5398,4919],{"class":519},[235,5400,1693],{"class":499},[235,5402,4721],{"class":252},[235,5404,1704],{"class":519},[235,5406,1344],{"class":499},[235,5408,4415],{"class":245},[235,5410,1344],{"class":499},[235,5412,1403],{"class":499},[235,5414,4734],{"class":499},[235,5416,4737],{"class":495},[235,5418,500],{"class":499},[235,5420,503],{"class":499},[235,5422,4098],{"class":245},[235,5424,1344],{"class":499},[235,5426,1949],{"class":499},[235,5428,1714],{"class":519},[235,5430,1693],{"class":499},[235,5432,4754],{"class":252},[235,5434,1704],{"class":519},[235,5436,4707],{"class":1374},[235,5438,1704],{"class":499},[235,5440,4763],{"class":4673},[235,5442,1714],{"class":499},[235,5444,1275],{"class":499},[235,5446,5447,5450,5452,5454,5456,5458,5460,5462,5464,5466,5468,5470,5472],{"class":237,"line":249},[235,5448,5449],{"class":519},"    document",[235,5451,1693],{"class":499},[235,5453,4777],{"class":252},[235,5455,1704],{"class":495},[235,5457,1516],{"class":499},[235,5459,4343],{"class":245},[235,5461,1516],{"class":499},[235,5463,1714],{"class":495},[235,5465,1693],{"class":499},[235,5467,4792],{"class":519},[235,5469,1381],{"class":499},[235,5471,4763],{"class":519},[235,5473,1347],{"class":499},[235,5475,5476,5478,5480,5482,5484,5486,5488,5490,5492,5494,5496,5498],{"class":237,"line":259},[235,5477,5449],{"class":519},[235,5479,1693],{"class":499},[235,5481,4777],{"class":252},[235,5483,1704],{"class":495},[235,5485,1516],{"class":499},[235,5487,4277],{"class":245},[235,5489,1516],{"class":499},[235,5491,1714],{"class":495},[235,5493,1693],{"class":499},[235,5495,4098],{"class":252},[235,5497,2223],{"class":495},[235,5499,1347],{"class":499},[235,5501,5502,5504,5506],{"class":237,"line":266},[235,5503,1949],{"class":499},[235,5505,1714],{"class":519},[235,5507,1347],{"class":499},[13,5509,5510],{},"の実行から２分以内で利用する必要があります。そのためページがリロードされた瞬間ときに実行していると、フォーム入力中に時間切れになったります。そのためsubmit時に実行することをお勧めします。",[74,5512,5513],{"id":5513},"実装まとめ",[13,5515,5516],{},"以上がrecaptchaの実装方法です。recaptchaはあくまでBotかどうかの判断のみをしているので、実際にリクエストを通すかはアプリケーション側の仕事です。フロントとバックでの実装が少し面倒ですが、recaptchaの機能を自前で実装しようとするとそこそこ、面倒なのと実績のあるGoogle様に検証してもらうのも結構安心です。",[13,5518,5519],{},"バックエンドはLaravelを想定しますが、他のフレームワークや言語でもやることは特に変わりません。上手くご自身の環境に置き換えてください。",[2398,5521,5522],{},"html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .sC9rS, html code.shiki .sC9rS{--shiki-default:#464B5D;--shiki-default-font-style:italic}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html pre.shiki code .s7ZW3, html code.shiki .s7ZW3{--shiki-default:#BABED8;--shiki-default-font-style:italic}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}",{"title":231,"searchDepth":259,"depth":259,"links":5524},[5525,5526,5527,5528,5532,5536],{"id":3899,"depth":249,"text":3900},{"id":3906,"depth":249,"text":3906},{"id":3915,"depth":249,"text":3916},{"id":3955,"depth":249,"text":3955,"children":5529},[5530,5531],{"id":4164,"depth":259,"text":4165},{"id":4423,"depth":259,"text":4423},{"id":4941,"depth":249,"text":4941,"children":5533},[5534,5535],{"id":5254,"depth":259,"text":5254},{"id":5312,"depth":259,"text":5312},{"id":5513,"depth":249,"text":5513},[2426],"2021-05-21","reCAPTCHAのbot検証をフロントエンド とLaravelでのバックエンドの実装を行います。",{},"\u002Farticles\u002Fimplement-recaptcha",{"title":3882,"description":5539},"articles\u002Fimplement-recaptcha",[1028,1261,970,5545],"security","_mix\u002Flaravel-recaptcha.jpg","AYarFTQUZE-KIUOU5A3co7uCUDW9hqT091VEnFYUN6Q",{"id":5549,"title":5550,"body":5551,"category":6048,"createdAt":6049,"description":5550,"extension":2429,"index":2438,"meta":6050,"navigation":262,"path":6051,"publish":262,"seo":6052,"series":2438,"seriesTitle":2438,"stem":6053,"tag":6054,"thumbnail":6057,"updatedAt":2438,"__hash__":6058},"articles\u002Farticles\u002Flaravel-mixx-heroku-fail.md","Laravel mixのアセットがherokuで動かない時の対処法",{"type":10,"value":5552,"toc":6039},[5553,5556,5559,5562,5566,5573,5576,5579,5583,5597,5601,5604,5607,5611,5626,5643,5656,5666,5670,5677,5687,5853,5856,6003,6012,6015,6018,6029,6036],[13,5554,5555],{},"こんにちはjunです。最近よくLaravelの開発を行っていて、プロトタイプとかをherokuにあげるのですがLaravel mixのアセットが動かない（main.jsが404）でちょっと困ったので、今回はその対処法を書きたいと思います。",[13,5557,5558],{},"Laravel\bmixの説明を軽くするので、さっさと解決策みたいひとは「ビルドアセットをgitignoreしていた」から参照してください。なお使用環境は以下の通りです。",[13,5560,5561],{},"Laravel 6\nPHP 7.4\nNode.js 12.21（開発）\nNode.js 14.16（heroku）",[74,5563,5565],{"id":5564},"laravel-mixって","Laravel Mixって？",[13,5567,5568,5569,5572],{},"Laravel MixはLaravelが公式に出している、JS・CSSのアセットコンパイラとモジュールバンドラです。",[39,5570,5571],{},"resource","配下のjsやsassをコンパイルしたり、バンドルするwebpackの設定がすでにLaraveに最適化されています。webpackをほとんど触らずともvue、react、sass、そのほかjsライブラリをもちいた開発ができます。",[13,5574,5575],{},"resource 配下のアセットはLaravel Mixによってpublic配下に自動的に吐かれるようになっています。開発時にはnpm run watchをしていますが、よく見るとpublic配下に開発用ビルドしたアセットファイルが逐一置かれています。",[13,5577,5578],{},"大体のLaraveフロントエンドではこのMixを用いて開発していることが多いと思います。しかしherokuや本番環境にあげた時にちょっと問題がおきました。",[74,5580,5582],{"id":5581},"mainjs-is-not-found","main.js is not found",[13,5584,5585,5586,5588,5589,5592,5593,5596],{},"Laravel Mixでフロントを構築して、ある程度バック含めて完成したのでherokuにデプロイしてみました。ビルドは普通に成功し、満を辞して目的の画面をみても",[39,5587,5582],{},"となってしましました。",[39,5590,5591],{},"main.js","にはビルドしたvue.jsのプロジェクトがあるはずなのにと思い、サーバーに入って",[39,5594,5595],{},"public\u002Fjs","配下をみてみたところ、何もありませんでした。",[81,5598,5600],{"id":5599},"原因その１ビルドアセットをgitignoreしていた","原因その１：ビルドアセットをgitignoreしていた",[13,5602,5603],{},"ビルドされたアセットはpublic配下に置かれますが、ファイル量が膨大だったり、開発モードと本番モードが混じる可能性があること、あとどうせコロコロ変わるのでバージョン管理から外そうと思い、public\u002Fjsとpublic\u002Fcssはバージョン管理から外していました。",[13,5605,5606],{},"herokuは基本的にgitを用いてデプロイします。管理対象外のファイルはもちろんherokuサーバー上にないので、アセットがあるはずもありません。であればherokuにデプロイした時にLaravel Mixを叩いて、ビルドすれば解決します。",[81,5608,5610],{"id":5609},"原因その２laravel-mixがdevdependencies","原因その２：Laravel MixがdevDependencies",[13,5612,5613,5614,5617,5618,5621,5622,5625],{},"Herokuでは",[39,5615,5616],{},"NODE_ENV","が",[39,5619,5620],{},"production","（本番環境）で定義されていると、",[39,5623,5624],{},"devDependencies","がプリーニングされてしまい、ビルドがうまく走りません。",[1224,5627,5630,5631,5634],{"className":5628},[1227,5629],"alert-success","\nデフォルトでは、Heroku は ​package.json​ の ​dependencies​ および ​devDependencies​ に記載されているすべての依存関係をインストールします。\n",[13,5632,5633],{},"インストールおよび​ビルドステップ​を実行した後、 Heroku はアプリケーションをデプロイする前に、​devDependencies​ に宣言されているパッケージを取り除きます。",[13,5635,5636],{},[5637,5638,5639],"small",{},[197,5640,5641],{"href":5641,"rel":5642},"https:\u002F\u002Fdevcenter.heroku.com\u002Fja\u002Farticles\u002Fnodejs-support#skip-pruning",[201],[13,5644,5645,5646,5651,5652,5655],{},"インストールおよび​",[197,5647,5650],{"href":5648,"rel":5649},"https:\u002F\u002Fdevcenter.heroku.com\u002Farticles\u002Fnodejs-support#heroku-specific-build-steps",[201],"ビルドステップ","​を実行した後、 Heroku はアプリケーションをデプロイする前に、",[39,5653,5654],{},"​devDependencies​"," に宣言されているパッケージを取り除きます。",[13,5657,5658,5659,5661,5662,5665],{},"対策としては",[39,5660,2965],{},"をいじって",[39,5663,5664],{},"dependencies","に移動します。デフォルトでLaravel Mixはdevの方にインストールされます。それを移動してプッシュすればひとまずLaravel Mixのビルドがうまくいきます。",[81,5667,5669],{"id":5668},"原因その３npm-run-productionを動かすように設定","原因その３：npm run productionを動かすように設定",[13,5671,5672,5673,5676],{},"その２に加えて、今度はherokuデプロイの際にLaravel Mixのビルドスクリプトがキックされるように設定します。herokuはNode.jsのプロジェクトがあり、そのスクリプトに",[39,5674,5675],{},"npm run build","がある場合はそれを実行してくれます。",[13,5678,5679,5680,5682,5683,5686],{},"初期の",[39,5681,2965],{},"のスクリプトは",[39,5684,5685],{},"build","がないので、mixが実行されません。",[226,5688,5693],{"className":5689,"code":5690,"filename":5691,"language":5692,"meta":231,"style":231},"language-json shiki shiki-themes material-theme-ocean","\"scripts\": {\n    \"dev\": \"npm run development\",\n    \"development\": \"cross-env NODE_ENV=development node_modules\u002Fwebpack\u002Fbin\u002Fwebpack.js --progress --config=node_modules\u002Flaravel-mix\u002Fsetup\u002Fwebpack.config.js\",\n    \"watch\": \"npm run development -- --watch\",\n    \"watch-poll\": \"npm run watch -- --watch-poll\",\n    \"hot\": \"cross-env NODE_ENV=development node_modules\u002Fwebpack-dev-server\u002Fbin\u002Fwebpack-dev-server.js --inline --hot --disable-host-check --config=node_modules\u002Flaravel-mix\u002Fsetup\u002Fwebpack.config.js\",\n    \"prod\": \"npm run production\",\n    \"production\": \"cross-env NODE_ENV=production node_modules\u002Fwebpack\u002Fbin\u002Fwebpack.js --no-progress --config=node_modules\u002Flaravel-mix\u002Fsetup\u002Fwebpack.config.js\"\n},\n","pakage.json","json",[39,5694,5695,5709,5730,5750,5770,5790,5810,5830,5847],{"__ignoreMap":231},[235,5696,5697,5699,5702,5704,5707],{"class":237,"line":238},[235,5698,1516],{"class":499},[235,5700,5701],{"class":245},"scripts",[235,5703,1516],{"class":499},[235,5705,5706],{"class":519},": ",[235,5708,1975],{"class":499},[235,5710,5711,5714,5717,5719,5721,5723,5726,5728],{"class":237,"line":249},[235,5712,5713],{"class":499},"    \"",[235,5715,5716],{"class":1374},"dev",[235,5718,1516],{"class":499},[235,5720,500],{"class":499},[235,5722,595],{"class":499},[235,5724,5725],{"class":245},"npm run development",[235,5727,1516],{"class":499},[235,5729,1983],{"class":499},[235,5731,5732,5734,5737,5739,5741,5743,5746,5748],{"class":237,"line":259},[235,5733,5713],{"class":499},[235,5735,5736],{"class":1374},"development",[235,5738,1516],{"class":499},[235,5740,500],{"class":499},[235,5742,595],{"class":499},[235,5744,5745],{"class":245},"cross-env NODE_ENV=development node_modules\u002Fwebpack\u002Fbin\u002Fwebpack.js --progress --config=node_modules\u002Flaravel-mix\u002Fsetup\u002Fwebpack.config.js",[235,5747,1516],{"class":499},[235,5749,1983],{"class":499},[235,5751,5752,5754,5757,5759,5761,5763,5766,5768],{"class":237,"line":266},[235,5753,5713],{"class":499},[235,5755,5756],{"class":1374},"watch",[235,5758,1516],{"class":499},[235,5760,500],{"class":499},[235,5762,595],{"class":499},[235,5764,5765],{"class":245},"npm run development -- --watch",[235,5767,1516],{"class":499},[235,5769,1983],{"class":499},[235,5771,5772,5774,5777,5779,5781,5783,5786,5788],{"class":237,"line":275},[235,5773,5713],{"class":499},[235,5775,5776],{"class":1374},"watch-poll",[235,5778,1516],{"class":499},[235,5780,500],{"class":499},[235,5782,595],{"class":499},[235,5784,5785],{"class":245},"npm run watch -- --watch-poll",[235,5787,1516],{"class":499},[235,5789,1983],{"class":499},[235,5791,5792,5794,5797,5799,5801,5803,5806,5808],{"class":237,"line":283},[235,5793,5713],{"class":499},[235,5795,5796],{"class":1374},"hot",[235,5798,1516],{"class":499},[235,5800,500],{"class":499},[235,5802,595],{"class":499},[235,5804,5805],{"class":245},"cross-env NODE_ENV=development node_modules\u002Fwebpack-dev-server\u002Fbin\u002Fwebpack-dev-server.js --inline --hot --disable-host-check --config=node_modules\u002Flaravel-mix\u002Fsetup\u002Fwebpack.config.js",[235,5807,1516],{"class":499},[235,5809,1983],{"class":499},[235,5811,5812,5814,5817,5819,5821,5823,5826,5828],{"class":237,"line":291},[235,5813,5713],{"class":499},[235,5815,5816],{"class":1374},"prod",[235,5818,1516],{"class":499},[235,5820,500],{"class":499},[235,5822,595],{"class":499},[235,5824,5825],{"class":245},"npm run production",[235,5827,1516],{"class":499},[235,5829,1983],{"class":499},[235,5831,5832,5834,5836,5838,5840,5842,5845],{"class":237,"line":298},[235,5833,5713],{"class":499},[235,5835,5620],{"class":1374},[235,5837,1516],{"class":499},[235,5839,500],{"class":499},[235,5841,595],{"class":499},[235,5843,5844],{"class":245},"cross-env NODE_ENV=production node_modules\u002Fwebpack\u002Fbin\u002Fwebpack.js --no-progress --config=node_modules\u002Flaravel-mix\u002Fsetup\u002Fwebpack.config.js",[235,5846,601],{"class":499},[235,5848,5849,5851],{"class":237,"line":380},[235,5850,1949],{"class":499},[235,5852,1983],{"class":519},[13,5854,5855],{},"ちょうどprodというのがあるのでbuildに変えてやりましょう。",[226,5857,5859],{"className":5689,"code":5858,"filename":5691,"language":5692,"meta":231,"style":231},"\"scripts\": {\n    \"dev\": \"npm run development\",\n    \"development\": \"cross-env NODE_ENV=development node_modules\u002Fwebpack\u002Fbin\u002Fwebpack.js --progress --config=node_modules\u002Flaravel-mix\u002Fsetup\u002Fwebpack.config.js\",\n    \"watch\": \"npm run development -- --watch\",\n    \"watch-poll\": \"npm run watch -- --watch-poll\",\n    \"hot\": \"cross-env NODE_ENV=development node_modules\u002Fwebpack-dev-server\u002Fbin\u002Fwebpack-dev-server.js --inline --hot --disable-host-check --config=node_modules\u002Flaravel-mix\u002Fsetup\u002Fwebpack.config.js\",\n    \"build\": \"npm run production\",\n    \"production\": \"cross-env NODE_ENV=production node_modules\u002Fwebpack\u002Fbin\u002Fwebpack.js --no-progress --config=node_modules\u002Flaravel-mix\u002Fsetup\u002Fwebpack.config.js\"\n},\n",[39,5860,5861,5873,5891,5909,5927,5945,5963,5981,5997],{"__ignoreMap":231},[235,5862,5863,5865,5867,5869,5871],{"class":237,"line":238},[235,5864,1516],{"class":499},[235,5866,5701],{"class":245},[235,5868,1516],{"class":499},[235,5870,5706],{"class":519},[235,5872,1975],{"class":499},[235,5874,5875,5877,5879,5881,5883,5885,5887,5889],{"class":237,"line":249},[235,5876,5713],{"class":499},[235,5878,5716],{"class":1374},[235,5880,1516],{"class":499},[235,5882,500],{"class":499},[235,5884,595],{"class":499},[235,5886,5725],{"class":245},[235,5888,1516],{"class":499},[235,5890,1983],{"class":499},[235,5892,5893,5895,5897,5899,5901,5903,5905,5907],{"class":237,"line":259},[235,5894,5713],{"class":499},[235,5896,5736],{"class":1374},[235,5898,1516],{"class":499},[235,5900,500],{"class":499},[235,5902,595],{"class":499},[235,5904,5745],{"class":245},[235,5906,1516],{"class":499},[235,5908,1983],{"class":499},[235,5910,5911,5913,5915,5917,5919,5921,5923,5925],{"class":237,"line":266},[235,5912,5713],{"class":499},[235,5914,5756],{"class":1374},[235,5916,1516],{"class":499},[235,5918,500],{"class":499},[235,5920,595],{"class":499},[235,5922,5765],{"class":245},[235,5924,1516],{"class":499},[235,5926,1983],{"class":499},[235,5928,5929,5931,5933,5935,5937,5939,5941,5943],{"class":237,"line":275},[235,5930,5713],{"class":499},[235,5932,5776],{"class":1374},[235,5934,1516],{"class":499},[235,5936,500],{"class":499},[235,5938,595],{"class":499},[235,5940,5785],{"class":245},[235,5942,1516],{"class":499},[235,5944,1983],{"class":499},[235,5946,5947,5949,5951,5953,5955,5957,5959,5961],{"class":237,"line":283},[235,5948,5713],{"class":499},[235,5950,5796],{"class":1374},[235,5952,1516],{"class":499},[235,5954,500],{"class":499},[235,5956,595],{"class":499},[235,5958,5805],{"class":245},[235,5960,1516],{"class":499},[235,5962,1983],{"class":499},[235,5964,5965,5967,5969,5971,5973,5975,5977,5979],{"class":237,"line":291},[235,5966,5713],{"class":499},[235,5968,5685],{"class":1374},[235,5970,1516],{"class":499},[235,5972,500],{"class":499},[235,5974,595],{"class":499},[235,5976,5825],{"class":245},[235,5978,1516],{"class":499},[235,5980,1983],{"class":499},[235,5982,5983,5985,5987,5989,5991,5993,5995],{"class":237,"line":298},[235,5984,5713],{"class":499},[235,5986,5620],{"class":1374},[235,5988,1516],{"class":499},[235,5990,500],{"class":499},[235,5992,595],{"class":499},[235,5994,5844],{"class":245},[235,5996,601],{"class":499},[235,5998,5999,6001],{"class":237,"line":380},[235,6000,1949],{"class":499},[235,6002,1983],{"class":519},[13,6004,6005,6006,6008,6009,6011],{},"こうすれば",[39,6007,5675],{}," が ",[39,6010,5825],{}," を実行して本番用のアセットを作ってくれます。これで解決です。",[74,6013,6014],{"id":6014},"ビルドアセットがうまく出力されない時に考えるべきこと",[13,6016,6017],{},"今回はいくつかのファイルをビルドする必要があり、デプロイ作業が自動で行われる状況でした。開発環境ではアセットがあるのに、本番だとなくなっているという場合は以下のことを考えましょう。",[17,6019,6020,6023,6026],{},[20,6021,6022],{},"ファイルはある？（サーバーに入ってlsしてチェック）",[20,6024,6025],{},"ビルドに必要なモジュールがインストールされているか？",[20,6027,6028],{},"ビルドスクリプトはキックされているか？",[13,6030,6031,6032,6035],{},"大体こうゆうときはビルドがうまくいっていないので、そこからチェックしていくといいです。ちなみに私が検索した時のワードは ",[3700,6033,6034],{},"「heroku laravel mix ビルド」"," でした。それではまた。",[2398,6037,6038],{},"html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":231,"searchDepth":259,"depth":259,"links":6040},[6041,6042,6047],{"id":5564,"depth":249,"text":5565},{"id":5581,"depth":249,"text":5582,"children":6043},[6044,6045,6046],{"id":5599,"depth":259,"text":5600},{"id":5609,"depth":259,"text":5610},{"id":5668,"depth":259,"text":5669},{"id":6014,"depth":249,"text":6014},[2426],"2021-03-27",{},"\u002Farticles\u002Flaravel-mixx-heroku-fail",{"title":5550,"description":5550},"articles\u002Flaravel-mixx-heroku-fail",[1261,6055,970,6056],"webpack","infrastructure","_mix\u002F8_laravel-icon.3f70de72f3.jpg","zvECdzfredOonr51btVrUrKqFCKw247gmW-jXba47XU",{"id":6060,"title":6061,"body":6062,"category":7028,"createdAt":7029,"description":6061,"extension":2429,"index":2438,"meta":7030,"navigation":262,"path":7031,"publish":262,"seo":7032,"series":2438,"seriesTitle":2438,"stem":7033,"tag":7034,"thumbnail":7035,"updatedAt":2438,"__hash__":7036},"articles\u002Farticles\u002Ftweet-check-laravel-jpb-scheduler.md","Larave 7.0でキュー・ジョブ・スケジューラー触ってみる。取得したツイートが存在するか毎夜毎夜チェックするジョブとスケジューラー。",{"type":10,"value":6063,"toc":7015},[6064,6067,6070,6073,6076,6079,6082,6090,6093,6096,6103,6115,6118,6121,6124,6127,6133,6136,6139,6142,6148,6151,6155,6158,6269,6279,6567,6570,6578,6581,6584,6714,6717,6724,6737,6740,6746,6749,6752,6759,6775,6782,6931,6941,6953,6959,6962,6969,6975,6978,6984,6990,6997,7003,7006,7009,7012],[13,6065,6066],{},"こんにちはjunです。仕事でLaravelを触ることになり必死にドキュメントを読んだりして対策をしています。フルスタックフレームワークと言われているほど、Laravelは機能が豊富なので想像していた物が作れて感動しています。",[13,6068,6069],{},"そこで今回はドキュメントをみてもイマイチパッとしない、ジョブ(Job)、キュー(Queue)、スケジューラー(Scheduler)を触りながら機能を確かめていきたいと思います。",[74,6071,6072],{"id":6072},"作りたい物",[13,6074,6075],{},"Laravelを用いて外部APIと通信して、取得したデータをDBに入れたりブラウザ上で操作するアプリケーションを作成していたときにある問題にぶつかりました。TwitterAPIを用いてツイートの情報を保存していたのですが、非公開もしくは削除されたため表示できないツイートがありました。",[13,6077,6078],{},"削除されたツイートを破棄するシステムを構築する必要があります。システム自体は簡単でDBから保存されたツイート一つ一つのIDを取得してTwitterAPIをを投げて、ツイートが存在するかの結果を確認すればいいだけです。スクレイピングするような感じです。しかしこの方法には課題があります。",[13,6080,6081],{},"保存されたツイートが大量にある場合、Twitterのサーバーに大量のリクエストを短時間に送信してしまい、Dosとなってしまいます。そのため各リクエスト毎に最低1秒程でも緩急を置く必要があります。その場合、その処理が終了する時間は保存されたツイート量によってはかなり長くなります。",[13,6083,6084,6085,6089],{},"大体、このような長くなりがちな処理はキューとして、サーバー上で非同期に行わせるのがベストです。そしてLarvelではジョブを簡単に実装できる",[197,6086,6088],{"href":6087,"target":2917},"https:\u002F\u002Freadouble.com\u002Flaravel\u002F7.x\u002Fja\u002Fscheduling.html","「タスクスケジュール」","という物があります。",[13,6091,6092],{},"今回は24時になると保存したツイートの存在を確認してくれる自動実行ジョブを構築したいと思います。",[74,6094,6095],{"id":6095},"タスクスケージューラーを理解する",[13,6097,6098,6102],{},[197,6099,6101],{"href":6100,"target":2917},"https:\u002F\u002Flaravel.com\u002Fdocs\u002F7.x\u002Fscheduling","公式のドキュメント","を見てみましょう。おおよそですが実装の手順としては",[6104,6105,6106,6109,6112],"ol",{},[20,6107,6108],{},"ジョブの定義（実際の処理ロジック）をapp\u002Fjob\u002F 配下に作成する。",[20,6110,6111],{},"行うジョブやその実行時間は app\u002Fconsole\u002Fkernel.php に記述。",[20,6113,6114],{},"cronに毎分Larvelスケジューラーを実行させるコードを記述。",[13,6116,6117],{},"こんな感じです。細かく解説していきます。",[74,6119,6120],{"id":6120},"ジョブを作成する",[81,6122,6123],{"id":6123},"キューを使えるようにする",[13,6125,6126],{},"まずはLaracelでジョブを実行できる環境を整えます。「キュー」に関するドキュメントを見ると以下のコマンドを唱えてジョブを記録するテーブルを作成します。",[226,6128,6131],{"className":6129,"code":6130,"language":933},[931],"php artisan queue:table\n\nphp artisan migrate\n",[39,6132,6130],{"__ignoreMap":231},[13,6134,6135],{},"このコマンドを唱えると「jobs」「failed_jobs」というテーブルが作成されます。ジョブとキューはセットで使われることが多いので上記のコマンドを唱えておきましょう",[88,6137,6138],{"id":6138},"ジョブファイルを作成する",[13,6140,6141],{},"ジョブファイルは app\u002Fjob 配下に作成します。しかし最初はこのjobディレクトリは存在しないので以下のコマンドを唱えて、ジョブファイルのテンプレートとディレクトリを作ってもらいます。",[226,6143,6146],{"className":6144,"code":6145,"language":933},[931],"php artisan make:job YOUR_JOB_NAME\n",[39,6147,6145],{"__ignoreMap":231},[13,6149,6150],{},"「YOUR_JOB_NAME」の部分にジョブの名前を入力してください。他のmakeコマンドと同じ感じですね。このコマンドを唱えるとjobディレクトリとjobクラスが書かれたファイルが配下に作成されます。そのファイルにジョブの実行処理を書いていきます。",[81,6152,6154],{"id":6153},"twitterにapiを送るジョブを記述","TwitterにAPIを送るジョブを記述",[13,6156,6157],{},"今回はジョブ名を「CheckNotFoundUrls」として作成します。ジョブファイルは作成するとまず下記のように書かれています。",[226,6159,6161],{"className":1025,"code":6160,"language":1028,"meta":231,"style":231},"\u003C?php\nnamespace App\\Jobs;\n\nuse Illuminate\\Bus\\Queueable;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Illuminate\\Foundation\\Bus\\Dispatchable;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Illuminate\\Queue\\SerializesModels;\n\nclass CheckNotFoundUrls implements ShouldQueue\n{\n    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;\n\n    public function __construct()\n    {\n        \u002F\u002F\n    }\n\n    public function handle()\n    {\n        \u002F\u002Fここに処理を記述\n    }\n}\n",[39,6162,6163,6168,6173,6177,6182,6187,6192,6197,6202,6206,6211,6215,6220,6224,6229,6234,6239,6243,6247,6252,6256,6261,6265],{"__ignoreMap":231},[235,6164,6165],{"class":237,"line":238},[235,6166,6167],{},"\u003C?php\n",[235,6169,6170],{"class":237,"line":249},[235,6171,6172],{},"namespace App\\Jobs;\n",[235,6174,6175],{"class":237,"line":259},[235,6176,263],{"emptyLinePlaceholder":262},[235,6178,6179],{"class":237,"line":266},[235,6180,6181],{},"use Illuminate\\Bus\\Queueable;\n",[235,6183,6184],{"class":237,"line":275},[235,6185,6186],{},"use Illuminate\\Contracts\\Queue\\ShouldQueue;\n",[235,6188,6189],{"class":237,"line":283},[235,6190,6191],{},"use Illuminate\\Foundation\\Bus\\Dispatchable;\n",[235,6193,6194],{"class":237,"line":291},[235,6195,6196],{},"use Illuminate\\Queue\\InteractsWithQueue;\n",[235,6198,6199],{"class":237,"line":298},[235,6200,6201],{},"use Illuminate\\Queue\\SerializesModels;\n",[235,6203,6204],{"class":237,"line":380},[235,6205,263],{"emptyLinePlaceholder":262},[235,6207,6208],{"class":237,"line":465},[235,6209,6210],{},"class CheckNotFoundUrls implements ShouldQueue\n",[235,6212,6213],{"class":237,"line":590},[235,6214,1975],{},[235,6216,6217],{"class":237,"line":604},[235,6218,6219],{},"    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;\n",[235,6221,6222],{"class":237,"line":612},[235,6223,263],{"emptyLinePlaceholder":262},[235,6225,6226],{"class":237,"line":623},[235,6227,6228],{},"    public function __construct()\n",[235,6230,6231],{"class":237,"line":634},[235,6232,6233],{},"    {\n",[235,6235,6236],{"class":237,"line":4},[235,6237,6238],{},"        \u002F\u002F\n",[235,6240,6241],{"class":237,"line":652},[235,6242,2180],{},[235,6244,6245],{"class":237,"line":660},[235,6246,263],{"emptyLinePlaceholder":262},[235,6248,6249],{"class":237,"line":669},[235,6250,6251],{},"    public function handle()\n",[235,6253,6254],{"class":237,"line":677},[235,6255,6233],{},[235,6257,6258],{"class":237,"line":685},[235,6259,6260],{},"        \u002F\u002Fここに処理を記述\n",[235,6262,6263],{"class":237,"line":693},[235,6264,2180],{},[235,6266,6267],{"class":237,"line":701},[235,6268,1316],{},[13,6270,6271,6274,6275,6278],{},[39,6272,6273],{},"handle()","に処理内容を記述します。ジョブを実行するときはこのクラスを読み込み、ジョブインスタンスを作成し、",[39,6276,6277],{},"dispatch()","するとジョブが実行されます。まあ、とりあえずTwitterにAPIを飛ばすコードを書きます。コードは載せますが詳細の説明は省きます。",[226,6280,6282],{"className":1025,"code":6281,"language":1028,"meta":231,"style":231},"\u003C?php\nnamespace App\\Jobs;\n\n\u002F\u002FTwitter API ライブラリ\nuse Abraham\\TwitterOAuth\\TwitterOAuth;\n\nuse App\\Models\\Tweets; \u002F\u002F ツイート情報が格納されたテーブルに接続するモデル\nuse App\\Models\\JobReport;　\u002F\u002F 自作ジョブの結果を記録しておくテーブル\n\nuse Illuminate\\Bus\\Queueable;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Illuminate\\Foundation\\Bus\\Dispatchable;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Illuminate\\Queue\\SerializesModels;\n\nclass CheckNotFoundUrls implements ShouldQueue\n{\n    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;\n\n    public function __construct()\n    {\n        \u002F\u002F\n    }\n\n    public function handle()\n    {\n        try{\n            \u002F\u002F ツイート情報があるテーブルからツイート情報（JSON）と必要なレコードを取得\n            $datas = Tweets::select('tweet_rawdata','id','user_id')->get();\n            $deletedTweetOwner = array();\n    \n            foreach($datas as $val){\n                \u002F\u002F'tweet_rawdata'カラムはjsonなので一旦decode。\n                \u002F\u002Fjsonからid_strというツイートIDを取得\n                $decodeTweetData = json_decode($val->tweet_rawdata);\n                $tweet_id = $decodeTweetData->id_str;\n\n                \u002F\u002FAPIに接続するための準備（ライブラリ）をして、APIを送信\n                $connection = new TwitterOAuth(env('TW_CONSUMER_KEY'), env('TW_CONSUMER_KEY_SECRET'), env('TW_ACCESS_TOKEN'), env('TW_ACCESS_TOKEN_SECRET'));\n                $connection->setTimeouts(10, 15);\n                $content = $connection->get(\"statuses\u002Fshow\",['id'=>intval($tweet_id)]);\n    \n                \u002F\u002F請求したツイートIDのツイートが存在しないという結果ならばレコードのIDを $deletedTweetOwnerにユーザーIDを記録\n                if(property_exists($content,'errors')){\n                    switch($content->errors[0]->code){\n                        case 144:\n                            Tweets::destroy($val->id);\n                            array_push($deletedTweetOwner,$val->id);\n                        break;\n                    }\n                }\n                \u002F\u002FDosにならないように1秒止める\n                sleep(1.0);\n            }\n  \n        }catch(\\Exception $e){\n            \u002F\u002F処理が失敗したら管理者用のレポートにエラ〜メッセージを記録する。\n            JobReport::reportException(0,$e->getMessage());\n        }\n    }\n}\n",[39,6283,6284,6288,6292,6296,6301,6306,6310,6315,6320,6324,6328,6332,6336,6340,6344,6348,6352,6356,6360,6364,6368,6372,6376,6380,6384,6388,6392,6397,6402,6407,6412,6416,6421,6426,6431,6436,6441,6445,6450,6455,6460,6465,6469,6474,6479,6484,6489,6494,6499,6504,6509,6514,6519,6524,6528,6534,6540,6546,6552,6557,6562],{"__ignoreMap":231},[235,6285,6286],{"class":237,"line":238},[235,6287,6167],{},[235,6289,6290],{"class":237,"line":249},[235,6291,6172],{},[235,6293,6294],{"class":237,"line":259},[235,6295,263],{"emptyLinePlaceholder":262},[235,6297,6298],{"class":237,"line":266},[235,6299,6300],{},"\u002F\u002FTwitter API ライブラリ\n",[235,6302,6303],{"class":237,"line":275},[235,6304,6305],{},"use Abraham\\TwitterOAuth\\TwitterOAuth;\n",[235,6307,6308],{"class":237,"line":283},[235,6309,263],{"emptyLinePlaceholder":262},[235,6311,6312],{"class":237,"line":291},[235,6313,6314],{},"use App\\Models\\Tweets; \u002F\u002F ツイート情報が格納されたテーブルに接続するモデル\n",[235,6316,6317],{"class":237,"line":298},[235,6318,6319],{},"use App\\Models\\JobReport;　\u002F\u002F 自作ジョブの結果を記録しておくテーブル\n",[235,6321,6322],{"class":237,"line":380},[235,6323,263],{"emptyLinePlaceholder":262},[235,6325,6326],{"class":237,"line":465},[235,6327,6181],{},[235,6329,6330],{"class":237,"line":590},[235,6331,6186],{},[235,6333,6334],{"class":237,"line":604},[235,6335,6191],{},[235,6337,6338],{"class":237,"line":612},[235,6339,6196],{},[235,6341,6342],{"class":237,"line":623},[235,6343,6201],{},[235,6345,6346],{"class":237,"line":634},[235,6347,263],{"emptyLinePlaceholder":262},[235,6349,6350],{"class":237,"line":4},[235,6351,6210],{},[235,6353,6354],{"class":237,"line":652},[235,6355,1975],{},[235,6357,6358],{"class":237,"line":660},[235,6359,6219],{},[235,6361,6362],{"class":237,"line":669},[235,6363,263],{"emptyLinePlaceholder":262},[235,6365,6366],{"class":237,"line":677},[235,6367,6228],{},[235,6369,6370],{"class":237,"line":685},[235,6371,6233],{},[235,6373,6374],{"class":237,"line":693},[235,6375,6238],{},[235,6377,6378],{"class":237,"line":701},[235,6379,2180],{},[235,6381,6382],{"class":237,"line":711},[235,6383,263],{"emptyLinePlaceholder":262},[235,6385,6386],{"class":237,"line":722},[235,6387,6251],{},[235,6389,6390],{"class":237,"line":729},[235,6391,6233],{},[235,6393,6394],{"class":237,"line":740},[235,6395,6396],{},"        try{\n",[235,6398,6399],{"class":237,"line":751},[235,6400,6401],{},"            \u002F\u002F ツイート情報があるテーブルからツイート情報（JSON）と必要なレコードを取得\n",[235,6403,6404],{"class":237,"line":762},[235,6405,6406],{},"            $datas = Tweets::select('tweet_rawdata','id','user_id')->get();\n",[235,6408,6409],{"class":237,"line":773},[235,6410,6411],{},"            $deletedTweetOwner = array();\n",[235,6413,6414],{"class":237,"line":784},[235,6415,5024],{},[235,6417,6418],{"class":237,"line":793},[235,6419,6420],{},"            foreach($datas as $val){\n",[235,6422,6423],{"class":237,"line":805},[235,6424,6425],{},"                \u002F\u002F'tweet_rawdata'カラムはjsonなので一旦decode。\n",[235,6427,6428],{"class":237,"line":814},[235,6429,6430],{},"                \u002F\u002Fjsonからid_strというツイートIDを取得\n",[235,6432,6433],{"class":237,"line":822},[235,6434,6435],{},"                $decodeTweetData = json_decode($val->tweet_rawdata);\n",[235,6437,6438],{"class":237,"line":832},[235,6439,6440],{},"                $tweet_id = $decodeTweetData->id_str;\n",[235,6442,6443],{"class":237,"line":3536},[235,6444,263],{"emptyLinePlaceholder":262},[235,6446,6447],{"class":237,"line":3541},[235,6448,6449],{},"                \u002F\u002FAPIに接続するための準備（ライブラリ）をして、APIを送信\n",[235,6451,6452],{"class":237,"line":3546},[235,6453,6454],{},"                $connection = new TwitterOAuth(env('TW_CONSUMER_KEY'), env('TW_CONSUMER_KEY_SECRET'), env('TW_ACCESS_TOKEN'), env('TW_ACCESS_TOKEN_SECRET'));\n",[235,6456,6457],{"class":237,"line":3552},[235,6458,6459],{},"                $connection->setTimeouts(10, 15);\n",[235,6461,6462],{"class":237,"line":3557},[235,6463,6464],{},"                $content = $connection->get(\"statuses\u002Fshow\",['id'=>intval($tweet_id)]);\n",[235,6466,6467],{"class":237,"line":3563},[235,6468,5024],{},[235,6470,6471],{"class":237,"line":3569},[235,6472,6473],{},"                \u002F\u002F請求したツイートIDのツイートが存在しないという結果ならばレコードのIDを $deletedTweetOwnerにユーザーIDを記録\n",[235,6475,6476],{"class":237,"line":3575},[235,6477,6478],{},"                if(property_exists($content,'errors')){\n",[235,6480,6481],{"class":237,"line":3581},[235,6482,6483],{},"                    switch($content->errors[0]->code){\n",[235,6485,6486],{"class":237,"line":3587},[235,6487,6488],{},"                        case 144:\n",[235,6490,6491],{"class":237,"line":3593},[235,6492,6493],{},"                            Tweets::destroy($val->id);\n",[235,6495,6496],{"class":237,"line":3599},[235,6497,6498],{},"                            array_push($deletedTweetOwner,$val->id);\n",[235,6500,6501],{"class":237,"line":3605},[235,6502,6503],{},"                        break;\n",[235,6505,6506],{"class":237,"line":3611},[235,6507,6508],{},"                    }\n",[235,6510,6511],{"class":237,"line":3616},[235,6512,6513],{},"                }\n",[235,6515,6516],{"class":237,"line":3621},[235,6517,6518],{},"                \u002F\u002FDosにならないように1秒止める\n",[235,6520,6521],{"class":237,"line":3626},[235,6522,6523],{},"                sleep(1.0);\n",[235,6525,6526],{"class":237,"line":3632},[235,6527,5111],{},[235,6529,6531],{"class":237,"line":6530},55,[235,6532,6533],{},"  \n",[235,6535,6537],{"class":237,"line":6536},56,[235,6538,6539],{},"        }catch(\\Exception $e){\n",[235,6541,6543],{"class":237,"line":6542},57,[235,6544,6545],{},"            \u002F\u002F処理が失敗したら管理者用のレポートにエラ〜メッセージを記録する。\n",[235,6547,6549],{"class":237,"line":6548},58,[235,6550,6551],{},"            JobReport::reportException(0,$e->getMessage());\n",[235,6553,6555],{"class":237,"line":6554},59,[235,6556,4847],{},[235,6558,6560],{"class":237,"line":6559},60,[235,6561,2180],{},[235,6563,6565],{"class":237,"line":6564},61,[235,6566,1316],{},[13,6568,6569],{},"ちなみにTwitterへのAPIアクセスには Abraham\\TwitterOAuth というTwttiter公式が紹介するサードパーティライブラリを使用しています。composerで読み込んでuseすればすぐに使え、上記のようにouthインスタンスを作成するだけで使えます。簡単なのでLarvelでTwitterAPIを使う時などにおすすめです。",[1224,6571,6573,6574],{"className":6572},[1227,2913],"\n    ",[197,6575,6577],{"href":6576,"target":2917},"https:\u002F\u002Fgithub.com\u002Fabraham\u002Ftwitteroauth","GITHUB：abraham\u002Ftwitteroauth\n    ",[13,6579,6580],{},"とりあえずこれでジョブは完成しました。試しにチェックをしてみたいですが、なんとかしてこのジョブを実行する必要があります。しかしLaravelには定義したジョブを実行するコマンドが見当たりません。（もしかしたら見落としてるだけかも）",[13,6582,6583],{},"コマンドでジョブを叩いて実行したいので、以下のカスタムコマンドを自作します。",[226,6585,6587],{"className":1025,"code":6586,"language":1028,"meta":231,"style":231},"\u003C?php\n\nnamespace App\\Console\\Commands;\n\nuse Illuminate\\Console\\Command;\n\nclass DispatchJob extends Command\n{\n    protected $signature = 'job:dispatch {job}';\n\n    \u002F**\n     * The console command description.\n     *\n     * @var string\n     *\u002F\n    protected $description = 'Dispatch job';\n\n    public function __construct()\n    {\n        parent::__construct();\n    }\n\n    public function handle()\n    {\n        $class = '\\\\App\\\\Jobs\\\\' . $this->argument('job');\n        dispatch(new $class());\n    }\n}\n",[39,6588,6589,6593,6597,6602,6606,6611,6615,6620,6624,6629,6633,6638,6643,6648,6653,6658,6663,6667,6671,6675,6680,6684,6688,6692,6696,6701,6706,6710],{"__ignoreMap":231},[235,6590,6591],{"class":237,"line":238},[235,6592,6167],{},[235,6594,6595],{"class":237,"line":249},[235,6596,263],{"emptyLinePlaceholder":262},[235,6598,6599],{"class":237,"line":259},[235,6600,6601],{},"namespace App\\Console\\Commands;\n",[235,6603,6604],{"class":237,"line":266},[235,6605,263],{"emptyLinePlaceholder":262},[235,6607,6608],{"class":237,"line":275},[235,6609,6610],{},"use Illuminate\\Console\\Command;\n",[235,6612,6613],{"class":237,"line":283},[235,6614,263],{"emptyLinePlaceholder":262},[235,6616,6617],{"class":237,"line":291},[235,6618,6619],{},"class DispatchJob extends Command\n",[235,6621,6622],{"class":237,"line":298},[235,6623,1975],{},[235,6625,6626],{"class":237,"line":380},[235,6627,6628],{},"    protected $signature = 'job:dispatch {job}';\n",[235,6630,6631],{"class":237,"line":465},[235,6632,263],{"emptyLinePlaceholder":262},[235,6634,6635],{"class":237,"line":590},[235,6636,6637],{},"    \u002F**\n",[235,6639,6640],{"class":237,"line":604},[235,6641,6642],{},"     * The console command description.\n",[235,6644,6645],{"class":237,"line":612},[235,6646,6647],{},"     *\n",[235,6649,6650],{"class":237,"line":623},[235,6651,6652],{},"     * @var string\n",[235,6654,6655],{"class":237,"line":634},[235,6656,6657],{},"     *\u002F\n",[235,6659,6660],{"class":237,"line":4},[235,6661,6662],{},"    protected $description = 'Dispatch job';\n",[235,6664,6665],{"class":237,"line":652},[235,6666,263],{"emptyLinePlaceholder":262},[235,6668,6669],{"class":237,"line":660},[235,6670,6228],{},[235,6672,6673],{"class":237,"line":669},[235,6674,6233],{},[235,6676,6677],{"class":237,"line":677},[235,6678,6679],{},"        parent::__construct();\n",[235,6681,6682],{"class":237,"line":685},[235,6683,2180],{},[235,6685,6686],{"class":237,"line":693},[235,6687,263],{"emptyLinePlaceholder":262},[235,6689,6690],{"class":237,"line":701},[235,6691,6251],{},[235,6693,6694],{"class":237,"line":711},[235,6695,6233],{},[235,6697,6698],{"class":237,"line":722},[235,6699,6700],{},"        $class = '\\\\App\\\\Jobs\\\\' . $this->argument('job');\n",[235,6702,6703],{"class":237,"line":729},[235,6704,6705],{},"        dispatch(new $class());\n",[235,6707,6708],{"class":237,"line":740},[235,6709,2180],{},[235,6711,6712],{"class":237,"line":751},[235,6713,1316],{},[13,6715,6716],{},"このstack overflowの記事を引用しています。ありがとうございます。",[1224,6718,6573,6720],{"className":6719},[1227,2913],[197,6721,6723],{"href":6722,"target":2917},"https:\u002F\u002Fstackoverflow.com\u002Fquestions\u002F43357472\u002Fhow-to-manually-run-a-laravel-lumen-job-using-command-line\u002F51045851","StackOverflow How to manually run a laravel\u002Flumen job using command line\n    ",[13,6725,6726,6727,6729,6730,6733,6734,6736],{},"このファイルをapp\u002FConsole\u002FCommands 配下に作成します。Console\u002FCommands 配下では",[39,6728,3103],{}," を用いたコマンドを自分で作成することができます。ここでは ",[39,6731,6732],{},"php artisan job:dispatch JOB_NAME"," と唱えればJOB_NAMEで書かれたジョブクラスの",[39,6735,6273],{},"を実行してくれます。",[13,6738,6739],{},"これを用いて以下のように唱えます。",[226,6741,6744],{"className":6742,"code":6743,"language":933},[931],"php artisan job:dispatch CheckNotFoundUrls\n",[39,6745,6743],{"__ignoreMap":231},[13,6747,6748],{},"するとprintなどを一時的につけて、結果を見ると無事に404のツイートの数が検知され、削除を実行してくれました。",[81,6750,6751],{"id":6751},"ジョブが実行される時間を定める",[13,6753,6754,6755,6758],{},"それでは次に上で作成したジョブをスケジューラーに登録します。スケジューラーで実行させるジョブは ",[39,6756,6757],{},"app\u002FConsole\u002FKernel.php"," にジョブインスタンスを作って実行します。",[13,6760,6761,6763,6764,6767,6768,6771,6772,6774],{},[39,6762,6757],{}," には ",[39,6765,6766],{},"schedule()"," というメソッドがあり ",[39,6769,6770],{},"php artisan run:schedule"," を実行すると ",[39,6773,6766],{}," が実行されます。",[13,6776,6777,6778,6781],{},"先ほどのジョブクラスをこの",[39,6779,6780],{},"Kernel.php","で使えるようにして、ジョブインスタンスを作成します。",[226,6783,6785],{"className":1025,"code":6784,"language":1028,"meta":231,"style":231},"\u003C?php\n\nnamespace App\\Console;\n\nuse Illuminate\\Console\\Scheduling\\Schedule;\nuse Illuminate\\Foundation\\Console\\Kernel as ConsoleKernel;\nuse App\\Jobs\\CheckNotFoundUrls; \u002F\u002Fここで先ほど作成したジョブクラス\nuse DateTime;\n\nclass Kernel extends ConsoleKernel\n{\n    \u002F**\n     * The Artisan commands provided by your application.\n     *\n     * @var array\n     *\u002F\n    protected $commands = [\n        \u002F\u002F\n    ];\n\n    \u002F**\n     * Define the application's command schedule.\n     *\n     * @param  \\Illuminate\\Console\\Scheduling\\Schedule  $schedule\n     * @return void\n     *\u002F\n    protected function schedule(Schedule $schedule)\n    {\n        \u002F\u002F実行するjobをキューに投入する。そして実行時間を設定。\n        $schedule->job(new CheckNotFoundUrls(),'checkUrl')->diary()\n    }\n}\n",[39,6786,6787,6791,6795,6800,6804,6809,6814,6819,6824,6828,6833,6837,6841,6846,6850,6855,6859,6864,6868,6873,6877,6881,6886,6890,6895,6900,6904,6909,6913,6918,6923,6927],{"__ignoreMap":231},[235,6788,6789],{"class":237,"line":238},[235,6790,6167],{},[235,6792,6793],{"class":237,"line":249},[235,6794,263],{"emptyLinePlaceholder":262},[235,6796,6797],{"class":237,"line":259},[235,6798,6799],{},"namespace App\\Console;\n",[235,6801,6802],{"class":237,"line":266},[235,6803,263],{"emptyLinePlaceholder":262},[235,6805,6806],{"class":237,"line":275},[235,6807,6808],{},"use Illuminate\\Console\\Scheduling\\Schedule;\n",[235,6810,6811],{"class":237,"line":283},[235,6812,6813],{},"use Illuminate\\Foundation\\Console\\Kernel as ConsoleKernel;\n",[235,6815,6816],{"class":237,"line":291},[235,6817,6818],{},"use App\\Jobs\\CheckNotFoundUrls; \u002F\u002Fここで先ほど作成したジョブクラス\n",[235,6820,6821],{"class":237,"line":298},[235,6822,6823],{},"use DateTime;\n",[235,6825,6826],{"class":237,"line":380},[235,6827,263],{"emptyLinePlaceholder":262},[235,6829,6830],{"class":237,"line":465},[235,6831,6832],{},"class Kernel extends ConsoleKernel\n",[235,6834,6835],{"class":237,"line":590},[235,6836,1975],{},[235,6838,6839],{"class":237,"line":604},[235,6840,6637],{},[235,6842,6843],{"class":237,"line":612},[235,6844,6845],{},"     * The Artisan commands provided by your application.\n",[235,6847,6848],{"class":237,"line":623},[235,6849,6647],{},[235,6851,6852],{"class":237,"line":634},[235,6853,6854],{},"     * @var array\n",[235,6856,6857],{"class":237,"line":4},[235,6858,6657],{},[235,6860,6861],{"class":237,"line":652},[235,6862,6863],{},"    protected $commands = [\n",[235,6865,6866],{"class":237,"line":660},[235,6867,6238],{},[235,6869,6870],{"class":237,"line":669},[235,6871,6872],{},"    ];\n",[235,6874,6875],{"class":237,"line":677},[235,6876,263],{"emptyLinePlaceholder":262},[235,6878,6879],{"class":237,"line":685},[235,6880,6637],{},[235,6882,6883],{"class":237,"line":693},[235,6884,6885],{},"     * Define the application's command schedule.\n",[235,6887,6888],{"class":237,"line":701},[235,6889,6647],{},[235,6891,6892],{"class":237,"line":711},[235,6893,6894],{},"     * @param  \\Illuminate\\Console\\Scheduling\\Schedule  $schedule\n",[235,6896,6897],{"class":237,"line":722},[235,6898,6899],{},"     * @return void\n",[235,6901,6902],{"class":237,"line":729},[235,6903,6657],{},[235,6905,6906],{"class":237,"line":740},[235,6907,6908],{},"    protected function schedule(Schedule $schedule)\n",[235,6910,6911],{"class":237,"line":751},[235,6912,6233],{},[235,6914,6915],{"class":237,"line":762},[235,6916,6917],{},"        \u002F\u002F実行するjobをキューに投入する。そして実行時間を設定。\n",[235,6919,6920],{"class":237,"line":773},[235,6921,6922],{},"        $schedule->job(new CheckNotFoundUrls(),'checkUrl')->diary()\n",[235,6924,6925],{"class":237,"line":784},[235,6926,2180],{},[235,6928,6929],{"class":237,"line":793},[235,6930,1316],{},[13,6932,6933,6936,6937,6940],{},[39,6934,6935],{},"$schedule->()","の第一引数にジョブインスタンス、第二引数にキュー名を入れます。キューというのは実行登録したジョブを一時的にためておく場所のことです。私もよくわかっていませんが、ジョブの実行数を制限したり、どの順に実行をしていくかなども決められるそうです。とりあえず今回は404チェック用のジョブキューとして ",[39,6938,6939],{},"checkUrl"," としておきます。",[13,6942,6943,6944,6947,6948,6952],{},"そして行末に",[39,6945,6946],{},"diary()","などがありますが、",[197,6949,6951],{"href":6950,"target":2917},"https:\u002F\u002Flaravel.com\u002Fdocs\u002F7.x\u002Fscheduling#schedule-frequency-options","公式ドキュメント","の通り実行する時間を指定できます。cron通りの時刻設定もできますし、1日ごと、週末ごと、月初に１回などよくある時間設定はメソッドを指定すれば設定できます。",[13,6954,6955,6956,6958],{},"上記のコードでは",[39,6957,6946],{},"なので毎日0時になるとジョブが実行されます。",[81,6960,6961],{"id":6961},"cronを設定する",[13,6963,6964,6965,6968],{},"ジョブを定義し、その時間も定めたのでもう自動で実行されそうな気もしますが、これだけでは動きません。指定の時間になったら特定のコマンドつまり",[39,6966,6967],{},"php artisan schedule:run"," を唱えてphpを動かさないといけません。",[13,6970,6971,6972,6974],{},"ここはサーバー(Linux)のcronというものを用いて、常に",[39,6973,6967],{}," を唱えるように設定します。cronとは時間設定基づいてコマンドを定期的に唱えてくれるUNIX系のOSに入っているプログラムです。",[13,6976,6977],{},"cronに以下の設定を記述します。",[226,6979,6982],{"className":6980,"code":6981,"language":933},[931],"~ $ crontab -e #これでcronの設定ファイルを編集できます。\n",[39,6983,6981],{"__ignoreMap":231},[226,6985,6988],{"className":6986,"code":6987,"language":933},[931],"* * * * * cd \u002Fpath-to-your-project && php artisan schedule:run >> \u002Fdev\u002Fnull 2>&1\n",[39,6989,6987],{"__ignoreMap":231},[13,6991,6992,6993,6996],{},"行の最初にある",[39,6994,6995],{},"(* * * * *)","は時間の設定をしています。この書き方の場合は毎分実行するようになります。つまり毎分Laravelのスケジューラーを叩いているだけなんです。",[13,6998,6999,7002],{},[39,7000,7001],{},"cd \u002Fpath-to-your-project"," はLaravelプロジェクトのルートへ移動するという意味です。そしてプロジェクトルートでスケジュールを実行します。",[74,7004,7005],{"id":7005},"以上でスケジューラー実装完了",[13,7007,7008],{},"以上でスケジューラーの実装が完了しました。今回のような404を毎日チェックしたり、定期的に時間を設定して行う処理などをアプリ内で行う機能を実装する時に便利です。",[13,7010,7011],{},"ユーザーごとに実行するかどうか、いつ実行するかの情報を格納しておくことで一般ユーザーが定期的な処理を行う処理を設定することもできます。",[2398,7013,7014],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":231,"searchDepth":259,"depth":259,"links":7016},[7017,7018,7019,7027],{"id":6072,"depth":249,"text":6072},{"id":6095,"depth":249,"text":6095},{"id":6120,"depth":249,"text":6120,"children":7020},[7021,7024,7025,7026],{"id":6123,"depth":259,"text":6123,"children":7022},[7023],{"id":6138,"depth":266,"text":6138},{"id":6153,"depth":259,"text":6154},{"id":6751,"depth":259,"text":6751},{"id":6961,"depth":259,"text":6961},{"id":7005,"depth":249,"text":7005},[2426],"2020-07-11",{},"\u002Farticles\u002Ftweet-check-laravel-jpb-scheduler",{"title":6061,"description":6061},"articles\u002Ftweet-check-laravel-jpb-scheduler",[970],"_mix\u002Flaravel-schedular-768x768.jpg","4qvDlezjHnhP7U6K9l6HlE2ilA1kcrRrD19TfDyuMNc",{"id":7038,"title":7039,"body":7040,"category":7878,"createdAt":7879,"description":7039,"extension":2429,"index":2438,"meta":7880,"navigation":262,"path":7881,"publish":262,"seo":7882,"series":2438,"seriesTitle":2438,"stem":7883,"tag":7884,"thumbnail":7885,"updatedAt":2438,"__hash__":7886},"articles\u002Farticles\u002Flalavel7-deplay-on-heroku.md","HerokuにLaravel 7.0で作成したアプリケーションをデプロイする。",{"type":10,"value":7041,"toc":7847},[7042,7045,7052,7056,7059,7070,7077,7081,7084,7098,7101,7104,7108,7114,7117,7129,7132,7135,7139,7142,7145,7152,7155,7187,7191,7195,7205,7208,7211,7218,7221,7225,7232,7235,7239,7245,7248,7251,7254,7257,7260,7267,7270,7273,7277,7280,7283,7290,7293,7297,7300,7304,7310,7313,7316,7319,7322,7330,7335,7338,7341,7344,7347,7351,7362,7366,7369,7373,7376,7541,7544,7558,7561,7568,7571,7579,7582,7589,7597,7600,7604,7610,7613,7616,7620,7623,7626,7629,7634,7637,7640,7644,7647,7653,7660,7663,7666,7670,7678,7681,7684,7690,7693,7699,7705,7711,7717,7720,7726,7729,7733,7739,7742,7759,7762,7766,7769,7783,7786,7790,7793,7796,7800,7803,7809,7812,7815,7818,7821,7824,7831,7834,7837,7841,7844],[13,7043,7044],{},"こんにちはじゅんです。会社で試験的に作成したLaravelアプリケーションをHerokuにデプロイする機会があり、いくらかハマりポイントなどがあったのでメモがてら残そうと思います。どんなLaravel アプリなのかなど、ある程度具体的な要件の定義から書くので、",[13,7046,7047,7048,7051],{},"「さっさとデプロイの説明しろや！」という人は目次から ",[3700,7049,7050],{},"「Herokuへのデプロイ作業を開始」"," をクリックして飛んでください。",[74,7053,7055],{"id":7054},"デプロイの前に作成したlaravel-アプリについて","デプロイの前に、作成したLaravel アプリについて",[13,7057,7058],{},"会社でRestfulなWebAPIとそのコンテンツを提供するサービスを開発しようという動きがあり、Laravelやvue、webAPIは少しわかる私に試験的な開発をまかされLaravelで作りました。",[13,7060,7061,7062,7065,7066,7069],{},"最近、巷で有名な ",[3700,7063,7064],{},"「Headless CMS」と似たような動きをします。"," 細かい要件は書くことはできないですが、 ",[3700,7067,7068],{},"LaravelでAPIサーバー兼コンテンツ管理画面を作成して置きます。"," お客さんはアプリケーションにログインしてコンテンツの編集などを行えます。そしてフロントエンドは別のサーバーに配置するか、既存のアプリを使用します。",[13,7071,7072,7073,7076],{},"Ajaxなどを通して認証付きAPIをLaravelが置いてあるサーバーに投げれば、 ",[3700,7074,7075],{},"データ（掲載コンテンツの内容）をJSON・XMLで手に入れることができます。"," 欲しい情報は適宜、非同期でAPIで呼び出して取得するという形式を取っています。モダンなやり方ですね。",[81,7078,7080],{"id":7079},"laravel-でapiサーバーを構築した理由","Laravel でAPIサーバーを構築した理由",[13,7082,7083],{},"APIサーバーを構築するフレームワークは他にもありますが以下のような理由により、Laravelを選びました。",[17,7085,7086,7089,7092,7095],{},[20,7087,7088],{},"私が扱いやすい。学習しやすい。",[20,7090,7091],{},"Laravel Passport でOAuth2.0 に準拠したAPI認証機能をさくっと構築可能",[20,7093,7094],{},"Laravel Mixのおかげで管理画面（お客さんが触るGUI）の作成がとても簡単。",[20,7096,7097],{},"LaravelにはAPIに関する認証や制限を設置できるので、",[13,7099,7100],{},"決められたお客さんのコンテンツ（サーバー）からのAPIアクセスを許可する。\nお試しユーザー、プレミアムユーザーと言ったユーザーロールごとに使用できるAPIの制限が簡単。\nと言ったことも可能です。「やっぱりLaravelはフルスタックフレームワークだけあるな」と思えるほどの物です。とにかく上記のようなLaravel アプリを今回はHerokuにデプロイします。",[13,7102,7103],{},"作成したLaravelアプリについての説明・構築は今回の記事ではしません。Herokuへのデプロイがメインになります。",[81,7105,7107],{"id":7106},"なぜheroku","なぜHeroku??",[13,7109,7110,7111],{},"今回のプロジェクト自体がまだ試験的であることも理由の一つですが、単にVPSとかで構築するのが面倒だったからです。 ",[3700,7112,7113],{},"各種ミドルウェア やソフトが最新版の物であっても、マニュアルがあってもセキュリティや権限の設定とかを考えて構築すると日が暮れます。",[13,7115,7116],{},"Herokuの理念が",[1224,7118,7120,7121],{"className":7119},[1227,5629],"\nHeroku はアプリの構築、提供、監視、スケールに役立つクラウドプラットフォームで、アイデアを出してから運用を開始するまでのプロセスを迅速に進めることが可能です。また、インフラストラクチャの管理の問題からも解放されます。\n",[5637,7122,7123],{},[7124,7125,7126],"i",{},[197,7127,7128],{"href":7128,"target":2917},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fja\u002Fdocs\u002FWeb\u002FCSS\u002Ffont-feature-settings",[13,7130,7131],{},"とあるようにインフラの構築や管理は面倒です。そのため予めインフラがすぐに作られ、しかしある程度カスタマイズが可能なサービスを利用すると開発者としてはアプリ（今回でいうLaravelアプリ）の開発に注力できます。",[13,7133,7134],{},"またVPSでも可能だと思いますが、GitHubとかと連携して本番環境・開発環境を構築できるのも魅力の一つです。まだ試験的な段階なので、AWSやVPSほどの自由度は今回のアプリではいらないかな？と思いHerokuにしました。",[74,7136,7138],{"id":7137},"herokuで構築した開発運用フロー","Herokuで構築した開発・運用フロー",[13,7140,7141],{},"下図のような感じです。バージョン管理などはGitHubを用いています。適宜、リモートリポジトリにpushしたらその変更が自動でHerokuのアプリ（個々のサーバーみたいなものだと思ってください。）に反映されます。",[183,7143],{":src":7144,":width":3010},"'_mix\u002Fheroku_archtecture-768x446.jpg'",[13,7146,7147,7148,7151],{},"プルリクエストを送ると、 ",[3700,7149,7150],{},"そのリクエストに応じたアプリが自動的に構築されるように設定もできます。"," Stagingで最終確認をしてOKであれば公開環境にデプロイさせます。",[13,7153,7154],{},"そのため今回の記事では以下のような流れで構築してます。",[6104,7156,7157,7160,7163,7166,7169,7172,7175,7178,7181,7184],{},[20,7158,7159],{},"Herokuのアカウント作成、アプリの作成",[20,7161,7162],{},"Herokuパイプラインの作成",[20,7164,7165],{},"Herokuアプリの設定",[20,7167,7168],{},"HerokuとGithubの連携",[20,7170,7171],{},"DBとの連携",[20,7173,7174],{},"環境変数の設定",[20,7176,7177],{},"Laravelアプリでのmigrationなど",[20,7179,7180],{},"本番環境との連携",[20,7182,7183],{},"SSL化する",[20,7185,7186],{},"API呼び出し確認",[74,7188,7190],{"id":7189},"herokuへのデプロイ作業を開始","Herokuへのデプロイ作業を開始",[81,7192,7194],{"id":7193},"herokuのアカウントを作成","Herokuのアカウントを作成",[13,7196,7197,7198,7201,7204],{},"当たり前ですがHerokuでのアカウントを作成します。これがないと何も始まりません。 ",[197,7199],{"href":7200,"target":2917},"https:\u002F\u002Fjp.heroku.com\u002F",[197,7202,7200],{"href":7200,"rel":7203},[201],"にアクセスして「新規登録」に進みます。",[183,7206],{":src":7207,":width":3010},"'_mix\u002Fheroku-1.png'",[13,7209,7210],{},"名前やアドレスなど適宜入力します。アカウントを作成する段階では日本語でも大丈夫です。選択するプランですがとりあえず無料の物にしておきましょう。",[13,7212,7213,7217],{},[197,7214,7215],{"href":7215,"rel":7216},"https:\u002F\u002Fjp.heroku.com\u002Fpricing",[201]," にてプランの内容や価格を知ることができます。完全フリーの場合はSSL(httpsにするやつ)の設定が行えない、最低限のスペックしかないなど本当に試験的にサーバー上にアップする程度に使用します。",[13,7219,7220],{},"商用で運用する場合は有料のStandardなどにアップグレードしましょう。ここでは開発サーバーをフリー版、本番環境をhobbyにしておきます。",[81,7222,7224],{"id":7223},"herokuアプリを作成する","Herokuアプリを作成する。",[13,7226,7227,7228,7231],{},"今回デプロイするLaravelのソース達を入れる、Herokuアプリを作成します。 ",[3700,7229,7230],{},"Herokuアプリというのは簡単にいうとサーバーそのものだと思ってください。"," Herokuアプリに、使用するソフトやドキュメントルートの設定、環境変数の設定、連携するGithubのレポジトリなどの設定を行うことで、Laravelが正常に動いてアプリケーションサーバー（サービスを提供するサーバー）として機能するようになります。",[13,7233,7234],{},"ここでは実務の開発体制をとるのでpipelineを構築します。",[88,7236,7238],{"id":7237},"pipe-linesを構築する","pipe linesを構築する",[13,7240,7241,7244],{},[3700,7242,7243],{},"pipe linesというのは「開発、レビュー、ステージング、本番」など複数の段階に分けてアプリを動かすことができるHerokuアプリのグループのことをいいます。"," もう少し簡単にいうと、",[13,7246,7247],{},"修正した物開発中のものをテストする「開発サーバー」、お客さんが触り実際にサービスが提供される「本番サーバー」を同時に構築して管理しやすくしたアプリ達です。今回は開発用・確認用、本番の２つの構成にします。",[13,7249,7250],{},"アカウントを作ると個人のダッシュボード画面に移動します。そこの「New」をクリックし、[Create New pipeline」を選択します。",[183,7252],{":src":7253,":width":3010},"'_mix\u002Fheroku_createnew-1024x306.png'",[13,7255,7256],{},"pipelineを作成すると、pipelineの名前と連携するリポジトリを選ぶことができます。ここではまだ連携しないので、名前だけ入力して「create pipeline」で作成。すると二つのアプリを加えることができるpipelineが作成されました。",[183,7258],{":src":7259,":width":3010},"'_mix\u002Fdashnoard-1024x262.png'",[13,7261,7262,7263,7266],{},"そしてたらアプリの構築をしていきます。二つ作るのは少し面倒ですが仕方ないです。では ",[3700,7264,7265],{},"開発の方（staging）"," から作成していきます。「Add app」をクリックすると、既存のアプリを追加するか、新しく作るかを聞いてきます。ここでは「create new app」を選択。すると右側にアプリの基本情報を入力する欄が出てきます。",[13,7268,7269],{},"そしたらアプリの名前とリージョンを選択して「create app」します。フリープランの場合はアメリカかヨーロッパリージョンしか選べませんが、特に問題ないです。（その地域に隕石が落るとかしてサーバーが物理的にやられない限り）",[13,7271,7272],{},"アプリ名を英語で入力して「create」をクリックして作成されます。名前はとりあえず「staging-app-laravel」としておきます。",[81,7274,7276],{"id":7275},"build-packを設定する","Build packを設定する",[13,7278,7279],{},"開発用アプリの設定が可能になったのでダッシュボードから、アプリ名をクリックして移動します。すると下のようなアプリに関する設定ができる画面に移ります。設定画面でできることは後で使用するheroku CLIでも行うことができます。今回はGUIの方をつかっていきます。",[183,7281],{":src":7282,":width":3010},"'_mix\u002Fapp_setting_dev-1024x497.png'",[13,7284,7285,7286,7289],{},"まずは ",[3700,7287,7288],{},"一番最初にbuild packというものを設定"," します。build packではnode.js,PHPなどを選択します。まあ使うサーバーサイドの言語を選ぶ物だと思ってください。上記の画面から「Settings」を選択します。",[13,7291,7292],{},"「Buildpacks」という項目があるので「Add builpack」でパックを追加します。",[183,7294],{":src":7295,":width":7296,":center":186},"'_mix\u002Fbuldpack-300x222.png'","'500px'",[13,7298,7299],{},"LaravelなのでPHPは必須です。そしてnode modulesもあるのでnode.jsも入れます。ここでサーバーサイドの言語を複数追加すれば各言語でアプリを動かすことができます。",[81,7301,7303],{"id":7302},"githubと連携してソースを入れる","Githubと連携してソースを入れる",[13,7305,7285,7306,7309],{},[3700,7307,7308],{},"Laravelアプリケーションのソースをこのアプリにぶち込む必要があります。"," herokuではGithubとの連携がオススメされているので、今回はその方法で行きます。上記画面から「Deploy」をクリックして移動します。",[183,7311],{":src":7312,":width":3010},"'_mix\u002Fdeply_set-1024x257.png'",[13,7314,7315],{},"deployment method でGithubを選びます。すると連携するリポジトリ名を入力する欄が出現します。もし初めてherokuを触ってgithubと連携していない場合は「githubと連携する？」みたいな物が表示されたはずなので、言われる通りに連携します。",[13,7317,7318],{},"githubのアカウントを持っていてログイン状態が保持されていれば、githubの連携許可画面が表示されます。それで簡単に連携ができます。",[13,7320,7321],{},"連携が完了すると下のようにデプロイに関する設定が出てきます。githubを用いる場合は２通りのデプロイがあります。",[17,7323,7324,7327],{},[20,7325,7326],{},"ブランチを指定して手動でデプロイ",[20,7328,7329],{},"ブランチに変化があったら自動でデプロイ",[13,7331,7332],{},[3700,7333,7334],{},"毎回herokuにログインするのも面倒なので「Enable Automatic Deploys」を選択して自動デプロイができるようにしておきます。",[183,7336],{":src":7337,":width":3010},"'_mix\u002Fautodeploy-1024x421.png'",[13,7339,7340],{},"リポジトリにソースをプッシュすると以下のようにbuildしているのがダッシュボードで見ることができます。（たまに更新ボタンを押さないと表示されない時がある。）",[183,7342],{":src":7343,":width":7296,":center":186},"'_mix\u002Fbuildings-300x166.png'",[13,7345,7346],{},"しかしまだDBとの連携や環境変数の設定が済んでいないので、アプリをみたところで500エラーしか表示されません。次はDBの設定に移ります。",[81,7348,7350],{"id":7349},"jawsdbを設定する","JawsDBを設定する",[13,7352,7353,7354,7357,7358,7361],{},"herokuでは",[39,7355,7356],{},"Procfile"," を用いてドキュメントルート やwebサーバー&phpの設定ができますが、DBに関してはアドオンを利用する必要があります。herokuではpostgreSQLの使用を進めていますが、私が作成したLaravelアプリはmysql仕様で作っていたので ",[3700,7359,7360],{},"今回はmysqlをDBとして使用します。","（まあpostgreSQLに変更もできましたけど…）",[81,7363,7365],{"id":7364},"使用するdbのアドオンについて","使用するDBのアドオンについて",[13,7367,7368],{},"ちなみに、「heroku mysql DB」と検索すると **「ClearDB」というmysqlベースのDBアドオンを入れるように書かれた記事が多いですが、私はそこでハマった物がありました。**以下に記述しておきます。",[88,7370,7372],{"id":7371},"laravel-55以上の場合cleardbの使用はおすすめしない理由","Laravel 5.5以上の場合、ClearDBの使用はおすすめしない理由",[13,7374,7375],{},"ClearDBを用いててデータベースへマイグレーションをおこなった際に。こんなエラーが発生。",[226,7377,7379],{"className":228,"code":7378,"language":230,"meta":231,"style":231},"php artisan migrate\n...\n...\nSQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes (SQL: alter table `users` add unique `users_email_unique`(`email`))\nSQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes\n",[39,7380,7381,7389,7394,7398,7500],{"__ignoreMap":231},[235,7382,7383,7385,7387],{"class":237,"line":238},[235,7384,1028],{"class":241},[235,7386,3188],{"class":245},[235,7388,3148],{"class":245},[235,7390,7391],{"class":237,"line":249},[235,7392,7393],{"class":252},"...\n",[235,7395,7396],{"class":237,"line":259},[235,7397,7393],{"class":252},[235,7399,7400,7403,7406,7409,7412,7415,7418,7421,7424,7427,7430,7433,7436,7439,7442,7444,7447,7450,7453,7456,7459,7462,7465,7468,7471,7474,7477,7480,7482,7485,7487,7489,7491,7494,7496,7498],{"class":237,"line":266},[235,7401,7402],{"class":241},"SQLSTATE[42000]:",[235,7404,7405],{"class":245}," Syntax",[235,7407,7408],{"class":245}," error",[235,7410,7411],{"class":245}," or",[235,7413,7414],{"class":245}," access",[235,7416,7417],{"class":245}," violation:",[235,7419,7420],{"class":2835}," 1071",[235,7422,7423],{"class":245}," Specified",[235,7425,7426],{"class":245}," key",[235,7428,7429],{"class":245}," was",[235,7431,7432],{"class":245}," too",[235,7434,7435],{"class":245}," long",[235,7437,7438],{"class":499},";",[235,7440,7441],{"class":241}," max",[235,7443,7426],{"class":245},[235,7445,7446],{"class":245}," length",[235,7448,7449],{"class":245}," is",[235,7451,7452],{"class":2835}," 767",[235,7454,7455],{"class":245}," bytes",[235,7457,7458],{"class":519}," (SQL: ",[235,7460,7461],{"class":245},"alter",[235,7463,7464],{"class":245}," table",[235,7466,7467],{"class":499}," `",[235,7469,7470],{"class":241},"users",[235,7472,7473],{"class":499},"`",[235,7475,7476],{"class":241}," add",[235,7478,7479],{"class":245}," unique",[235,7481,7467],{"class":499},[235,7483,7484],{"class":241},"users_email_unique",[235,7486,7473],{"class":499},[235,7488,1704],{"class":499},[235,7490,7473],{"class":499},[235,7492,7493],{"class":241},"email",[235,7495,7473],{"class":499},[235,7497,1714],{"class":499},[235,7499,1952],{"class":519},[235,7501,7502,7504,7506,7508,7510,7512,7514,7516,7518,7520,7522,7524,7526,7528,7530,7532,7534,7536,7538],{"class":237,"line":275},[235,7503,7402],{"class":241},[235,7505,7405],{"class":245},[235,7507,7408],{"class":245},[235,7509,7411],{"class":245},[235,7511,7414],{"class":245},[235,7513,7417],{"class":245},[235,7515,7420],{"class":2835},[235,7517,7423],{"class":245},[235,7519,7426],{"class":245},[235,7521,7429],{"class":245},[235,7523,7432],{"class":245},[235,7525,7435],{"class":245},[235,7527,7438],{"class":499},[235,7529,7441],{"class":241},[235,7531,7426],{"class":245},[235,7533,7446],{"class":245},[235,7535,7449],{"class":245},[235,7537,7452],{"class":2835},[235,7539,7540],{"class":245}," bytes\n",[13,7542,7543],{},"ローカルでは起きなかった謎のエラー。「上限は767bytesだけど、1071bytesのキーが設定されている」と怒っている。とりあえずmigrationが中途半端にUserテーブルだけ作って止まってしまったので、migrationをリフレッシュする。",[226,7545,7547],{"className":228,"code":7546,"language":230,"meta":231,"style":231},"php artisan migrate:fresh\n",[39,7548,7549],{"__ignoreMap":231},[235,7550,7551,7553,7555],{"class":237,"line":238},[235,7552,1028],{"class":241},[235,7554,3188],{"class":245},[235,7556,7557],{"class":245}," migrate:fresh\n",[13,7559,7560],{},"このエラーを調べたところどうやら使用した「ClearDB」のmysqlが5.5と古い上に、Laravelで設定していたmysqlで用いる文字コードとの関係で起きていた。以下のQiita記事が非常に参考になった。",[1224,7562,6573,7564],{"className":7563},[1227,2913],[197,7565,7567],{"href":7566,"target":2917},"https:\u002F\u002Fqiita.com\u002Fbeer_geek\u002Fitems\u002F6e4264db142745ea666f","Laravel5.4以上、MySQL5.7.7未満 でusersテーブルのマイグレーションを実行すると Syntax error が発生する",[13,7569,7570],{},"対処は２つ。",[17,7572,7573,7576],{},[20,7574,7575],{},"mysqlのバージョンを上げて5.7にする。",[20,7577,7578],{},"文字コードを上限を超えないものに変更する。",[13,7580,7581],{},"今回の場合はmysqlのバージョンを上げる方にした。しかしこちらのStackOver Flow にもあるようにClear DBで用いられているmysqlのバージョンを上げるのは無理らしい。。",[1224,7583,6573,7585],{"className":7584},[1227,2913],[197,7586,7588],{"href":7587,"target":2917},"https:\u002F\u002Fstackoverflow.com\u002Fquestions\u002F56253354\u002Fhow-to-update-the-version-of-the-mysql-engine-in-cleardb","How to update the version of the MySQL engine in ClearDB?\n    ",[13,7590,7591,7592,7596],{},"代わりに",[197,7593,7595],{"href":7594,"target":2917},"https:\u002F\u002Felements.heroku.com\u002Faddons\u002Fjawsdb","「JawsDB」","というのを使うとmysqlのバージョンが5.7のものが入ってくれる。",[13,7598,7599],{},"herokuでのmysql導入を調べると「ClearDBを入れろ」という記事が多く、言われたままにやったら起きたのでハマりポイントでした。",[81,7601,7603],{"id":7602},"jawsdbアドオンを入れる","jawsDBアドオンを入れる",[13,7605,7606,7607],{},"アドオンはherokuの追加機能のような物です。「Resources」を選ぶと「Add-ons」という項目がありそこでアドオンを導入できます。jawsDBと入力するとすぐに出てきます。 ",[3700,7608,7609],{},"「jawsDB MySQL」を選択して追加。",[183,7611],{":src":7612,":width":7296,":center":186},"'_mix\u002Fjawsdb-300x248.png'",[13,7614,7615],{},"データ容量などに応じたプランを選ぶことができます。ここではFreeでおk。「Provision」をクリックしてアプリに追加します。",[81,7617,7619],{"id":7618},"dbの接続情報を手に入れる","DBの接続情報を手に入れる",[13,7621,7622],{},"このjawsDBはherokuのアプリ内（というかサーバー）に組み込まれている訳ではないので、ホストやポートなどを指定する必要があります。jawsDBを導入するとそのherokuアプリを結びついたデータベースが作成されます。",[13,7624,7625],{},"その情報はアドオンのページで、jawsDBの箇所がリンクになっているのでそこをクリックします。",[183,7627],{":src":7628,":width":7296,":center":186},"'_mix\u002Faddons-768x194.png'",[13,7630,7631],{},[3700,7632,7633],{},"するとDBへの接続情報が書かれた画面が表示されます。ここに接続先ホスト、ユーザー名、パスワードなどが書いてあるので控えておきます。",[183,7635],{":src":7636,":width":3010},"'_mix\u002Fjawsdashboard-768x360.png'",[13,7638,7639],{},"自前のmysqlコマンドやクラアントから普通にアクセスすることができます。では次にこれらの情報を元に環境変数を設定します。後少しでアプリがデプロイされます！頑張ってください！",[81,7641,7643],{"id":7642},"config-bars環境変数を設定","config bars(環境変数)を設定",[13,7645,7646],{},"DBの接続情報を手に入れたのでLaravel内で使用される環境変数を定義していきます。Laravelのファイル群に「.env」というのがあったと思います。そのファイルは環境変数を定義しており、DBのパスワード、APIキーなど重要な値や環境によって変化する値が格納されています。",[13,7648,7649,7650],{},"基本的には.envはgitでは管理せず、githubにはプッシュされないように除外してあります。",[3700,7651,7652],{},"ローカルとherokuではDBの接続情報も違うので異なる環境変数を設定する必要がります。",[13,7654,7655,7656,7659],{},"そのために ",[3700,7657,7658],{},"「Settings」の「config bars」にて環境変数を設定","できます。Laravelの.envを開いて必要な項目をコピーして適宜適切な値を入れておきましょう。",[183,7661],{":src":7662,":width":3010},"'_mix\u002Fconfigure-768x115.png'",[81,7664,7665],{"id":7665},"マイグレーションを行う",[88,7667,7669],{"id":7668},"heroku-cliを入れる","heroku CLIを入れる",[13,7671,7672,7673,7677],{},"環境変数の設定を終えればLaravelのマイグレーションが使えるようになります。heroku CLIを用いてアプリをBashで操作します。ローカルに",[197,7674,7676],{"href":7675,"target":2917},"https:\u002F\u002Fdevcenter.heroku.com\u002Farticles\u002Fheroku-cli","heroku CLI","をダウンロードします。",[88,7679,7680],{"id":7680},"アプリをbashから操作する",[13,7682,7683],{},"macならターミナルを開いてログインをします。",[226,7685,7688],{"className":7686,"code":7687,"language":933},[931],"~ % heroku login\nheroku: Press any key to open up the browser to login or q to exit:[Enter]\nOpening browser to https:\u002F\u002Fcli-auth.heroku.com\u002Fauth\u002Fcli\u002Fbrowser\u002F********\nLogging in... done\nLogged in as your@email.com\n",[39,7689,7687],{"__ignoreMap":231},[13,7691,7692],{},"ブラウザがいったん開いてログインが完了です。そしたらアプリをbashで操作します。以下のコマンドを唱えます。",[226,7694,7697],{"className":7695,"code":7696,"language":933},[931],"~ % heroku run bash -a staging-app-laravel\nRunning bash on ⬢ staging-app-laravel... done\n",[39,7698,7696],{"__ignoreMap":231},[13,7700,7701,7704],{},[3700,7702,7703],{},"「-a」でアプリ名を指定できます。バッシュの時はアプリ名を指定しないと怒られます。"," すると表示が少し変わりバッシュ操作が可能になります。",[13,7706,7707,7708,7710],{},"ホームディレクトリ 内にLaravelのソース達が配置されているのが確認できます。このLaravelアプリのルートで ",[39,7709,3103],{},"を唱えることができます。それ以外のディレクトリなどで行うと「そんなコマンド知らんよ」と怒られます。ではマイグレーション。",[226,7712,7715],{"className":7713,"code":7714,"language":933},[931],"~ $ php artisan migrate\n**************************************\n*     Application In Production!     *\n**************************************\n\n Do you really wish to run this command? (yes\u002Fno) [no]:\n > yes\n\nMigration table created successfully.\nMigrating: 2014_10_12_000000_create_users_table\nMigrated:  2014_10_12_000000_create_users_table (0.06 seconds)\nMigrating: 2014_10_12_100000_create_password_resets_table\nMigrated:  2014_10_12_100000_create_password_resets_table (0.05 seconds)\nMigrating: 2016_06_01_000001_create_oauth_auth_codes_table\nMigrated:  2016_06_01_000001_create_oauth_auth_codes_table (0.09 seconds)\nMigrating: 2016_06_01_000002_create_oauth_access_tokens_table\nMigrated:  2016_06_01_000002_create_oauth_access_tokens_table (0.09 seconds)\nMigrating: 2016_06_01_000003_create_oauth_refresh_tokens_table\nMigrated:  2016_06_01_000003_create_oauth_refresh_tokens_table (0.06 seconds)\nMigrating: 2016_06_01_000004_create_oauth_clients_table\nMigrated:  2016_06_01_000004_create_oauth_clients_table (0.05 seconds)\nMigrating: 2016_06_01_000005_create_oauth_personal_access_clients_table\nMigrated:  2016_06_01_000005_create_oauth_personal_access_clients_table (0.02 seconds)\nMigrating: 2019_08_19_000000_create_failed_jobs_table\nMigrated:  2019_08_19_000000_create_failed_jobs_table (0.03 seconds)\n...#自分で設定した他のテーブルも無事マイグレーションされた。\n",[39,7716,7714],{"__ignoreMap":231},[13,7718,7719],{},"これでDBに必要なテーブルが設定されました。ユーザーテーブルに私たちが管理するための管理ユーザーを作成するシーダーを予め作成したおいたのでそれも実行。",[226,7721,7724],{"className":7722,"code":7723,"language":933},[931],"~ $ php artisan db:seed\n",[39,7725,7723],{"__ignoreMap":231},[13,7727,7728],{},"これでDBにもテーブルが挿入されたのでアプリが動くようになりました。",[81,7730,7732],{"id":7731},"herokuアプリをブラウザで開く","Herokuアプリをブラウザで開く",[13,7734,7735,7736],{},"ブラウザのHerokuの画面に戻り、pipe lineの一覧を開きます。 ",[3700,7737,7738],{},"開発アプリの「Open app」をクリックすると構築したLaravelアプリの画面が表示されました。",[13,7740,7741],{},"「500 Internal ServerError」などが表示されている場合は以下の４つを確かめましょう。",[6104,7743,7744,7747,7750,7753,7756],{},[20,7745,7746],{},"$ heroku logs --tail -a your_app_name でログを確かめる。",[20,7748,7749],{},"Laravelアプリそのものにミスはないか？",[20,7751,7752],{},"環境変数の設定はあっているか？",[20,7754,7755],{},"DBに接続されているか？DBのテーブルの設定は正しいか？",[20,7757,7758],{},"ビルドのログを確かめてビルドが失敗していないか？",[13,7760,7761],{},"アプリのビルドが成功している場合はサーバー自体は構築できています。Laravelアプリや環境変数などに問題ある可能性が高いので確認してみましょう。",[81,7763,7765],{"id":7764},"次は本番のアプリ","次は本番のアプリ！！",[13,7767,7768],{},"ここまでもかなりボリューミーですが、今回は２つアプリを入れたので２つ目も設定します。ですがやることは開発の方でやったように",[17,7770,7771,7774,7777,7780],{},[20,7772,7773],{},"ビルドパックを入れる",[20,7775,7776],{},"アドオンを入れる",[20,7778,7779],{},"環境変数を設定する",[20,7781,7782],{},"DBと接続してマイグレーションする",[13,7784,7785],{},"ぐらいです。そして私の運用ではgithubと本番のアプリは連携させず、stagingのアプリを本番に移行するので少し手間が省けます。stagingのものを本番に移行する場合はpipelineの画面で「promote to production」を選択すればすぐに反映されます。",[81,7787,7789],{"id":7788},"本番サーバーのssl化","本番サーバーのSSL化",[13,7791,7792],{},"最後に本番サーバーのSSL化です。無料プランのは自動でSSLは付与されません。証明書があれば付与することができますが無料のdynoはスペックが最低限な上、証明書取得でもお金がかかるので大人しくhobbyプラン以上の有料版にアップグレードしましょう。",[13,7794,7795],{},"本番アプリをhobbyプランに上げるとACM（自動証明書管理）が有効になり、証明書が発行できて有効になってます。SSL化はこれだけです。込み入った証明書や独自ドメイン の付与の際には少し設定が必要です。",[81,7797,7799],{"id":7798},"apiサーバーとして機能しているかを確かめる","APIサーバーとして機能しているかを確かめる",[13,7801,7802],{},"とりあえず本番環境までのデプロイが完了しました。Laravel Passport を用いて認証付きAPIを投げられるのでそのチェックをしてみます。とその前にlaravel passportの設定上必要なものがあるのでアプリ内にbashで入って以下を実行。",[226,7804,7807],{"className":7805,"code":7806,"language":933},[931],"~ $ php artisan passport:install\n",[39,7808,7806],{"__ignoreMap":231},[13,7810,7811],{},"Laravel Passport を用いて認証付きAPIを投げられるのでそのチェックをしてみます。シーダーで作成した管理ユーザーでログインします。Laravel Passport ではユーザーごとのアクセストークンが発行できるので、それを作成。（トークン取得画面のUIなどは適宜作成してください。Laravel側でも用意されています。）",[183,7813],{":src":7814,":width":7296,":center":186},"'_mix\u002Fapis-768x841.png'",[13,7816,7817],{},"アクセストークンを発行して手元にメモしておきます。「Talend API Tester」というChrome拡張機能を使ってこのHerokuアプリ宛にAPIを投げてみます。",[13,7819,7820],{},"アプリのドメイン名が表示されているのでコピー。外部からのAPIアクセスの際にはAPIキーをリクエストヘッダーに仕込んで送ると受け取る設定になっています。まずはAPIキーなして投げると、",[183,7822],{":src":7823,":width":3010},"'_mix\u002Fapi_test_01-1-768x330.png'",[13,7825,7826,7827,7830],{},"おっ！401 Unauthorized が戻ってきました。 ",[3700,7828,7829],{},"とりあえず指定のAPIルーティングは存在して、リクエストが受け取れています。つまりAPIサーバーとして機能していることがわかります。"," ではヘッダに先ほどのキーを仕込んで、また諸所の設定値も入れて再度投げると、",[183,7832],{":src":7833,":width":3010},"'_mix\u002Fapitest_02-768x438.png'",[13,7835,7836],{},"200 OK が返りました。プレビューを見るとJSONでのデータが戻ってきているので成功です。これでHerokuにLaravelで開発したAPIサーバー及び管理アプリを構築することができました。このサーバーに認証キー付きAPIを投げることでデータを取得できます。",[81,7838,7840],{"id":7839},"終了","終了〜〜〜",[13,7842,7843],{},"以上がherokuにlaravel で作成したAPIサービスを構築する手順でした。めちゃくちゃ長い記事になりましたが、VPSならもっと長くなっています笑。githubやSSL化そして他の管理を気にせず簡単にデプロイできるのでherokuが人気なのもわかります。Xserverとかは異なってスケーラブルでnodeとかpythonとかも入れられ、githubで管理できるのは大きいです。",[2398,7845,7846],{},"html pre.shiki code .s5Dmg, html code.shiki .s5Dmg{--shiki-default:#FFCB6B}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html pre.shiki code .sx098, html code.shiki .sx098{--shiki-default:#F78C6C}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":231,"searchDepth":259,"depth":259,"links":7848},[7849,7853,7854],{"id":7054,"depth":249,"text":7055,"children":7850},[7851,7852],{"id":7079,"depth":259,"text":7080},{"id":7106,"depth":259,"text":7107},{"id":7137,"depth":249,"text":7138},{"id":7189,"depth":249,"text":7190,"children":7855},[7856,7857,7860,7861,7862,7863,7866,7867,7868,7869,7873,7874,7875,7876,7877],{"id":7193,"depth":259,"text":7194},{"id":7223,"depth":259,"text":7224,"children":7858},[7859],{"id":7237,"depth":266,"text":7238},{"id":7275,"depth":259,"text":7276},{"id":7302,"depth":259,"text":7303},{"id":7349,"depth":259,"text":7350},{"id":7364,"depth":259,"text":7365,"children":7864},[7865],{"id":7371,"depth":266,"text":7372},{"id":7602,"depth":259,"text":7603},{"id":7618,"depth":259,"text":7619},{"id":7642,"depth":259,"text":7643},{"id":7665,"depth":259,"text":7665,"children":7870},[7871,7872],{"id":7668,"depth":266,"text":7669},{"id":7680,"depth":266,"text":7680},{"id":7731,"depth":259,"text":7732},{"id":7764,"depth":259,"text":7765},{"id":7788,"depth":259,"text":7789},{"id":7798,"depth":259,"text":7799},{"id":7839,"depth":259,"text":7840},[2426],"2020-07-07",{},"\u002Farticles\u002Flalavel7-deplay-on-heroku",{"title":7039,"description":7039},"articles\u002Flalavel7-deplay-on-heroku",[1028,970,6056],"_mix\u002Fheroku_archtecture-768x446.jpg","i3Z8K7cn-JUTupKxFl5lgfnHLhl9EAJPK97f9yd7CX0",1780987146878]