[{"data":1,"prerenderedAt":12208},["ShallowReactive",2],{"tag-laravel":3},{"count":4,"content":5},16,[6,1893,5603,8271,8684,8835,10455,10931,11403,11898],{"id":7,"title":8,"body":9,"category":1880,"createdAt":1882,"description":8,"extension":1883,"index":1884,"meta":1885,"navigation":407,"path":1886,"publish":407,"seo":1887,"series":1884,"seriesTitle":1884,"stem":1888,"tag":1889,"thumbnail":1891,"updatedAt":1882,"__hash__":1892},"articles\u002Farticles\u002Fbuild-lamp-with-docker.md","Dockerでosから作るcentos8+apache2.4+laravel 6 開発環境構築",{"type":10,"value":11,"toc":1854},"minimark",[12,16,19,23,34,37,332,335,348,351,355,365,379,382,385,388,391,642,645,649,718,732,740,758,761,765,771,789,807,814,817,823,829,833,866,884,892,895,928,931,935,938,955,958,961,967,981,987,990,996,1217,1220,1223,1226,1325,1336,1340,1343,1381,1397,1400,1434,1452,1456,1563,1572,1575,1578,1585,1591,1601,1607,1619,1623,1637,1643,1650,1653,1657,1667,1673,1680,1686,1691,1696,1699,1705,1712,1718,1721,1725,1728,1734,1737,1743,1754,1760,1769,1775,1785,1795,1801,1809,1841,1844,1847,1850],[13,14,15],"p",{},"こんにちはjunです。laravelの開発環境をDockerで構築した機会がありました。よくlaravelぐらいならば apcheイメージを入れたりして構築すると思います。その場合大体イメージのosがUbuntuになったりしますが、本番環境のosはcentosだったりと「osから構築したいなー」と思ったので今回はDockerfileとdocker-composeを用いてosからlaravelまで構築しようと思います。",[13,17,18],{},"この記事で使用したOSとdockerのバージョン\ndocker：19.03.13\nmaxOS Catalina 10.15.5",[20,21,22],"h2",{"id":22},"構成の全体像",[24,25,30],"pre",{"className":26,"code":28,"language":29},[27],"language-text","centos_laravel\n├── docker-compose.yml\n├── laravel\n│\n├── web\n    ├── Dockerfile\n    ├── httpd.conf\n    └── php.ini\n","text",[31,32,28],"code",{"__ignoreMap":33},"",[13,35,36],{},"ディレクトリは上記の様な感じです。docker-compose.ymlは以下の様になっています。",[24,38,43],{"className":39,"code":40,"filename":41,"language":42,"meta":33,"style":33},"language-yml shiki shiki-themes material-theme-ocean","version: '3'\nservices: \n  web_1:\n    image: centos8_apache:1.0\n    depends_on: \n      - db\n    volumes: \n      - .\u002Flaravel\u002F:\u002Fvar\u002Fwww\u002Fhtml\u002F\n      - \u002Fsys\u002Ffs\u002Fcgroup:\u002Fsys\u002Ffs\u002Fcgroup:ro\n    ports: \n      - \"9000:80\"\n      - \"3000:3000\"\n    privileged: true\n    command: \u002Fsbin\u002Finit\n  db:\n    image: mysql:5.7\n    environment:\n      MYSQL_DATABASE: larvel_docker\n      MYSQL_USER: test\n      MYSQL_PASSWORD: testtest\n      MYSQL_ROOT_PASSWORD: rootroot\n    ports: \n      - \"3306:3306\"\n    volumes: \n      - laravel_data:\u002Fvar\u002Flib\u002Fmysql\nvolumes: \n  laravel_data: {}\n","docker-compose.yml","yml",[31,44,45,68,80,89,100,110,119,129,137,145,155,169,181,193,204,212,221,229,240,251,262,273,282,294,303,311,321],{"__ignoreMap":33},[46,47,50,54,58,61,65],"span",{"class":48,"line":49},"line",1,[46,51,53],{"class":52},"s-wAU","version",[46,55,57],{"class":56},"sAklC",":",[46,59,60],{"class":56}," '",[46,62,64],{"class":63},"sfyAc","3",[46,66,67],{"class":56},"'\n",[46,69,71,74,76],{"class":48,"line":70},2,[46,72,73],{"class":52},"services",[46,75,57],{"class":56},[46,77,79],{"class":78},"s0W1g"," \n",[46,81,83,86],{"class":48,"line":82},3,[46,84,85],{"class":52},"  web_1",[46,87,88],{"class":56},":\n",[46,90,92,95,97],{"class":48,"line":91},4,[46,93,94],{"class":52},"    image",[46,96,57],{"class":56},[46,98,99],{"class":63}," centos8_apache:1.0\n",[46,101,103,106,108],{"class":48,"line":102},5,[46,104,105],{"class":52},"    depends_on",[46,107,57],{"class":56},[46,109,79],{"class":78},[46,111,113,116],{"class":48,"line":112},6,[46,114,115],{"class":56},"      -",[46,117,118],{"class":63}," db\n",[46,120,122,125,127],{"class":48,"line":121},7,[46,123,124],{"class":52},"    volumes",[46,126,57],{"class":56},[46,128,79],{"class":78},[46,130,132,134],{"class":48,"line":131},8,[46,133,115],{"class":56},[46,135,136],{"class":63}," .\u002Flaravel\u002F:\u002Fvar\u002Fwww\u002Fhtml\u002F\n",[46,138,140,142],{"class":48,"line":139},9,[46,141,115],{"class":56},[46,143,144],{"class":63}," \u002Fsys\u002Ffs\u002Fcgroup:\u002Fsys\u002Ffs\u002Fcgroup:ro\n",[46,146,148,151,153],{"class":48,"line":147},10,[46,149,150],{"class":52},"    ports",[46,152,57],{"class":56},[46,154,79],{"class":78},[46,156,158,160,163,166],{"class":48,"line":157},11,[46,159,115],{"class":56},[46,161,162],{"class":56}," \"",[46,164,165],{"class":63},"9000:80",[46,167,168],{"class":56},"\"\n",[46,170,172,174,176,179],{"class":48,"line":171},12,[46,173,115],{"class":56},[46,175,162],{"class":56},[46,177,178],{"class":63},"3000:3000",[46,180,168],{"class":56},[46,182,184,187,189],{"class":48,"line":183},13,[46,185,186],{"class":52},"    privileged",[46,188,57],{"class":56},[46,190,192],{"class":191},"sbqyR"," true\n",[46,194,196,199,201],{"class":48,"line":195},14,[46,197,198],{"class":52},"    command",[46,200,57],{"class":56},[46,202,203],{"class":63}," \u002Fsbin\u002Finit\n",[46,205,207,210],{"class":48,"line":206},15,[46,208,209],{"class":52},"  db",[46,211,88],{"class":56},[46,213,214,216,218],{"class":48,"line":4},[46,215,94],{"class":52},[46,217,57],{"class":56},[46,219,220],{"class":63}," mysql:5.7\n",[46,222,224,227],{"class":48,"line":223},17,[46,225,226],{"class":52},"    environment",[46,228,88],{"class":56},[46,230,232,235,237],{"class":48,"line":231},18,[46,233,234],{"class":52},"      MYSQL_DATABASE",[46,236,57],{"class":56},[46,238,239],{"class":63}," larvel_docker\n",[46,241,243,246,248],{"class":48,"line":242},19,[46,244,245],{"class":52},"      MYSQL_USER",[46,247,57],{"class":56},[46,249,250],{"class":63}," test\n",[46,252,254,257,259],{"class":48,"line":253},20,[46,255,256],{"class":52},"      MYSQL_PASSWORD",[46,258,57],{"class":56},[46,260,261],{"class":63}," testtest\n",[46,263,265,268,270],{"class":48,"line":264},21,[46,266,267],{"class":52},"      MYSQL_ROOT_PASSWORD",[46,269,57],{"class":56},[46,271,272],{"class":63}," rootroot\n",[46,274,276,278,280],{"class":48,"line":275},22,[46,277,150],{"class":52},[46,279,57],{"class":56},[46,281,79],{"class":78},[46,283,285,287,289,292],{"class":48,"line":284},23,[46,286,115],{"class":56},[46,288,162],{"class":56},[46,290,291],{"class":63},"3306:3306",[46,293,168],{"class":56},[46,295,297,299,301],{"class":48,"line":296},24,[46,298,124],{"class":52},[46,300,57],{"class":56},[46,302,79],{"class":78},[46,304,306,308],{"class":48,"line":305},25,[46,307,115],{"class":56},[46,309,310],{"class":63}," laravel_data:\u002Fvar\u002Flib\u002Fmysql\n",[46,312,314,317,319],{"class":48,"line":313},26,[46,315,316],{"class":52},"volumes",[46,318,57],{"class":56},[46,320,79],{"class":78},[46,322,324,327,329],{"class":48,"line":323},27,[46,325,326],{"class":52},"  laravel_data",[46,328,57],{"class":56},[46,330,331],{"class":56}," {}\n",[13,333,334],{},"構築の流れとしては",[336,337,338,342,345],"ul",{},[339,340,341],"li",{},"centos8、apache2.4、php7.4、nodejsが入ったwebサーバーイメージを作成",[339,343,344],{},"webサーバーイメージとmysqlイメージで作られた2つのコンテナをdocker-composeで連携する",[339,346,347],{},"laraevelのマイグレーションを行う。",[13,349,350],{},"と行った流れで行います。",[352,353,354],"h3",{"id":354},"webサーバーイメージ",[13,356,357,360,361,364],{},[31,358,359],{},"web","ディレクトリ配下にはcentos8、apache、php、nodeがインストールされたイメージを作成する",[31,362,363],{},"Dockerfile","とapacheの設定ファイル、phpの設定ファイルがあります。",[13,366,367,368,371,372,374,375,378],{},"laravelでは実運用の際にドキュメントルート を変更する必要があるので",[31,369,370],{},"httpd.conf","を編集して、それをコンテナの",[31,373,370],{},"にコピーしています。",[31,376,377],{},"php.ini","はタイムゾーン を書き足すぐらいですけど同じ様にエディタ上で設定ファイルを変更できる様にしています。",[352,380,381],{"id":381},"laravelディレクトリ",[13,383,384],{},"larvelディレクトリはlaravelソースが予めインストールされています。そのソースをコンテナのドキュメントルート 配下にボリュームすることでローカルのエディタで編集してコンテナ環境でレビューすることができます。",[20,386,387],{"id":387},"webサーバーイメージを作成",[13,389,390],{},"それではまずwebサーバーイメージを作成していきます。と言っても以下のDockerfileの内容で事足ります。",[24,392,396],{"className":393,"code":394,"language":395,"meta":33,"style":33},"language-dockerfile shiki shiki-themes material-theme-ocean","FROM centos:8\n\nENV container docker\nRUN (cd \u002Flib\u002Fsystemd\u002Fsystem\u002Fsysinit.target.wants\u002F; for i in *; do [ $i == \\\nsystemd-tmpfiles-setup.service ] || rm -f $i; done); \\\nrm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Fmulti-user.target.wants\u002F*;\\\nrm -f \u002Fetc\u002Fsystemd\u002Fsystem\u002F*.wants\u002F*;\\\nrm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Flocal-fs.target.wants\u002F*; \\\nrm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Fsockets.target.wants\u002F*udev*; \\\nrm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Fsockets.target.wants\u002F*initctl*; \\\nrm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Fbasic.target.wants\u002F*;\\\nrm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Fanaconda.target.wants\u002F*;\nVOLUME [ \"\u002Fsys\u002Ffs\u002Fcgroup\" ]\nCMD [\"\u002Fusr\u002Fsbin\u002Finit\"]\n\nRUN \u002Fbin\u002Fcp \u002Fusr\u002Fshare\u002Fzoneinfo\u002FAsia\u002FTokyo \u002Fetc\u002Flocaltime\n\nRUN yum install -y epel-release && yum clean all\n\nRUN rpm -ivh http:\u002F\u002Fftp.riken.jp\u002FLinux\u002Fremi\u002Fenterprise\u002Fremi-release-8.rpm\n\nRUN yum -y update && yum clean all\n\nRUN yum -y install httpd && yum clean all\n\nCOPY .\u002Fhttpd.conf \u002Fetc\u002Fhttpd\u002Fconf\u002Fhttpd.conf\n\nRUN yum -y install php74-php php74-php-mysqli php74-php-gd php74-php-mbstring php74-php-opcache php74-php-xml php74-php-pear php74-php-devel php74-php-pecl-imagick php74-php-pecl-imagick-devel php74-php-pecl-zip\n\nRUN ln \u002Fusr\u002Fbin\u002Fphp74 \u002Fusr\u002Fbin\u002Fphp\n\nCOPY .\u002Fphp.ini \u002Fetc\u002Fopt\u002Fremi\u002Fphp74\u002Fphp.ini\n\nRUN chown -R apache:apache \u002Fvar\u002Fwww\u002Fhtml\n\nRUN systemctl enable php74-php-fpm\n\nRUN systemctl enable httpd \n\nVOLUME [ \"\u002Fvar\u002Fwww\u002Fhtml\" ]\n\nRUN chown -R apache:apache \u002Fvar\u002Fwww\u002Fhtml\n\nRUN dnf -y module enable nodejs:12\n\nRUN dnf -y install nodejs\n\nEXPOSE 80\n","dockerfile",[31,397,398,403,409,414,419,424,429,434,439,444,449,454,459,464,469,473,478,482,487,491,496,500,505,509,514,518,523,527,533,538,544,549,555,560,566,571,577,582,588,593,599,604,609,614,620,625,631,636],{"__ignoreMap":33},[46,399,400],{"class":48,"line":49},[46,401,402],{},"FROM centos:8\n",[46,404,405],{"class":48,"line":70},[46,406,408],{"emptyLinePlaceholder":407},true,"\n",[46,410,411],{"class":48,"line":82},[46,412,413],{},"ENV container docker\n",[46,415,416],{"class":48,"line":91},[46,417,418],{},"RUN (cd \u002Flib\u002Fsystemd\u002Fsystem\u002Fsysinit.target.wants\u002F; for i in *; do [ $i == \\\n",[46,420,421],{"class":48,"line":102},[46,422,423],{},"systemd-tmpfiles-setup.service ] || rm -f $i; done); \\\n",[46,425,426],{"class":48,"line":112},[46,427,428],{},"rm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Fmulti-user.target.wants\u002F*;\\\n",[46,430,431],{"class":48,"line":121},[46,432,433],{},"rm -f \u002Fetc\u002Fsystemd\u002Fsystem\u002F*.wants\u002F*;\\\n",[46,435,436],{"class":48,"line":131},[46,437,438],{},"rm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Flocal-fs.target.wants\u002F*; \\\n",[46,440,441],{"class":48,"line":139},[46,442,443],{},"rm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Fsockets.target.wants\u002F*udev*; \\\n",[46,445,446],{"class":48,"line":147},[46,447,448],{},"rm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Fsockets.target.wants\u002F*initctl*; \\\n",[46,450,451],{"class":48,"line":157},[46,452,453],{},"rm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Fbasic.target.wants\u002F*;\\\n",[46,455,456],{"class":48,"line":171},[46,457,458],{},"rm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Fanaconda.target.wants\u002F*;\n",[46,460,461],{"class":48,"line":183},[46,462,463],{},"VOLUME [ \"\u002Fsys\u002Ffs\u002Fcgroup\" ]\n",[46,465,466],{"class":48,"line":195},[46,467,468],{},"CMD [\"\u002Fusr\u002Fsbin\u002Finit\"]\n",[46,470,471],{"class":48,"line":206},[46,472,408],{"emptyLinePlaceholder":407},[46,474,475],{"class":48,"line":4},[46,476,477],{},"RUN \u002Fbin\u002Fcp \u002Fusr\u002Fshare\u002Fzoneinfo\u002FAsia\u002FTokyo \u002Fetc\u002Flocaltime\n",[46,479,480],{"class":48,"line":223},[46,481,408],{"emptyLinePlaceholder":407},[46,483,484],{"class":48,"line":231},[46,485,486],{},"RUN yum install -y epel-release && yum clean all\n",[46,488,489],{"class":48,"line":242},[46,490,408],{"emptyLinePlaceholder":407},[46,492,493],{"class":48,"line":253},[46,494,495],{},"RUN rpm -ivh http:\u002F\u002Fftp.riken.jp\u002FLinux\u002Fremi\u002Fenterprise\u002Fremi-release-8.rpm\n",[46,497,498],{"class":48,"line":264},[46,499,408],{"emptyLinePlaceholder":407},[46,501,502],{"class":48,"line":275},[46,503,504],{},"RUN yum -y update && yum clean all\n",[46,506,507],{"class":48,"line":284},[46,508,408],{"emptyLinePlaceholder":407},[46,510,511],{"class":48,"line":296},[46,512,513],{},"RUN yum -y install httpd && yum clean all\n",[46,515,516],{"class":48,"line":305},[46,517,408],{"emptyLinePlaceholder":407},[46,519,520],{"class":48,"line":313},[46,521,522],{},"COPY .\u002Fhttpd.conf \u002Fetc\u002Fhttpd\u002Fconf\u002Fhttpd.conf\n",[46,524,525],{"class":48,"line":323},[46,526,408],{"emptyLinePlaceholder":407},[46,528,530],{"class":48,"line":529},28,[46,531,532],{},"RUN yum -y install php74-php php74-php-mysqli php74-php-gd php74-php-mbstring php74-php-opcache php74-php-xml php74-php-pear php74-php-devel php74-php-pecl-imagick php74-php-pecl-imagick-devel php74-php-pecl-zip\n",[46,534,536],{"class":48,"line":535},29,[46,537,408],{"emptyLinePlaceholder":407},[46,539,541],{"class":48,"line":540},30,[46,542,543],{},"RUN ln \u002Fusr\u002Fbin\u002Fphp74 \u002Fusr\u002Fbin\u002Fphp\n",[46,545,547],{"class":48,"line":546},31,[46,548,408],{"emptyLinePlaceholder":407},[46,550,552],{"class":48,"line":551},32,[46,553,554],{},"COPY .\u002Fphp.ini \u002Fetc\u002Fopt\u002Fremi\u002Fphp74\u002Fphp.ini\n",[46,556,558],{"class":48,"line":557},33,[46,559,408],{"emptyLinePlaceholder":407},[46,561,563],{"class":48,"line":562},34,[46,564,565],{},"RUN chown -R apache:apache \u002Fvar\u002Fwww\u002Fhtml\n",[46,567,569],{"class":48,"line":568},35,[46,570,408],{"emptyLinePlaceholder":407},[46,572,574],{"class":48,"line":573},36,[46,575,576],{},"RUN systemctl enable php74-php-fpm\n",[46,578,580],{"class":48,"line":579},37,[46,581,408],{"emptyLinePlaceholder":407},[46,583,585],{"class":48,"line":584},38,[46,586,587],{},"RUN systemctl enable httpd \n",[46,589,591],{"class":48,"line":590},39,[46,592,408],{"emptyLinePlaceholder":407},[46,594,596],{"class":48,"line":595},40,[46,597,598],{},"VOLUME [ \"\u002Fvar\u002Fwww\u002Fhtml\" ]\n",[46,600,602],{"class":48,"line":601},41,[46,603,408],{"emptyLinePlaceholder":407},[46,605,607],{"class":48,"line":606},42,[46,608,565],{},[46,610,612],{"class":48,"line":611},43,[46,613,408],{"emptyLinePlaceholder":407},[46,615,617],{"class":48,"line":616},44,[46,618,619],{},"RUN dnf -y module enable nodejs:12\n",[46,621,623],{"class":48,"line":622},45,[46,624,408],{"emptyLinePlaceholder":407},[46,626,628],{"class":48,"line":627},46,[46,629,630],{},"RUN dnf -y install nodejs\n",[46,632,634],{"class":48,"line":633},47,[46,635,408],{"emptyLinePlaceholder":407},[46,637,639],{"class":48,"line":638},48,[46,640,641],{},"EXPOSE 80\n",[13,643,644],{},"もう少し細かく解説していきます。",[352,646,648],{"id":647},"centos8をいれ設定","centos8をいれ、設定",[24,650,652],{"className":393,"code":651,"language":395,"meta":33,"style":33},"FROM centos:8\n\nENV container docker\nRUN (cd \u002Flib\u002Fsystemd\u002Fsystem\u002Fsysinit.target.wants\u002F; for i in *; do [ $i == \\\nsystemd-tmpfiles-setup.service ] || rm -f $i; done); \\\nrm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Fmulti-user.target.wants\u002F*;\\\nrm -f \u002Fetc\u002Fsystemd\u002Fsystem\u002F*.wants\u002F*;\\\nrm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Flocal-fs.target.wants\u002F*; \\\nrm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Fsockets.target.wants\u002F*udev*; \\\nrm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Fsockets.target.wants\u002F*initctl*; \\\nrm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Fbasic.target.wants\u002F*;\\\nrm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Fanaconda.target.wants\u002F*;\nVOLUME [ \"\u002Fsys\u002Ffs\u002Fcgroup\" ]\nCMD [\"\u002Fusr\u002Fsbin\u002Finit\"]\n\nRUN \u002Fbin\u002Fcp \u002Fusr\u002Fshare\u002Fzoneinfo\u002FAsia\u002FTokyo \u002Fetc\u002Flocaltime\n",[31,653,654,658,662,666,670,674,678,682,686,690,694,698,702,706,710,714],{"__ignoreMap":33},[46,655,656],{"class":48,"line":49},[46,657,402],{},[46,659,660],{"class":48,"line":70},[46,661,408],{"emptyLinePlaceholder":407},[46,663,664],{"class":48,"line":82},[46,665,413],{},[46,667,668],{"class":48,"line":91},[46,669,418],{},[46,671,672],{"class":48,"line":102},[46,673,423],{},[46,675,676],{"class":48,"line":112},[46,677,428],{},[46,679,680],{"class":48,"line":121},[46,681,433],{},[46,683,684],{"class":48,"line":131},[46,685,438],{},[46,687,688],{"class":48,"line":139},[46,689,443],{},[46,691,692],{"class":48,"line":147},[46,693,448],{},[46,695,696],{"class":48,"line":157},[46,697,453],{},[46,699,700],{"class":48,"line":171},[46,701,458],{},[46,703,704],{"class":48,"line":183},[46,705,463],{},[46,707,708],{"class":48,"line":195},[46,709,468],{},[46,711,712],{"class":48,"line":206},[46,713,408],{"emptyLinePlaceholder":407},[46,715,716],{"class":48,"line":4},[46,717,477],{},[13,719,720,723,724,731],{},[31,721,722],{},"FROM centos:8","で書かれている様に、centos8自身のイメージは公式の",[725,726,730],"a",{"href":727,"rel":728},"https:\u002F\u002Fhub.docker.com\u002F_\u002Fcentos",[729],"nofollow","dockerHub","からpullします。そしてこれらの記述は公式の手順のソースを貼り付けた感じです。",[13,733,734,735,739],{},"以前dockerにcentos8の環境を作ろうとして詰まった時がありました（",[725,736,738],{"href":737},"\u002Farticles\u002Fstuck-on-docker-centos8","こちら","の記事）。その際はこの以下の部分だけを書いていました。",[24,741,743],{"className":393,"code":742,"language":395,"meta":33,"style":33},"FROM centos:8\nRUN \u002Fbin\u002Fcp \u002Fusr\u002Fshare\u002Fzoneinfo\u002FAsia\u002FTokyo \u002Fetc\u002Flocaltime\n#..以下同じ\n",[31,744,745,749,753],{"__ignoreMap":33},[46,746,747],{"class":48,"line":49},[46,748,402],{},[46,750,751],{"class":48,"line":70},[46,752,477],{},[46,754,755],{"class":48,"line":82},[46,756,757],{},"#..以下同じ\n",[13,759,760],{},"純粋に公式のcentosイメージはデフォルトでsystemdがアクティブにならず、コンテナを立てても応答しません。docker-composeの際にも一工夫必要になりますが、まずは上の方の記述でcenotsのイメージ設定をしましょう。",[352,762,764],{"id":763},"apache24をいれる","apache2.4をいれる",[24,766,769],{"className":767,"code":768,"language":29},[27],"RUN yum -y install httpd && yum clean all\n\nCOPY .\u002Fhttpd.conf \u002Fetc\u002Fhttpd\u002Fconf\u002Fhttpd.conf\n",[31,770,768],{"__ignoreMap":33},[13,772,773,774,777,778,781,782,785,786,788],{},"こんだけです。",[31,775,776],{},"yum -y install httpd","を",[31,779,780],{},"RUN","してhttpdを入れているだけですね。ちなみにdockerコンテナ内でyumなどを用いてインストールする際は",[31,783,784],{},"-y","オプションをつけましょう。",[31,787,784],{},"オプションは全てyesで答えるというオプションです。これがないとインストール時に「本当にインストールしますか？（Yes\u002FNo）」と聞かれ、ビルドがとまります。",[13,790,791,792,794,795,797,798,800,801,803,804,806],{},"そして",[31,793,363],{},"と同じディレクトリにいる",[31,796,370],{},"をコンテナ内の",[31,799,370],{},"にCOPYします。",[31,802,370],{},"は別途にapache2.4コンテナを立ててコピーするか、最初はこのCOPY部分をコメントアウトして一度この",[31,805,363],{},"をビルドしてコピーしても大丈夫です。",[808,809,813],"div",{"className":810},[811,812],"alert","alert-info","\nコンテナ内からソースを手元にコピーするためにはdocker cp コマンドを用います。\n",[13,815,816],{},"例えば別途にapache2.4という名前でコンテナを立ち上げている場合、以下の様にしてhttpd.confをコピーします。",[24,818,821],{"className":819,"code":820,"language":29},[27],"docker container cp apache2.4:\u002Fetc\u002Fhttpd\u002Fconf\u002Fhttpd.conf .\u002F\n",[31,822,820],{"__ignoreMap":33},[13,824,825,826,828],{},"こうするとコンテナ内のソースが手元にコピーされます。インストールする環境によって",[31,827,370],{},"の置き場所が変わったりもするのでまずはCOPY無しでビルドして、パスを確認してから手元にコピーするといいです。",[352,830,832],{"id":831},"php-をいれる","php をいれる",[24,834,836],{"className":393,"code":835,"language":395,"meta":33,"style":33},"RUN rpm -ivh http:\u002F\u002Fftp.riken.jp\u002FLinux\u002Fremi\u002Fenterprise\u002Fremi-release-8.rpm\n\nRUN yum -y install php74-php php74-php-mysqli php74-php-gd php74-php-mbstring php74-php-opcache php74-php-xml php74-php-pear php74-php-devel php74-php-pecl-imagick php74-php-pecl-imagick-devel php74-php-pecl-zip\n\nRUN ln \u002Fusr\u002Fbin\u002Fphp74 \u002Fusr\u002Fbin\u002Fphp\n\nCOPY .\u002Fphp.ini \u002Fetc\u002Fopt\u002Fremi\u002Fphp74\u002Fphp.ini\n",[31,837,838,842,846,850,854,858,862],{"__ignoreMap":33},[46,839,840],{"class":48,"line":49},[46,841,495],{},[46,843,844],{"class":48,"line":70},[46,845,408],{"emptyLinePlaceholder":407},[46,847,848],{"class":48,"line":82},[46,849,532],{},[46,851,852],{"class":48,"line":91},[46,853,408],{"emptyLinePlaceholder":407},[46,855,856],{"class":48,"line":102},[46,857,543],{},[46,859,860],{"class":48,"line":112},[46,861,408],{"emptyLinePlaceholder":407},[46,863,864],{"class":48,"line":121},[46,865,554],{},[13,867,868,871,872,875,876,879,880,883],{},[31,869,870],{},"remi","リポジトリをインストールして",[31,873,874],{},"yum","を通じてphp7.4を入れます。",[31,877,878],{},"RUN ln \u002Fusr\u002Fbin\u002Fphp74 \u002Fusr\u002Fbin\u002Fphp"," で ",[31,881,882],{},"php","コマンドを使用できる様にします。",[13,885,791,886,888,889,891],{},[31,887,370],{},"と同じ様に",[31,890,377],{},"を手元からコピーします。",[352,893,894],{"id":894},"権限の設定と永続化",[24,896,898],{"className":393,"code":897,"language":395,"meta":33,"style":33},"RUN chown -R apache:apache \u002Fvar\u002Fwww\u002Fhtml\n\nRUN systemctl enable php74-php-fpm\n\nRUN systemctl enable httpd \n\nVOLUME [ \"\u002Fvar\u002Fwww\u002Fhtml\" ]\n",[31,899,900,904,908,912,916,920,924],{"__ignoreMap":33},[46,901,902],{"class":48,"line":49},[46,903,565],{},[46,905,906],{"class":48,"line":70},[46,907,408],{"emptyLinePlaceholder":407},[46,909,910],{"class":48,"line":82},[46,911,576],{},[46,913,914],{"class":48,"line":91},[46,915,408],{"emptyLinePlaceholder":407},[46,917,918],{"class":48,"line":102},[46,919,587],{},[46,921,922],{"class":48,"line":112},[46,923,408],{"emptyLinePlaceholder":407},[46,925,926],{"class":48,"line":121},[46,927,598],{},[13,929,930],{},"webサーバーとphpを入れたのでドキュメントルート をapacheが触れる様に権限を変更します。そしてwebサーバーとphp-fpmが自動で起動する様にします。最後にlaravelプロジェクトをドキュメントルート配下にボリュームできる様にボリュームの設定をします。",[352,932,934],{"id":933},"nodejsをいれるおまけ","node.jsをいれる（おまけ）",[13,936,937],{},"node.jsをいれるのはフロントの開発でnuxt.jsを用いるためです。ここは飛ばしても構いません。",[24,939,941],{"className":393,"code":940,"language":395,"meta":33,"style":33},"RUN dnf -y module enable nodejs:12\n\nRUN dnf -y install nodejs\n",[31,942,943,947,951],{"__ignoreMap":33},[46,944,945],{"class":48,"line":49},[46,946,619],{},[46,948,949],{"class":48,"line":70},[46,950,408],{"emptyLinePlaceholder":407},[46,952,953],{"class":48,"line":82},[46,954,630],{},[20,956,957],{"id":957},"イメージをビルドする",[13,959,960],{},"webサーバーのDockerfileが書き終わったのでまずビルドしてイメージを作りましょう。Dockerfileがあるディレクトリで以下のコマンドを唱えます。",[24,962,965],{"className":963,"code":964,"language":29},[27],"$ docker build -t centos_apache:1.0 . \n",[31,966,964],{"__ignoreMap":33},[13,968,969,972,973,976,977,980],{},[31,970,971],{},". ","は「現在のディレクトリ」という意味です。そして -t でタグをつけを有効にします。今回作成したイメージは ",[31,974,975],{},"centos_apache","で",[31,978,979],{},"1.0","というタグをつけておきます。ビルドは少し時間がかかりますが、完了すればローカルにこのイメージが登録されます。dockerhubなどに置いておけば、他の人にがpullできる様になります。",[24,982,985],{"className":983,"code":984,"language":29},[27],"$ docker images                                           \nREPOSITORY                     TAG                 IMAGE ID            CREATED             SIZE\ncentos_apache                   1.0                54784hkjdfjk        17 hours ago        941MB\n",[31,986,984],{"__ignoreMap":33},[20,988,989],{"id":989},"docker-composeを作成",[13,991,992,993,995],{},"それではosから調整したwebサーバーイメージは作成できたので、次はdocker-composeを使用してDBコンテナとの連携などをしていきます。",[31,994,41],{},"は以下の通りです。",[24,997,999],{"className":39,"code":998,"filename":41,"language":42,"meta":33,"style":33},"version: '3'\nservices: \n  web_1:\n    image: centos_apache:1.0\n    depends_on: \n      - db\n    volumes: \n      - .\u002Flaravel\u002F:\u002Fvar\u002Fwww\u002Fhtml\u002F\n      - \u002Fsys\u002Ffs\u002Fcgroup:\u002Fsys\u002Ffs\u002Fcgroup:ro\n    ports: \n      - \"9000:80\"\n      - \"3000:3000\"\n    privileged: true\n    command: \u002Fsbin\u002Finit\n  db:\n    image: mysql:5.7\n    environment:\n      MYSQL_DATABASE: trend_system\n      MYSQL_USER: trend\n      MYSQL_PASSWORD: trendtrend\n      MYSQL_ROOT_PASSWORD: rootroot\n    ports: \n      - \"3306:3306\"\n    volumes: \n      - laravel_data:\u002Fvar\u002Flib\u002Fmysql\nvolumes: \n  laravel_data: {}\n",[31,1000,1001,1013,1021,1027,1036,1044,1050,1058,1064,1070,1078,1088,1098,1106,1114,1120,1128,1134,1143,1152,1161,1169,1177,1187,1195,1201,1209],{"__ignoreMap":33},[46,1002,1003,1005,1007,1009,1011],{"class":48,"line":49},[46,1004,53],{"class":52},[46,1006,57],{"class":56},[46,1008,60],{"class":56},[46,1010,64],{"class":63},[46,1012,67],{"class":56},[46,1014,1015,1017,1019],{"class":48,"line":70},[46,1016,73],{"class":52},[46,1018,57],{"class":56},[46,1020,79],{"class":78},[46,1022,1023,1025],{"class":48,"line":82},[46,1024,85],{"class":52},[46,1026,88],{"class":56},[46,1028,1029,1031,1033],{"class":48,"line":91},[46,1030,94],{"class":52},[46,1032,57],{"class":56},[46,1034,1035],{"class":63}," centos_apache:1.0\n",[46,1037,1038,1040,1042],{"class":48,"line":102},[46,1039,105],{"class":52},[46,1041,57],{"class":56},[46,1043,79],{"class":78},[46,1045,1046,1048],{"class":48,"line":112},[46,1047,115],{"class":56},[46,1049,118],{"class":63},[46,1051,1052,1054,1056],{"class":48,"line":121},[46,1053,124],{"class":52},[46,1055,57],{"class":56},[46,1057,79],{"class":78},[46,1059,1060,1062],{"class":48,"line":131},[46,1061,115],{"class":56},[46,1063,136],{"class":63},[46,1065,1066,1068],{"class":48,"line":139},[46,1067,115],{"class":56},[46,1069,144],{"class":63},[46,1071,1072,1074,1076],{"class":48,"line":147},[46,1073,150],{"class":52},[46,1075,57],{"class":56},[46,1077,79],{"class":78},[46,1079,1080,1082,1084,1086],{"class":48,"line":157},[46,1081,115],{"class":56},[46,1083,162],{"class":56},[46,1085,165],{"class":63},[46,1087,168],{"class":56},[46,1089,1090,1092,1094,1096],{"class":48,"line":171},[46,1091,115],{"class":56},[46,1093,162],{"class":56},[46,1095,178],{"class":63},[46,1097,168],{"class":56},[46,1099,1100,1102,1104],{"class":48,"line":183},[46,1101,186],{"class":52},[46,1103,57],{"class":56},[46,1105,192],{"class":191},[46,1107,1108,1110,1112],{"class":48,"line":195},[46,1109,198],{"class":52},[46,1111,57],{"class":56},[46,1113,203],{"class":63},[46,1115,1116,1118],{"class":48,"line":206},[46,1117,209],{"class":52},[46,1119,88],{"class":56},[46,1121,1122,1124,1126],{"class":48,"line":4},[46,1123,94],{"class":52},[46,1125,57],{"class":56},[46,1127,220],{"class":63},[46,1129,1130,1132],{"class":48,"line":223},[46,1131,226],{"class":52},[46,1133,88],{"class":56},[46,1135,1136,1138,1140],{"class":48,"line":231},[46,1137,234],{"class":52},[46,1139,57],{"class":56},[46,1141,1142],{"class":63}," trend_system\n",[46,1144,1145,1147,1149],{"class":48,"line":242},[46,1146,245],{"class":52},[46,1148,57],{"class":56},[46,1150,1151],{"class":63}," trend\n",[46,1153,1154,1156,1158],{"class":48,"line":253},[46,1155,256],{"class":52},[46,1157,57],{"class":56},[46,1159,1160],{"class":63}," trendtrend\n",[46,1162,1163,1165,1167],{"class":48,"line":264},[46,1164,267],{"class":52},[46,1166,57],{"class":56},[46,1168,272],{"class":63},[46,1170,1171,1173,1175],{"class":48,"line":275},[46,1172,150],{"class":52},[46,1174,57],{"class":56},[46,1176,79],{"class":78},[46,1178,1179,1181,1183,1185],{"class":48,"line":284},[46,1180,115],{"class":56},[46,1182,162],{"class":56},[46,1184,291],{"class":63},[46,1186,168],{"class":56},[46,1188,1189,1191,1193],{"class":48,"line":296},[46,1190,124],{"class":52},[46,1192,57],{"class":56},[46,1194,79],{"class":78},[46,1196,1197,1199],{"class":48,"line":305},[46,1198,115],{"class":56},[46,1200,310],{"class":63},[46,1202,1203,1205,1207],{"class":48,"line":313},[46,1204,316],{"class":52},[46,1206,57],{"class":56},[46,1208,79],{"class":78},[46,1210,1211,1213,1215],{"class":48,"line":323},[46,1212,326],{"class":52},[46,1214,57],{"class":56},[46,1216,331],{"class":56},[13,1218,1219],{},"コンテナはwebサーバーとDBサーバーの二つをたて、それらをdepends_onを通じて連携します。そして手元にはlaravelとnuxtをインストールしたプロジェクトディレクトリを置いておき、それをコンテナのドキュメントルートに置くと行った手順です。",[13,1221,1222],{},"それでは詳細を解説していきます。",[352,1224,1225],{"id":1225},"webサーバーコンテナの記述",[24,1227,1229],{"className":39,"code":1228,"filename":41,"language":42,"meta":33,"style":33},"web_1:\n    image: centos_apache:1.0\n    depends_on: \n        - db\n    volumes: \n        - .\u002Flaravel\u002F:\u002Fvar\u002Fwww\u002Fhtml\u002F\n        - \u002Fsys\u002Ffs\u002Fcgroup:\u002Fsys\u002Ffs\u002Fcgroup:ro\n    ports: \n        - \"9000:80\"\n        - \"3000:3000\"\n    privileged: true\n    command: \u002Fsbin\u002Finit\n",[31,1230,1231,1238,1246,1254,1261,1269,1275,1281,1289,1299,1309,1317],{"__ignoreMap":33},[46,1232,1233,1236],{"class":48,"line":49},[46,1234,1235],{"class":52},"web_1",[46,1237,88],{"class":56},[46,1239,1240,1242,1244],{"class":48,"line":70},[46,1241,94],{"class":52},[46,1243,57],{"class":56},[46,1245,1035],{"class":63},[46,1247,1248,1250,1252],{"class":48,"line":82},[46,1249,105],{"class":52},[46,1251,57],{"class":56},[46,1253,79],{"class":78},[46,1255,1256,1259],{"class":48,"line":91},[46,1257,1258],{"class":56},"        -",[46,1260,118],{"class":63},[46,1262,1263,1265,1267],{"class":48,"line":102},[46,1264,124],{"class":52},[46,1266,57],{"class":56},[46,1268,79],{"class":78},[46,1270,1271,1273],{"class":48,"line":112},[46,1272,1258],{"class":56},[46,1274,136],{"class":63},[46,1276,1277,1279],{"class":48,"line":121},[46,1278,1258],{"class":56},[46,1280,144],{"class":63},[46,1282,1283,1285,1287],{"class":48,"line":131},[46,1284,150],{"class":52},[46,1286,57],{"class":56},[46,1288,79],{"class":78},[46,1290,1291,1293,1295,1297],{"class":48,"line":139},[46,1292,1258],{"class":56},[46,1294,162],{"class":56},[46,1296,165],{"class":63},[46,1298,168],{"class":56},[46,1300,1301,1303,1305,1307],{"class":48,"line":147},[46,1302,1258],{"class":56},[46,1304,162],{"class":56},[46,1306,178],{"class":63},[46,1308,168],{"class":56},[46,1310,1311,1313,1315],{"class":48,"line":157},[46,1312,186],{"class":52},[46,1314,57],{"class":56},[46,1316,192],{"class":191},[46,1318,1319,1321,1323],{"class":48,"line":171},[46,1320,198],{"class":52},[46,1322,57],{"class":56},[46,1324,203],{"class":63},[13,1326,1327,1328,1331,1332,1335],{},"この箇所は先ほどビルドしたwebサーバーイメージを用いたコンテナに関する記述です。",[31,1329,1330],{},"depends_on: -db","とすることでDBと連携ができる様になります。そして",[31,1333,1334],{},".\u002Flaravel\u002F:\u002Fvar\u002Fwww\u002Fhtml\u002F","でプロジェクトソースをコンテナに入れ込んでいます。",[1337,1338,1339],"h4",{"id":1339},"centosのための記述",[13,1341,1342],{},"そして先述の通りcenots8のイメージを使用している場合はコンテナがきちんと起動するために以下のコードが必要です。",[24,1344,1346],{"className":39,"code":1345,"filename":41,"language":42,"meta":33,"style":33},"volumes: \n    - \u002Fsys\u002Ffs\u002Fcgroup:\u002Fsys\u002Ffs\u002Fcgroup:ro\nprivileged: true\ncommand: \u002Fsbin\u002Finit\n",[31,1347,1348,1356,1363,1372],{"__ignoreMap":33},[46,1349,1350,1352,1354],{"class":48,"line":49},[46,1351,316],{"class":52},[46,1353,57],{"class":56},[46,1355,79],{"class":78},[46,1357,1358,1361],{"class":48,"line":70},[46,1359,1360],{"class":56},"    -",[46,1362,144],{"class":63},[46,1364,1365,1368,1370],{"class":48,"line":82},[46,1366,1367],{"class":52},"privileged",[46,1369,57],{"class":56},[46,1371,192],{"class":191},[46,1373,1374,1377,1379],{"class":48,"line":91},[46,1375,1376],{"class":52},"command",[46,1378,57],{"class":56},[46,1380,203],{"class":63},[13,1382,1383,1384,1388,1389,1392,1393,1396],{},"詳細は",[725,1385,738],{"href":1386,"rel":1387},"https:\u002F\u002Fjun-app.com\u002Fdocker-centos8\u002F",[729],"の記事にて解説します。centos8はデフォルトでコンテナ内でアクティブにならずwebサーバーも起動しません。ホストマシンの",[31,1390,1391],{},"systemd","をマウントし、そして",[31,1394,1395],{},"privileged: true","にてコンテナにroot権限を与えることで、コンテナのcentos8が動きます。",[1337,1398,1399],{"id":1399},"ポートを通す",[24,1401,1403],{"className":39,"code":1402,"filename":41,"language":42,"meta":33,"style":33},"ports: \n    - \"9000:80\"\n    - \"3000:3000\"\n",[31,1404,1405,1414,1424],{"__ignoreMap":33},[46,1406,1407,1410,1412],{"class":48,"line":49},[46,1408,1409],{"class":52},"ports",[46,1411,57],{"class":56},[46,1413,79],{"class":78},[46,1415,1416,1418,1420,1422],{"class":48,"line":70},[46,1417,1360],{"class":56},[46,1419,162],{"class":56},[46,1421,165],{"class":63},[46,1423,168],{"class":56},[46,1425,1426,1428,1430,1432],{"class":48,"line":82},[46,1427,1360],{"class":56},[46,1429,162],{"class":56},[46,1431,178],{"class":63},[46,1433,168],{"class":56},[13,1435,1436,1437,1440,1441,1444,1445,1448,1449,1451],{},"ここでホストマシンの",[31,1438,1439],{},"localhost:9000","と",[31,1442,1443],{},"localhost:3000","をコンテナの",[31,1446,1447],{},"localhost","、",[31,1450,1443],{},"につなげる様にします。ホストマシンを9000にしたのは気分です。干渉しなければ他のポートでも大丈夫です。",[352,1453,1455],{"id":1454},"dbの記述","DBの記述",[24,1457,1459],{"className":39,"code":1458,"filename":41,"language":42,"meta":33,"style":33},"db:\n    image: mysql:5.7\n    environment:\n      MYSQL_DATABASE: laravel_test\n      MYSQL_USER: test\n      MYSQL_PASSWORD: testtest\n      MYSQL_ROOT_PASSWORD: rootroot\n    ports: \n      - \"3306:3306\"\n    volumes: \n      - laravel_data:\u002Fvar\u002Flib\u002Fmysql\nvolumes: \n  laravel_data: {}\n",[31,1460,1461,1468,1476,1482,1491,1499,1507,1515,1523,1533,1541,1547,1555],{"__ignoreMap":33},[46,1462,1463,1466],{"class":48,"line":49},[46,1464,1465],{"class":52},"db",[46,1467,88],{"class":56},[46,1469,1470,1472,1474],{"class":48,"line":70},[46,1471,94],{"class":52},[46,1473,57],{"class":56},[46,1475,220],{"class":63},[46,1477,1478,1480],{"class":48,"line":82},[46,1479,226],{"class":52},[46,1481,88],{"class":56},[46,1483,1484,1486,1488],{"class":48,"line":91},[46,1485,234],{"class":52},[46,1487,57],{"class":56},[46,1489,1490],{"class":63}," laravel_test\n",[46,1492,1493,1495,1497],{"class":48,"line":102},[46,1494,245],{"class":52},[46,1496,57],{"class":56},[46,1498,250],{"class":63},[46,1500,1501,1503,1505],{"class":48,"line":112},[46,1502,256],{"class":52},[46,1504,57],{"class":56},[46,1506,261],{"class":63},[46,1508,1509,1511,1513],{"class":48,"line":121},[46,1510,267],{"class":52},[46,1512,57],{"class":56},[46,1514,272],{"class":63},[46,1516,1517,1519,1521],{"class":48,"line":131},[46,1518,150],{"class":52},[46,1520,57],{"class":56},[46,1522,79],{"class":78},[46,1524,1525,1527,1529,1531],{"class":48,"line":139},[46,1526,115],{"class":56},[46,1528,162],{"class":56},[46,1530,291],{"class":63},[46,1532,168],{"class":56},[46,1534,1535,1537,1539],{"class":48,"line":147},[46,1536,124],{"class":52},[46,1538,57],{"class":56},[46,1540,79],{"class":78},[46,1542,1543,1545],{"class":48,"line":157},[46,1544,115],{"class":56},[46,1546,310],{"class":63},[46,1548,1549,1551,1553],{"class":48,"line":171},[46,1550,316],{"class":52},[46,1552,57],{"class":56},[46,1554,79],{"class":78},[46,1556,1557,1559,1561],{"class":48,"line":183},[46,1558,326],{"class":52},[46,1560,57],{"class":56},[46,1562,331],{"class":56},[13,1564,1565,1566,1571],{},"DBはmysql:5.7の",[725,1567,1570],{"href":1568,"rel":1569},"https:\u002F\u002Fhub.docker.com\u002F_\u002Fmysql",[729],"公式イメージ","を使用します。パスワードの設定なども簡単に行ってくれます。そしてデータ内容は永続化したいのでlaravel_data:{}をボリュームしておきます。",[13,1573,1574],{},"DBはこれでOKです。もしDBにあるデータをまっさらにしたい場合はコンテナを止めて、ボリュームを削除すれば大丈夫です。",[20,1576,1577],{"id":1577},"laravelのプロジェクトソースの調整",[13,1579,1580,1581,1584],{},"以上でwebサーバーとDBサーバーの準備は整いました。次は",[31,1582,1583],{},"laravel","ソースと細かい調整を行います。まずはlaravelディレクトリにプロジェクトを入れます。今回はバージョン６を入れます。ローカルにはcomposerが入っているものとします。",[24,1586,1589],{"className":1587,"code":1588,"language":29},[27],"$ cd laravel\n$ composer create-project \"laravel\u002Flaravel=6.*\" .\n",[31,1590,1588],{"__ignoreMap":33},[13,1592,1593,1594,1597,1598,1600],{},"laravelのソースが並んだら",[31,1595,1596],{},".env","を以下の様にDBの設定を書き込みます。",[31,1599,41],{},"で設定した通りのパスワード、ユーザーです。",[24,1602,1605],{"className":1603,"code":1604,"filename":1596,"language":29,"meta":33},[27],"...\nDB_CONNECTION=mysql\nDB_HOST=db\nDB_PORT=3306\nDB_DATABASE=laravel_test\nDB_USERNAME=root\nDB_PASSWORD=rootroot\n...\n",[31,1606,1604],{"__ignoreMap":33},[13,1608,1609,1612,1613,1615,1616,1618],{},[31,1610,1611],{},"DB_HOST","は",[31,1614,41],{},"のserviceで定義した名前",[31,1617,1465],{},"で大丈夫です。こうすればマイグレーションができる様になります。",[352,1620,1622],{"id":1621},"httpdconfとphpiniの微調整","httpd.confとphp.iniの微調整",[13,1624,1625,1626,1629,1630,1632,1633,1636],{},"Laravelではドキュメントルート を ",[31,1627,1628],{},"\u002Fpublic"," 配下にする様に言われています。",[31,1631,370],{},"のデフォルトのドキュメントルートは ",[31,1634,1635],{},"\u002Fvar\u002Fwww\u002Fhtml","なので以下の様に変更しておきます",[24,1638,1641],{"className":1639,"code":1640,"filename":370,"language":29,"meta":33},[27],"DocumentRoot \"\u002Fvar\u002Fwww\u002Fhtml\u002Fpublic\"\n\n\u003CDirectory \"\u002Fvar\u002Fwww\u002Fhtml\u002Fpublic\">\n    Options Indexes FollowSymLinks\n    AllowOverride All\n    Require all granted\n\u003C\u002FDirectory>\n",[31,1642,1640],{"__ignoreMap":33},[13,1644,1645,1646,1649],{},"こうするとapacheは",[31,1647,1648],{},"\u002Fvar\u002Fwww\u002Fhtml\u002Fpublic","をドキュメントルートとして見てくれます。",[13,1651,1652],{},"php.iniはタイムゾーン をAsia\u002FTokyoにするだけです。",[20,1654,1656],{"id":1655},"composeを起動","composeを起動！",[13,1658,1659,1660,1663,1664,1666],{},"それでは",[31,1661,1662],{},"docker-compose","で起動します。",[31,1665,41],{},"がある場所にて以下の様に唱えます。",[24,1668,1671],{"className":1669,"code":1670,"language":29},[27],"$ docker-compose up -d\n",[31,1672,1670],{"__ignoreMap":33},[13,1674,1675,1676,1679],{},"ビルドがうまくいけば",[31,1677,1678],{},"docker ps","で2つのコンテナが起動しているのが確認できます。",[24,1681,1684],{"className":1682,"code":1683,"language":29},[27],"CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                          NAMES\n3e121d905e5e        centos_apache:1.0   \"\u002Fsbin\u002Finit\"             5 seconds ago       Up 3 seconds        0.0.0.0:3000->3000\u002Ftcp, 0.0.0.0:9000->80\u002Ftcp   centos_apache_web_1_1\nea5076e310e9        mysql:5.7           \"docker-entrypoint.s…\"   5 seconds ago       Up 4 seconds        0.0.0.0:3306->3306\u002Ftcp, 33060\u002Ftcp              centos_apache_db_1\n",[31,1685,1683],{"__ignoreMap":33},[13,1687,1688,1690],{},[31,1689,1439],{},"をブラウザでアクセスすると",[1692,1693],"image-render",{":src":1694,":width":1695},"'_mix\u002Fsch-2020-11-28-17.33.36-768x407.png'","'100%'",[13,1697,1698],{},"はい。おなじみのLaravelの画面が表示されました。マイグレーションをするためにコンテナに入ります。",[24,1700,1703],{"className":1701,"code":1702,"language":29},[27],"$ docker exec -it centos_apache_web_1_1 \u002Fbin\u002Fbash\n",[31,1704,1702],{"__ignoreMap":33},[13,1706,1707,1708,1711],{},"プロジェクトルートに移動して",[31,1709,1710],{},"php artisan migrate","を唱えるとDBコンテナへマイグレーションされます。",[24,1713,1716],{"className":1714,"code":1715,"language":29},[27],"$ cd \u002Fvar\u002Fwww\u002Fhtml\n$ ls\nREADME.md  artisan    client         composer.lock  database      nuxt.config.js     package.json  public     routes      storage  vendor\napp        bootstrap  composer.json  config         node_modules  package-lock.json  phpunit.xml   resources  server.php  tests    webpack.mix.js\n\n$ php artisan migrate\nMigration table created successfully.\nMigrating: 2014_10_12_000000_create_users_table\nMigrated:  2014_10_12_000000_create_users_table (0.03 seconds)\nMigrating: 2014_10_12_100000_create_password_resets_table\nMigrated:  2014_10_12_100000_create_password_resets_table (0.02 seconds)\nMigrating: 2019_08_19_000000_create_failed_jobs_table\nMigrated:  2019_08_19_000000_create_failed_jobs_table (0.01 seconds)\n",[31,1717,1715],{"__ignoreMap":33},[13,1719,1720],{},"本当に入ったかはDBコンテナに入ってmysqlコマンドを通じて中身を見ると確認できます。ここまでくればLaravelの環境構築は完了です。phpのバージョンを変えてみたりで、OS環境による検証もある程度楽になるでしょう。",[20,1722,1724],{"id":1723},"おまけnuxtjsを起動させる","（おまけ）Nuxt.jsを起動させる",[13,1726,1727],{},"一応私のプロジェクトではnuxt.jsを入れていたので、せっかくなので解説します。larvelディレクトリにてnuxt.jsのプロジェクトを作成します。（ローカルでも、コンテナ内でもどちらでも。私はローカルで作成）",[24,1729,1732],{"className":1730,"code":1731,"language":29},[27],"$ cd laravel\n$ npx create-nuxt-app client\n",[31,1733,1731],{"__ignoreMap":33},[13,1735,1736],{},"以下の様なディレクトリになりました。",[24,1738,1741],{"className":1739,"code":1740,"language":29},[27],".\n├── README.md\n├── app\n├── artisan\n├── bootstrap\n├── client # nuxt ソース\n├── composer.json\n├── composer.lock\n├── config\n├── database\n├── phpunit.xml\n├── public\n├── resources\n├── routes\n├── server.php\n├── storage\n├── tests\n├── vendor\n└── webpack.mix.js\n",[31,1742,1740],{"__ignoreMap":33},[13,1744,1745,1746,1749,1750,1753],{},"ちなみにLaravelに初期にあった",[31,1747,1748],{},"package.json","は削除しています。",[31,1751,1752],{},"client","ディレクトリ配下にnuxtのソースがありますが、これだと管理がしにくいので以下の様に変更します。",[24,1755,1758],{"className":1756,"code":1757,"language":29},[27],".\n├── README.md\n├── app\n├── artisan\n├── bootstrap\n├── client\n├── composer.json\n├── composer.lock\n├── config\n├── database\n├── node_modules #clientから\n├── nuxt.config.js #clientから\n├── package-lock.json #clientから\n├── package.json #clientから\n├── phpunit.xml\n├── public\n├── resources\n├── routes\n├── server.php\n├── storage\n├── tests\n├── vendor\n└── webpack.mix.js\n",[31,1759,1757],{"__ignoreMap":33},[13,1761,1762,1764,1765,1768],{},[31,1763,1752],{},"から4つのファイルを引っ張り出し、そして",[31,1766,1767],{},"nuxt.config.js","に以下を追記します。",[24,1770,1773],{"className":1771,"code":1772,"filename":1767,"language":29,"meta":33},[27],"srcDir: 'client\u002F',\n",[31,1774,1772],{"__ignoreMap":33},[13,1776,1777,1778,1780,1781,1784],{},"こうすることで",[31,1779,1752],{},"配下はnuxtのソースだけを置いて設定ファイルや",[31,1782,1783],{},"dist","はLaravelのプロジェクトルートに出される様にします。この辺はチームの好みによるので自由に変更してください。",[13,1786,1787,1788,1791,1792,1794],{},"そしてコンテナ内に入って ",[31,1789,1790],{},"npm run dev","をすればnuxtの開発サーバーが立ち上がるのですが、その前にまた",[31,1793,1767],{},"に以下の内容を記述します。",[24,1796,1799],{"className":1797,"code":1798,"filename":1767,"language":29,"meta":33},[27],"server: {\n    host: '0.0.0.0'\n},\n",[31,1800,1798],{"__ignoreMap":33},[13,1802,1803,1804,1806,1807,976],{},"dockerコンテナを使用しない場合はこの設定は要らないのですが、コンテナ内で",[31,1805,1790],{},"をしてそれに接続するには上記の設定が必要です。コンテナ内のlocalhost:3000に開発サーバーが立ち上がります。",[31,1808,41],{},[24,1810,1812],{"className":39,"code":1811,"filename":41,"language":42,"meta":33,"style":33},"ports: \n    - \"9000:80\"\n    - \"3000:3000\n",[31,1813,1814,1822,1832],{"__ignoreMap":33},[46,1815,1816,1818,1820],{"class":48,"line":49},[46,1817,1409],{"class":52},[46,1819,57],{"class":56},[46,1821,79],{"class":78},[46,1823,1824,1826,1828,1830],{"class":48,"line":70},[46,1825,1360],{"class":56},[46,1827,162],{"class":56},[46,1829,165],{"class":63},[46,1831,168],{"class":56},[46,1833,1834,1836,1838],{"class":48,"line":82},[46,1835,1360],{"class":56},[46,1837,162],{"class":56},[46,1839,1840],{"class":63},"3000:3000\n",[13,1842,1843],{},"この様にホストの3000とコンテナの3000ポートをつないでいたので、localhost:3000にアクセスすると",[1692,1845],{":src":1846,":width":1695},"'_mix\u002Fsch-2020-11-28-18.02.48-768x599.png'",[13,1848,1849],{},"nuxtの画面が現れました。（UIフレームワークにvuetifyを選択するとこの画面になります。）これでnuxtの開発もコンテナ内で実行できます。",[1851,1852,1853],"style",{},"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 .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .sbqyR, html code.shiki .sbqyR{--shiki-default:#FF9CAC}",{"title":33,"searchDepth":82,"depth":82,"links":1855},[1856,1860,1867,1868,1875,1878,1879],{"id":22,"depth":70,"text":22,"children":1857},[1858,1859],{"id":354,"depth":82,"text":354},{"id":381,"depth":82,"text":381},{"id":387,"depth":70,"text":387,"children":1861},[1862,1863,1864,1865,1866],{"id":647,"depth":82,"text":648},{"id":763,"depth":82,"text":764},{"id":831,"depth":82,"text":832},{"id":894,"depth":82,"text":894},{"id":933,"depth":82,"text":934},{"id":957,"depth":70,"text":957},{"id":989,"depth":70,"text":989,"children":1869},[1870,1874],{"id":1225,"depth":82,"text":1225,"children":1871},[1872,1873],{"id":1339,"depth":91,"text":1339},{"id":1399,"depth":91,"text":1399},{"id":1454,"depth":82,"text":1455},{"id":1577,"depth":70,"text":1577,"children":1876},[1877],{"id":1621,"depth":82,"text":1622},{"id":1655,"depth":70,"text":1656},{"id":1723,"depth":70,"text":1724},[1881],"devstack","2026-01-16","md",null,{},"\u002Farticles\u002Fbuild-lamp-with-docker",{"title":8,"description":8},"articles\u002Fbuild-lamp-with-docker",[1890,1583],"docker","_mix\u002Fdokcer_laravel.jpeg","V2jA9dknjBNGZQzsV_h65rI9ap5VBJ9l1skOpFFoD8c",{"id":1894,"title":1895,"body":1896,"category":5593,"createdAt":5594,"description":1895,"extension":1883,"index":1884,"meta":5595,"navigation":407,"path":5596,"publish":407,"seo":5597,"series":1884,"seriesTitle":1884,"stem":5598,"tag":5599,"thumbnail":5601,"updatedAt":5594,"__hash__":5602},"articles\u002Farticles\u002Flaravel-nuxt-jwt-spa.md","laravel 6 + Nuxt.js で作るJWT認証つきSPA構築",{"type":10,"value":1897,"toc":5556},[1898,1901,1904,1907,1910,1918,1922,1925,1930,1933,1948,1951,1954,1962,2198,2220,2239,2243,2246,2252,2271,2275,2278,2282,2285,2292,2298,2304,2308,2311,2317,2324,2330,2334,2345,2349,2352,2358,2374,2377,2380,2386,2392,2468,2475,2481,2484,2487,2490,2496,2499,2505,2511,2515,2753,2757,2767,2902,2905,2911,2915,2922,2967,2970,2976,2979,2986,2992,2997,3402,3405,3409,3418,3421,3424,3431,3434,3437,3444,3448,3451,3454,3458,3464,3468,3494,3498,3504,3522,3607,3620,3624,3633,3636,3639,3645,3649,3654,3660,3663,3669,3982,3985,3996,4003,4024,4031,4034,4037,4152,4165,4171,4183,4186,4189,4192,4195,4202,5396,5407,5413,5416,5419,5422,5425,5428,5431,5434,5437,5464,5468,5471,5477,5486,5489,5492,5496,5499,5525,5528,5531,5553],[13,1899,1900],{},"こんにちはjunです。laravel をAPIサーバーとして扱いフロントはNuxt.jsのSPAで構築するプロジェクトがありました。認証を実装したり初期のセッティングでそこそこ詰まったのでメモがてら記事にします。",[13,1902,1903],{},"この手の記事にありがちな「開発サーバーまでの段階しかやらない」ではなくSPAをビルドしてDocker上の仮想的に作成したサーバー環境で実際に一つのアプリケーションとして動かすとこまでやってみます。",[13,1905,1906],{},"独立したフロントエンド とバックをどうやってつなげて、どんなサーバー構成にすればいいのか？という視点で見ていただければ幸いです。また私も昨日にようやく自分の頭で纏った程度なので、この手のアプリケーション作成がﾁｮｯﾄﾃﾞｷﾙ人はぜひアドバイスやコメントお願いします。",[13,1908,1909],{},"Dockerを用いた環境構築概要から話しています。環境がありLravelとNuxtのAPI連携について知りたい人は **「Larvel にJWT認証機能を追加する」**から読みはじめてください。",[13,1911,1912,1913,1917],{},"使用環境\ndocker：19.03.13\nmaxOS Catalina：10.15.5\ncomposer：1.10.6\nNode.js：12.19.0\n",[1914,1915,1916],"strong",{},"Laravel：6.20","\nNuxt.js：2.14.6",[20,1919,1921],{"id":1920},"dockerで仮想環境を構築","Dockerで仮想環境を構築",[13,1923,1924],{},"私が今回構築するサーバー構成は以下の図の感じです。",[1692,1926],{":src":1927,":width":1928,":center":1929},"'_mix\u002Flaravel-nuxt.jpeg'","'500px'","true",[13,1931,1932],{},"webサーバーではバーチャルホスト を使って、80・443などの通常のHTTP通信に対してはNuxt.jsのビルドファイルを返します。そして8000ポートにはLaravelのプロジェクトをおいて、APIサーバーとして使用します。",[13,1934,1935,1936,1939,1940,1943,1944,1947],{},"Nuxt.jsはLaravelから発行されたトークンを用いてAPIにアクセスしてデータを引っ張ります。実際の運用ではドメインが付与されるので、例えば",[31,1937,1938],{},"service.com","として、",[31,1941,1942],{},"http(s):\u002F\u002Fservice.com","はNuxt.jsへ、そしてNuxt.jsは",[31,1945,1946],{},"https:\u002F\u002Fapi.service.com","へAPIを飛ばします。",[13,1949,1950],{},"バーチャルホスト の設定をすることで公開側とAPIをconfレベルで分けることができます。つまりDockerのwebサーバーイメージには、ApacheまたはNginxの設定ファイルをボリュームして、それぞれのドキュメントルート を指定できるような環境があれば大丈夫です。（無理に私のイメージとかに合わせなくても大丈夫ということです。）",[352,1952,1953],{"id":1953},"docker-composeは以下のような感じ",[13,1955,1956,1958,1959,1961],{},[725,1957,738],{"href":1886},"記事で作成したイメージを使用します。cenotsから構築したLAMP環境です。",[31,1960,975],{},"イメージを用いてweb側をビルドしています。",[24,1963,1965],{"className":39,"code":1964,"filename":41,"language":42,"meta":33,"style":33},"version: '3'\nservices: \n  web_1:\n    image: centos_apache:1.0\n    depends_on: \n      - db\n    volumes: \n      - .\u002Fhtml\u002F:\u002Fvar\u002Fwww\u002Fhtml\u002F\n      - .\u002Fweb_1\u002Fhttpd.conf:\u002Fetc\u002Fhttpd\u002Fconf\u002Fhttpd.conf\n      - \u002Fsys\u002Ffs\u002Fcgroup:\u002Fsys\u002Ffs\u002Fcgroup:ro\n    ports: \n      - \"9000:80\"\n      - \"3000:3000\"\n      - \"8000:8000\"\n    privileged: true\n    command: \u002Fsbin\u002Finit\n  db:\n    image: mysql:5.7\n    environment:\n      MYSQL_DATABASE: trend_system\n      MYSQL_USER: trend\n      MYSQL_PASSWORD: trendtrend\n      MYSQL_ROOT_PASSWORD: rootroot\n    ports: \n      - \"3306:3306\"\n    volumes: \n      - laravel_data:\u002Fvar\u002Flib\u002Fmysql\nvolumes: \n  laravel_data: {}\n",[31,1966,1967,1979,1987,1993,2001,2009,2015,2023,2030,2037,2043,2051,2061,2071,2082,2090,2098,2104,2112,2118,2126,2134,2142,2150,2158,2168,2176,2182,2190],{"__ignoreMap":33},[46,1968,1969,1971,1973,1975,1977],{"class":48,"line":49},[46,1970,53],{"class":52},[46,1972,57],{"class":56},[46,1974,60],{"class":56},[46,1976,64],{"class":63},[46,1978,67],{"class":56},[46,1980,1981,1983,1985],{"class":48,"line":70},[46,1982,73],{"class":52},[46,1984,57],{"class":56},[46,1986,79],{"class":78},[46,1988,1989,1991],{"class":48,"line":82},[46,1990,85],{"class":52},[46,1992,88],{"class":56},[46,1994,1995,1997,1999],{"class":48,"line":91},[46,1996,94],{"class":52},[46,1998,57],{"class":56},[46,2000,1035],{"class":63},[46,2002,2003,2005,2007],{"class":48,"line":102},[46,2004,105],{"class":52},[46,2006,57],{"class":56},[46,2008,79],{"class":78},[46,2010,2011,2013],{"class":48,"line":112},[46,2012,115],{"class":56},[46,2014,118],{"class":63},[46,2016,2017,2019,2021],{"class":48,"line":121},[46,2018,124],{"class":52},[46,2020,57],{"class":56},[46,2022,79],{"class":78},[46,2024,2025,2027],{"class":48,"line":131},[46,2026,115],{"class":56},[46,2028,2029],{"class":63}," .\u002Fhtml\u002F:\u002Fvar\u002Fwww\u002Fhtml\u002F\n",[46,2031,2032,2034],{"class":48,"line":139},[46,2033,115],{"class":56},[46,2035,2036],{"class":63}," .\u002Fweb_1\u002Fhttpd.conf:\u002Fetc\u002Fhttpd\u002Fconf\u002Fhttpd.conf\n",[46,2038,2039,2041],{"class":48,"line":147},[46,2040,115],{"class":56},[46,2042,144],{"class":63},[46,2044,2045,2047,2049],{"class":48,"line":157},[46,2046,150],{"class":52},[46,2048,57],{"class":56},[46,2050,79],{"class":78},[46,2052,2053,2055,2057,2059],{"class":48,"line":171},[46,2054,115],{"class":56},[46,2056,162],{"class":56},[46,2058,165],{"class":63},[46,2060,168],{"class":56},[46,2062,2063,2065,2067,2069],{"class":48,"line":183},[46,2064,115],{"class":56},[46,2066,162],{"class":56},[46,2068,178],{"class":63},[46,2070,168],{"class":56},[46,2072,2073,2075,2077,2080],{"class":48,"line":195},[46,2074,115],{"class":56},[46,2076,162],{"class":56},[46,2078,2079],{"class":63},"8000:8000",[46,2081,168],{"class":56},[46,2083,2084,2086,2088],{"class":48,"line":206},[46,2085,186],{"class":52},[46,2087,57],{"class":56},[46,2089,192],{"class":191},[46,2091,2092,2094,2096],{"class":48,"line":4},[46,2093,198],{"class":52},[46,2095,57],{"class":56},[46,2097,203],{"class":63},[46,2099,2100,2102],{"class":48,"line":223},[46,2101,209],{"class":52},[46,2103,88],{"class":56},[46,2105,2106,2108,2110],{"class":48,"line":231},[46,2107,94],{"class":52},[46,2109,57],{"class":56},[46,2111,220],{"class":63},[46,2113,2114,2116],{"class":48,"line":242},[46,2115,226],{"class":52},[46,2117,88],{"class":56},[46,2119,2120,2122,2124],{"class":48,"line":253},[46,2121,234],{"class":52},[46,2123,57],{"class":56},[46,2125,1142],{"class":63},[46,2127,2128,2130,2132],{"class":48,"line":264},[46,2129,245],{"class":52},[46,2131,57],{"class":56},[46,2133,1151],{"class":63},[46,2135,2136,2138,2140],{"class":48,"line":275},[46,2137,256],{"class":52},[46,2139,57],{"class":56},[46,2141,1160],{"class":63},[46,2143,2144,2146,2148],{"class":48,"line":284},[46,2145,267],{"class":52},[46,2147,57],{"class":56},[46,2149,272],{"class":63},[46,2151,2152,2154,2156],{"class":48,"line":296},[46,2153,150],{"class":52},[46,2155,57],{"class":56},[46,2157,79],{"class":78},[46,2159,2160,2162,2164,2166],{"class":48,"line":305},[46,2161,115],{"class":56},[46,2163,162],{"class":56},[46,2165,291],{"class":63},[46,2167,168],{"class":56},[46,2169,2170,2172,2174],{"class":48,"line":313},[46,2171,124],{"class":52},[46,2173,57],{"class":56},[46,2175,79],{"class":78},[46,2177,2178,2180],{"class":48,"line":323},[46,2179,115],{"class":56},[46,2181,310],{"class":63},[46,2183,2184,2186,2188],{"class":48,"line":529},[46,2185,316],{"class":52},[46,2187,57],{"class":56},[46,2189,79],{"class":78},[46,2191,2192,2194,2196],{"class":48,"line":535},[46,2193,326],{"class":52},[46,2195,57],{"class":56},[46,2197,331],{"class":56},[13,2199,2200,2201,2203,2204,2207,2208,2211,2212,2215,2216,2219],{},"ホストマシンの",[31,2202,1439],{},"にアクセスするとコンテナの",[31,2205,2206],{},"localhost:80","につながります。Nuxt.jsの開発サーバーポートとなる",[31,2209,2210],{},"3000","はホストとコンテナ一緒にしています。多分使わないのですが ",[31,2213,2214],{},"php artisan serve","で立てるLaravel開発サーバーポート",[31,2217,2218],{},"8000","も一応用意しておきます。",[13,2221,2222,2223,2226,2227,1448,2230,2232,2233,2235,2236,2238],{},"ホスト側には",[31,2224,2225],{},"html","ディレクトリに",[31,2228,2229],{},"frontend",[31,2231,1583],{},"と言ったディレクトリがあります。Nuxt、Laravelのソースがそれぞれに配置されています。コンテナ起動時にはその",[31,2234,2225],{},"ディレクトリはコンテナの",[31,2237,1635],{},"にボリュームされます。",[352,2240,2242],{"id":2241},"httpdconfは以下の感じ","httpd.confは以下の感じ",[13,2244,2245],{},"Nginx兄貴達にはすみませんが、とりあえず以下のようなバーチャルホスト 設定が立てられば大丈夫です。",[24,2247,2250],{"className":2248,"code":2249,"filename":370,"language":29,"meta":33},[27],"Listen 8000\n\u003CDirectory \"\u002Fvar\u002Fwww\u002Fhtml\u002Ffrontend\u002Fdist\">\n    Options Indexes FollowSymLinks\n    AllowOverride All\n    Require all granted\n\u003C\u002FDirectory>\n\n\u003CVirtualHost *:80>\n    ServerName example.com\n    DocumentRoot \u002Fvar\u002Fwww\u002Fhtml\u002Ffrontend\u002Fdist\n\u003C\u002FVirtualHost>\n\n\u003CVirtualHost *:8000>\n    ServerName example.com\n    DocumentRoot \u002Fvar\u002Fwww\u002Fhtml\u002Flaravel\u002Fpublic\n\u003C\u002FVirtualHost>\n",[31,2251,2249],{"__ignoreMap":33},[13,2253,2254,2255,2258,2259,2262,2263,2266,2267,2270],{},"forntendはnuxtのプロジェクトディレクトリ、laravelはLaravelのプロジェクトディレクトリ名です。80でリクエストが来たらnuxtのビルドした",[31,2256,2257],{},"index.html","がある",[31,2260,2261],{},"\u002Fvar\u002Fwww\u002Fhtml\u002Ffrontend\u002Fdist","へ飛ばされます。一応後でブラウザからはアクセスできないようにしますが、8000できたリクエストは",[31,2264,2265],{},"\u002Fvar\u002Fwww\u002Fhtml\u002Flaravel\u002Fpublic","の",[31,2268,2269],{},"index.php","がキャッチします。",[352,2272,2274],{"id":2273},"ぶっちゃけdockerなくてもとりあえず行けます","ぶっちゃけDockerなくてもとりあえず行けます",[13,2276,2277],{},"初心者の人は「？」となるかもしれません。しかしDBがあり、ホストマシン上でnuxtプロジェクトとLaravelお互いの開発サーバ同士で連絡が取れれば、見出しの内容は実装可能です。ビルドまでしたり総合的に確かめたい場合にDockerを使用してください。",[20,2279,2281],{"id":2280},"laravelの設定をする","Laravelの設定をする",[352,2283,2284],{"id":2284},"コンテナを起動",[13,2286,2287,2288,2291],{},"docker-compose.ymlがあるディレクトリで",[31,2289,2290],{},"docker-compose up -d"," をします。特に問題なければコンテナーが起動するはずです。起動したら",[24,2293,2296],{"className":2294,"code":2295,"language":29},[27],"docker exec -it {web側のコンテナ名} \u002Fbin\u002Fbash\n",[31,2297,2295],{"__ignoreMap":33},[13,2299,2300,2301,2303],{},"をしてコンテナに入りましょう。",[31,2302,1635],{},"にいくとマウントしたLaravelプロジェクトとNuxt.jsプロジェクトがいるはずです。",[352,2305,2307],{"id":2306},"laravel-のenvファイルを変更","laravel のenvファイルを変更",[13,2309,2310],{},"マイグレーションをしたいのでlaravelのenvを設定します。",[24,2312,2315],{"className":2313,"code":2314,"filename":1596,"language":29,"meta":33},[27],"DB_CONNECTION=mysql\nDB_HOST=db \u002F\u002F docker-compose.yml で定義したDBのコンテナ\nDB_PORT=3306\nDB_DATABASE=laravel_test\nDB_USERNAME=root\nDB_PASSWORD=rootroot\n",[31,2316,2314],{"__ignoreMap":33},[13,2318,2319,2320,2323],{},"docker-composeのおかげでこの設定であればつながります。コンテナの中",[31,2321,2322],{},"\u002Fvar\u002Fwww\u002Fhtmm\u002Flaravel","にてマイグレーションを実行します。",[24,2325,2328],{"className":2326,"code":2327,"language":29},[27],"$ php artisan migrate\n",[31,2329,2327],{"__ignoreMap":33},[20,2331,2333],{"id":2332},"larvel-にjwt認証機能を追加する","Larvel にJWT認証機能を追加する",[13,2335,2336,2337,2340,2341,2344],{},"ここからが本番です。ちなみに私が使用している",[1914,2338,2339],{},"Laravel はバージョン6.2.0","です。バージョンによって設定が異なったりします。この ",[1914,2342,2343],{},"記事は2020年12月時点でLravel 8 は出ているけど、LTSなどの都合でLravel 6を使用するという状況で書いています。"," sライブラリなどはあえてバージョンを指定したりしていますので注意してください。",[352,2346,2348],{"id":2347},"jwtライブラリをインストール","JWTライブラリをインストール",[13,2350,2351],{},"Laravelには標準でJTWが入っていないのでライブラリをインストールします。",[24,2353,2356],{"className":2354,"code":2355,"language":29},[27],"composer require tymon\u002Fjwt-auth:1.0.0-rc.5\n",[31,2357,2355],{"__ignoreMap":33},[13,2359,2360,2363,2364,2367,2368,2373],{},[31,2361,2362],{},"tymon\u002Fjwt-auth"," というライブラリを入れますが、Laravel 6 の場合はバージョンに",[31,2365,2366],{},":1.0.0-rc.5","を指定しないとエラーになります。以下は",[725,2369,2372],{"href":2370,"rel":2371},"https:\u002F\u002Fjwt-auth.readthedocs.io\u002Fen\u002Fdevelop\u002F",[729],"このライブラリのドキュメント","通りの実装です。",[352,2375,2376],{"id":2376},"設定を一部変更",[13,2378,2379],{},"ライブラリのインストールをすればJWTは使えるようになります。しかし今回の構築では一部変更しなければならない箇所があったので、設定ファイルを生成します。",[24,2381,2384],{"className":2382,"code":2383,"language":29},[27],"php artisan vendor:publish --provider=\"Tymon\\JWTAuth\\Providers\\LaravelServiceProvider\"\n",[31,2385,2383],{"__ignoreMap":33},[13,2387,2388,2391],{},[31,2389,2390],{},"config\u002Fjwt.php","というファイルが生成されたはずです。このファイルにおいて以下の部分を書き換えます。",[24,2393,2396],{"className":2394,"code":2395,"language":882,"meta":33,"style":33},"language-php shiki shiki-themes material-theme-ocean","'providers' => [\n\n\u002F*\n|--------------------------------------------------------------------------\n| JWT Provider\n|--------------------------------------------------------------------------\n|\n| Specify the provider that is used to create and decode the tokens.\n|\n*\u002F\n\n\u002F\u002F 'jwt' => Tymon\\JWTAuth\\Providers\\JWT\\Lcobucci::class,\n'jwt' => Tymon\\JWTAuth\\Providers\\JWT\\Namshi::class,\n\n]\n",[31,2397,2398,2403,2407,2412,2417,2422,2426,2431,2436,2440,2445,2449,2454,2459,2463],{"__ignoreMap":33},[46,2399,2400],{"class":48,"line":49},[46,2401,2402],{},"'providers' => [\n",[46,2404,2405],{"class":48,"line":70},[46,2406,408],{"emptyLinePlaceholder":407},[46,2408,2409],{"class":48,"line":82},[46,2410,2411],{},"\u002F*\n",[46,2413,2414],{"class":48,"line":91},[46,2415,2416],{},"|--------------------------------------------------------------------------\n",[46,2418,2419],{"class":48,"line":102},[46,2420,2421],{},"| JWT Provider\n",[46,2423,2424],{"class":48,"line":112},[46,2425,2416],{},[46,2427,2428],{"class":48,"line":121},[46,2429,2430],{},"|\n",[46,2432,2433],{"class":48,"line":131},[46,2434,2435],{},"| Specify the provider that is used to create and decode the tokens.\n",[46,2437,2438],{"class":48,"line":139},[46,2439,2430],{},[46,2441,2442],{"class":48,"line":147},[46,2443,2444],{},"*\u002F\n",[46,2446,2447],{"class":48,"line":157},[46,2448,408],{"emptyLinePlaceholder":407},[46,2450,2451],{"class":48,"line":171},[46,2452,2453],{},"\u002F\u002F 'jwt' => Tymon\\JWTAuth\\Providers\\JWT\\Lcobucci::class,\n",[46,2455,2456],{"class":48,"line":183},[46,2457,2458],{},"'jwt' => Tymon\\JWTAuth\\Providers\\JWT\\Namshi::class,\n",[46,2460,2461],{"class":48,"line":195},[46,2462,408],{"emptyLinePlaceholder":407},[46,2464,2465],{"class":48,"line":206},[46,2466,2467],{},"]\n",[13,2469,2470,2471,2474],{},"JWTのプロバイダーを",[31,2472,2473],{},"Tymon\\JWTAuth\\Providers\\JWT\\Namshi::class","に変更します。というのも標準の設定でいくと",[24,2476,2479],{"className":2477,"code":2478,"language":29},[27],"Tymon\\JWTAuth\\Exceptions\\JWTException: Could not create token: Implicit conversion of keys from strings is deprecated. Please use InMemory or LocalFileReference classes. \n",[31,2480,2478],{"__ignoreMap":33},[13,2482,2483],{},"というエラーが生じます。JWTトークンを生成する処理に非推奨な部分があるそうで、プロバイダを変更する必要がありました。参考記事にあるgithub issueがお世話になりました。",[352,2485,2486],{"id":2486},"シークレットを生成",[13,2488,2489],{},"JWTトークンを生成するためのシークレットを作成します。",[24,2491,2494],{"className":2492,"code":2493,"language":29},[27],"php artisan jwt:secret\n",[31,2495,2493],{"__ignoreMap":33},[13,2497,2498],{},"するとenvファイルの下の方に",[24,2500,2503],{"className":2501,"code":2502,"language":29},[27],"JWT_SECRET=ve1b******\n",[31,2504,2502],{"__ignoreMap":33},[13,2506,2507,2508,2510],{},"というものが生成されます。ちなみにこのシークレットは絶対に外部に漏れてはいけません。間違ってgithubとかに上げないように ",[31,2509,1596],{},"ファイルはgitignoreしましょう。",[352,2512,2514],{"id":2513},"userモデルにメソッドを追加","Userモデルにメソッドを追加",[24,2516,2519],{"className":2394,"code":2517,"filename":2518,"language":882,"meta":33,"style":33},"\u003C?php\n\nnamespace App;\n\nuse Illuminate\\Contracts\\Auth\\MustVerifyEmail;\nuse Illuminate\\Foundation\\Auth\\User as Authenticatable;\nuse Illuminate\\Notifications\\Notifiable;\nuse Tymon\\JWTAuth\\Contracts\\JWTSubject; \u002F\u002F追加\n\nclass User extends Authenticatable implements JWTSubject \u002F\u002F追加\n{\n    use Notifiable;\n\n    \u002F**\n     * The attributes that are mass assignable.\n     *\n     * @var array\n     *\u002F\n    protected $fillable = [\n        'name', 'email', 'password',\n    ];\n\n    \u002F**\n     * The attributes that should be hidden for arrays.\n     *\n     * @var array\n     *\u002F\n    protected $hidden = [\n        'password', 'remember_token',\n    ];\n\n    \u002F**\n     * The attributes that should be cast to native types.\n     *\n     * @var array\n     *\u002F\n    protected $casts = [\n        'email_verified_at' => 'datetime',\n    ];\n\n    public function getJWTIdentifier()　 \u002F\u002F追加\n    {\n        return $this->getKey();\n    }\n\n    public function getJWTCustomClaims()　 \u002F\u002F追加\n    {\n        return [];\n    }\n}\n","app\u002FUser.php",[31,2520,2521,2526,2530,2535,2539,2544,2549,2554,2559,2563,2568,2573,2578,2582,2587,2592,2597,2602,2607,2612,2617,2622,2626,2630,2635,2639,2643,2647,2652,2657,2661,2665,2669,2674,2678,2682,2686,2691,2696,2700,2704,2709,2714,2719,2724,2728,2733,2737,2742,2747],{"__ignoreMap":33},[46,2522,2523],{"class":48,"line":49},[46,2524,2525],{},"\u003C?php\n",[46,2527,2528],{"class":48,"line":70},[46,2529,408],{"emptyLinePlaceholder":407},[46,2531,2532],{"class":48,"line":82},[46,2533,2534],{},"namespace App;\n",[46,2536,2537],{"class":48,"line":91},[46,2538,408],{"emptyLinePlaceholder":407},[46,2540,2541],{"class":48,"line":102},[46,2542,2543],{},"use Illuminate\\Contracts\\Auth\\MustVerifyEmail;\n",[46,2545,2546],{"class":48,"line":112},[46,2547,2548],{},"use Illuminate\\Foundation\\Auth\\User as Authenticatable;\n",[46,2550,2551],{"class":48,"line":121},[46,2552,2553],{},"use Illuminate\\Notifications\\Notifiable;\n",[46,2555,2556],{"class":48,"line":131},[46,2557,2558],{},"use Tymon\\JWTAuth\\Contracts\\JWTSubject; \u002F\u002F追加\n",[46,2560,2561],{"class":48,"line":139},[46,2562,408],{"emptyLinePlaceholder":407},[46,2564,2565],{"class":48,"line":147},[46,2566,2567],{},"class User extends Authenticatable implements JWTSubject \u002F\u002F追加\n",[46,2569,2570],{"class":48,"line":157},[46,2571,2572],{},"{\n",[46,2574,2575],{"class":48,"line":171},[46,2576,2577],{},"    use Notifiable;\n",[46,2579,2580],{"class":48,"line":183},[46,2581,408],{"emptyLinePlaceholder":407},[46,2583,2584],{"class":48,"line":195},[46,2585,2586],{},"    \u002F**\n",[46,2588,2589],{"class":48,"line":206},[46,2590,2591],{},"     * The attributes that are mass assignable.\n",[46,2593,2594],{"class":48,"line":4},[46,2595,2596],{},"     *\n",[46,2598,2599],{"class":48,"line":223},[46,2600,2601],{},"     * @var array\n",[46,2603,2604],{"class":48,"line":231},[46,2605,2606],{},"     *\u002F\n",[46,2608,2609],{"class":48,"line":242},[46,2610,2611],{},"    protected $fillable = [\n",[46,2613,2614],{"class":48,"line":253},[46,2615,2616],{},"        'name', 'email', 'password',\n",[46,2618,2619],{"class":48,"line":264},[46,2620,2621],{},"    ];\n",[46,2623,2624],{"class":48,"line":275},[46,2625,408],{"emptyLinePlaceholder":407},[46,2627,2628],{"class":48,"line":284},[46,2629,2586],{},[46,2631,2632],{"class":48,"line":296},[46,2633,2634],{},"     * The attributes that should be hidden for arrays.\n",[46,2636,2637],{"class":48,"line":305},[46,2638,2596],{},[46,2640,2641],{"class":48,"line":313},[46,2642,2601],{},[46,2644,2645],{"class":48,"line":323},[46,2646,2606],{},[46,2648,2649],{"class":48,"line":529},[46,2650,2651],{},"    protected $hidden = [\n",[46,2653,2654],{"class":48,"line":535},[46,2655,2656],{},"        'password', 'remember_token',\n",[46,2658,2659],{"class":48,"line":540},[46,2660,2621],{},[46,2662,2663],{"class":48,"line":546},[46,2664,408],{"emptyLinePlaceholder":407},[46,2666,2667],{"class":48,"line":551},[46,2668,2586],{},[46,2670,2671],{"class":48,"line":557},[46,2672,2673],{},"     * The attributes that should be cast to native types.\n",[46,2675,2676],{"class":48,"line":562},[46,2677,2596],{},[46,2679,2680],{"class":48,"line":568},[46,2681,2601],{},[46,2683,2684],{"class":48,"line":573},[46,2685,2606],{},[46,2687,2688],{"class":48,"line":579},[46,2689,2690],{},"    protected $casts = [\n",[46,2692,2693],{"class":48,"line":584},[46,2694,2695],{},"        'email_verified_at' => 'datetime',\n",[46,2697,2698],{"class":48,"line":590},[46,2699,2621],{},[46,2701,2702],{"class":48,"line":595},[46,2703,408],{"emptyLinePlaceholder":407},[46,2705,2706],{"class":48,"line":601},[46,2707,2708],{},"    public function getJWTIdentifier()　 \u002F\u002F追加\n",[46,2710,2711],{"class":48,"line":606},[46,2712,2713],{},"    {\n",[46,2715,2716],{"class":48,"line":611},[46,2717,2718],{},"        return $this->getKey();\n",[46,2720,2721],{"class":48,"line":616},[46,2722,2723],{},"    }\n",[46,2725,2726],{"class":48,"line":622},[46,2727,408],{"emptyLinePlaceholder":407},[46,2729,2730],{"class":48,"line":627},[46,2731,2732],{},"    public function getJWTCustomClaims()　 \u002F\u002F追加\n",[46,2734,2735],{"class":48,"line":633},[46,2736,2713],{},[46,2738,2739],{"class":48,"line":638},[46,2740,2741],{},"        return [];\n",[46,2743,2745],{"class":48,"line":2744},49,[46,2746,2723],{},[46,2748,2750],{"class":48,"line":2749},50,[46,2751,2752],{},"}\n",[352,2754,2756],{"id":2755},"authphpの認証ドライバを変更","auth.phpの認証ドライバを変更",[13,2758,2759,2762,2763,2766],{},[31,2760,2761],{},"config\u002Fauth.php","にて",[31,2764,2765],{},"Authentication","認証ドライバを変更します。",[24,2768,2770],{"className":2394,"code":2769,"filename":2761,"language":882,"meta":33,"style":33},"    \u002F*\n    |--------------------------------------------------------------------------\n    | Authentication Defaults\n    |--------------------------------------------------------------------------\n    |\n    | This option controls the default authentication \"guard\" and password\n    | reset options for your application. You may change these defaults\n    | as required, but they're a perfect start for most applications.\n    |\n    *\u002F\n\n    'defaults' => [\n        'guard' => 'api', \u002F\u002F変更\n        'passwords' => 'users',\n    ],\n...\n    'guards' => [\n        'web' => [\n            'driver' => 'session',\n            'provider' => 'users',\n        ],\n\n        'api' => [\n            'driver' => 'jwt', \u002F\u002F変更\n            'provider' => 'users',\n            'hash' => false,\n",[31,2771,2772,2777,2782,2787,2791,2796,2801,2806,2811,2815,2820,2824,2829,2837,2842,2847,2852,2857,2862,2867,2872,2877,2881,2886,2893,2897],{"__ignoreMap":33},[46,2773,2774],{"class":48,"line":49},[46,2775,2776],{},"    \u002F*\n",[46,2778,2779],{"class":48,"line":70},[46,2780,2781],{},"    |--------------------------------------------------------------------------\n",[46,2783,2784],{"class":48,"line":82},[46,2785,2786],{},"    | Authentication Defaults\n",[46,2788,2789],{"class":48,"line":91},[46,2790,2781],{},[46,2792,2793],{"class":48,"line":102},[46,2794,2795],{},"    |\n",[46,2797,2798],{"class":48,"line":112},[46,2799,2800],{},"    | This option controls the default authentication \"guard\" and password\n",[46,2802,2803],{"class":48,"line":121},[46,2804,2805],{},"    | reset options for your application. You may change these defaults\n",[46,2807,2808],{"class":48,"line":131},[46,2809,2810],{},"    | as required, but they're a perfect start for most applications.\n",[46,2812,2813],{"class":48,"line":139},[46,2814,2795],{},[46,2816,2817],{"class":48,"line":147},[46,2818,2819],{},"    *\u002F\n",[46,2821,2822],{"class":48,"line":157},[46,2823,408],{"emptyLinePlaceholder":407},[46,2825,2826],{"class":48,"line":171},[46,2827,2828],{},"    'defaults' => [\n",[46,2830,2831,2834],{"class":48,"line":183},[46,2832,2833],{},"        'guard' => 'api',",[46,2835,2836],{}," \u002F\u002F変更\n",[46,2838,2839],{"class":48,"line":195},[46,2840,2841],{},"        'passwords' => 'users',\n",[46,2843,2844],{"class":48,"line":206},[46,2845,2846],{},"    ],\n",[46,2848,2849],{"class":48,"line":4},[46,2850,2851],{},"...\n",[46,2853,2854],{"class":48,"line":223},[46,2855,2856],{},"    'guards' => [\n",[46,2858,2859],{"class":48,"line":231},[46,2860,2861],{},"        'web' => [\n",[46,2863,2864],{"class":48,"line":242},[46,2865,2866],{},"            'driver' => 'session',\n",[46,2868,2869],{"class":48,"line":253},[46,2870,2871],{},"            'provider' => 'users',\n",[46,2873,2874],{"class":48,"line":264},[46,2875,2876],{},"        ],\n",[46,2878,2879],{"class":48,"line":275},[46,2880,408],{"emptyLinePlaceholder":407},[46,2882,2883],{"class":48,"line":284},[46,2884,2885],{},"        'api' => [\n",[46,2887,2888,2891],{"class":48,"line":296},[46,2889,2890],{},"            'driver' => 'jwt',",[46,2892,2836],{},[46,2894,2895],{"class":48,"line":305},[46,2896,2871],{},[46,2898,2899],{"class":48,"line":313},[46,2900,2901],{},"            'hash' => false,\n",[13,2903,2904],{},"configはキャッシュされていますので、configをいじった後はキャッシュクリアをしましょう。",[24,2906,2909],{"className":2907,"code":2908,"language":29},[27],"php artisan config:clear\n",[31,2910,2908],{"__ignoreMap":33},[352,2912,2914],{"id":2913},"jwt認証用のルートを作成","JWT認証用のルートを作成",[13,2916,2917,2918,2921],{},"認証のルートを作成します。",[31,2919,2920],{},"route\u002Fapi.php","に以下のように記述します。",[24,2923,2925],{"className":2394,"code":2924,"filename":2920,"language":882,"meta":33,"style":33},"Route::prefix('v1')->group(function(){\n    Route::group(['middleware' => 'api', 'prefix' => 'auth'], function ($router) {\n        Route::post('login', 'AuthController@login')->name('login');;\n        Route::post('logout', 'AuthController@logout');\n        Route::post('refresh', 'AuthController@refresh');\n        Route::get('me', 'AuthController@me');\n    });\n});\n",[31,2926,2927,2932,2937,2942,2947,2952,2957,2962],{"__ignoreMap":33},[46,2928,2929],{"class":48,"line":49},[46,2930,2931],{},"Route::prefix('v1')->group(function(){\n",[46,2933,2934],{"class":48,"line":70},[46,2935,2936],{},"    Route::group(['middleware' => 'api', 'prefix' => 'auth'], function ($router) {\n",[46,2938,2939],{"class":48,"line":82},[46,2940,2941],{},"        Route::post('login', 'AuthController@login')->name('login');;\n",[46,2943,2944],{"class":48,"line":91},[46,2945,2946],{},"        Route::post('logout', 'AuthController@logout');\n",[46,2948,2949],{"class":48,"line":102},[46,2950,2951],{},"        Route::post('refresh', 'AuthController@refresh');\n",[46,2953,2954],{"class":48,"line":112},[46,2955,2956],{},"        Route::get('me', 'AuthController@me');\n",[46,2958,2959],{"class":48,"line":121},[46,2960,2961],{},"    });\n",[46,2963,2964],{"class":48,"line":131},[46,2965,2966],{},"});\n",[13,2968,2969],{},"ルートの設定は運用によって変わると思いますが、APIはバージョンで予め分けておくと将来の拡張性が高まります。一回ぽっきりのサービスでも実装して損はないと思います。上記のルートでは以下のような設定になります。",[24,2971,2974],{"className":2972,"code":2973,"language":29},[27],"$ php artisan route:list\n+--------+----------+---------------------+-------+---------------------------------------------+--------------+\n| Domain | Method   | URI                 | Name  | Action                                      | Middleware   |\n+--------+----------+---------------------+-------+---------------------------------------------+--------------+\n|        | POST     | api\u002Fv1\u002Fauth\u002Flogin   | login | App\\Http\\Controllers\\AuthController@login   | api          |\n|        | POST     | api\u002Fv1\u002Fauth\u002Flogout  |       | App\\Http\\Controllers\\AuthController@logout  | api,auth:api |\n|        | GET|HEAD | api\u002Fv1\u002Fauth\u002Fme      |       | App\\Http\\Controllers\\AuthController@me      | api,auth:api |\n|        | POST     | api\u002Fv1\u002Fauth\u002Frefresh |       | App\\Http\\Controllers\\AuthController@refresh | api,auth:api |\n+--------+----------+---------------------+-------+---------------------------------------------+--------------+\n",[31,2975,2973],{"__ignoreMap":33},[352,2977,2978],{"id":2978},"認証ルートに対するコントローラを作成",[13,2980,2981,2982,2985],{},"それぞれのルートは",[31,2983,2984],{},"AuthController.php","につなげる予定ですので、そのコントローラーを作成します。",[24,2987,2990],{"className":2988,"code":2989,"language":29},[27],"php artisan make:controller AuthController\n",[31,2991,2989],{"__ignoreMap":33},[13,2993,2994,2996],{},[31,2995,2984],{},"は以下のような設定になります。",[24,2998,3001],{"className":2394,"code":2999,"filename":3000,"language":882,"meta":33,"style":33},"\u003C?php\n\nnamespace App\\Http\\Controllers;\n\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Support\\Facades\\Auth;\n\nclass AuthController extends Controller\n{\n    \u002F**\n     * Create a new AuthController instance.\n     *\n     * @return void\n     *\u002F\n    public function __construct()\n    {\n        $this->middleware('auth:api', ['except' => ['login']]);\n    }\n\n    \u002F**\n     * Get a JWT via given credentials.\n     *\n     * @return \\Illuminate\\Http\\JsonResponse\n     *\u002F\n    public function login()\n    {\n        $credentials = request(['email', 'password']);\n\n        if (! $token = auth()->attempt($credentials)) {\n            return response()->json(['error' => 'Unauthorized'], 401);\n        }\n\n        return $this->respondWithToken($token);\n    }\n\n    \u002F**\n     * Get the authenticated User.\n     *\n     * @return \\Illuminate\\Http\\JsonResponse\n     *\u002F\n    public function me()\n    {\n        return response()->json(auth()->user());\n    }\n\n    \u002F**\n     * Log the user out (Invalidate the token).\n     *\n     * @return \\Illuminate\\Http\\JsonResponse\n     *\u002F\n    public function logout()\n    {\n        auth()->logout();\n\n        return response()->json(['message' => 'Successfully logged out']);\n    }\n\n    \u002F**\n     * Refresh a token.\n     *\n     * @return \\Illuminate\\Http\\JsonResponse\n     *\u002F\n    public function refresh()\n    {\n        return $this->respondWithToken(auth()->refresh());\n    }\n\n    \u002F**\n     * Get the token array structure.\n     *\n     * @param  string $token\n     *\n     * @return \\Illuminate\\Http\\JsonResponse\n     *\u002F\n    protected function respondWithToken($token)\n    {\n        return response()->json([\n            'access_token' => $token,\n            'token_type' => 'bearer',\n            'expires_in' => auth()->factory()->getTTL() * 60\n        ]);\n    }\n}\n","app\u002FHttp\u002FControllers\u002FAuthController.php",[31,3002,3003,3007,3011,3016,3020,3025,3030,3034,3039,3043,3047,3052,3056,3061,3065,3070,3074,3079,3083,3087,3091,3096,3100,3105,3109,3114,3118,3123,3127,3132,3137,3142,3146,3151,3155,3159,3163,3168,3172,3176,3180,3185,3189,3194,3198,3202,3206,3211,3215,3219,3223,3229,3234,3240,3245,3251,3256,3261,3266,3272,3277,3282,3287,3293,3298,3304,3309,3314,3319,3325,3330,3336,3341,3346,3351,3357,3362,3368,3374,3380,3386,3392,3397],{"__ignoreMap":33},[46,3004,3005],{"class":48,"line":49},[46,3006,2525],{},[46,3008,3009],{"class":48,"line":70},[46,3010,408],{"emptyLinePlaceholder":407},[46,3012,3013],{"class":48,"line":82},[46,3014,3015],{},"namespace App\\Http\\Controllers;\n",[46,3017,3018],{"class":48,"line":91},[46,3019,408],{"emptyLinePlaceholder":407},[46,3021,3022],{"class":48,"line":102},[46,3023,3024],{},"use Illuminate\\Http\\Request;\n",[46,3026,3027],{"class":48,"line":112},[46,3028,3029],{},"use Illuminate\\Support\\Facades\\Auth;\n",[46,3031,3032],{"class":48,"line":121},[46,3033,408],{"emptyLinePlaceholder":407},[46,3035,3036],{"class":48,"line":131},[46,3037,3038],{},"class AuthController extends Controller\n",[46,3040,3041],{"class":48,"line":139},[46,3042,2572],{},[46,3044,3045],{"class":48,"line":147},[46,3046,2586],{},[46,3048,3049],{"class":48,"line":157},[46,3050,3051],{},"     * Create a new AuthController instance.\n",[46,3053,3054],{"class":48,"line":171},[46,3055,2596],{},[46,3057,3058],{"class":48,"line":183},[46,3059,3060],{},"     * @return void\n",[46,3062,3063],{"class":48,"line":195},[46,3064,2606],{},[46,3066,3067],{"class":48,"line":206},[46,3068,3069],{},"    public function __construct()\n",[46,3071,3072],{"class":48,"line":4},[46,3073,2713],{},[46,3075,3076],{"class":48,"line":223},[46,3077,3078],{},"        $this->middleware('auth:api', ['except' => ['login']]);\n",[46,3080,3081],{"class":48,"line":231},[46,3082,2723],{},[46,3084,3085],{"class":48,"line":242},[46,3086,408],{"emptyLinePlaceholder":407},[46,3088,3089],{"class":48,"line":253},[46,3090,2586],{},[46,3092,3093],{"class":48,"line":264},[46,3094,3095],{},"     * Get a JWT via given credentials.\n",[46,3097,3098],{"class":48,"line":275},[46,3099,2596],{},[46,3101,3102],{"class":48,"line":284},[46,3103,3104],{},"     * @return \\Illuminate\\Http\\JsonResponse\n",[46,3106,3107],{"class":48,"line":296},[46,3108,2606],{},[46,3110,3111],{"class":48,"line":305},[46,3112,3113],{},"    public function login()\n",[46,3115,3116],{"class":48,"line":313},[46,3117,2713],{},[46,3119,3120],{"class":48,"line":323},[46,3121,3122],{},"        $credentials = request(['email', 'password']);\n",[46,3124,3125],{"class":48,"line":529},[46,3126,408],{"emptyLinePlaceholder":407},[46,3128,3129],{"class":48,"line":535},[46,3130,3131],{},"        if (! $token = auth()->attempt($credentials)) {\n",[46,3133,3134],{"class":48,"line":540},[46,3135,3136],{},"            return response()->json(['error' => 'Unauthorized'], 401);\n",[46,3138,3139],{"class":48,"line":546},[46,3140,3141],{},"        }\n",[46,3143,3144],{"class":48,"line":551},[46,3145,408],{"emptyLinePlaceholder":407},[46,3147,3148],{"class":48,"line":557},[46,3149,3150],{},"        return $this->respondWithToken($token);\n",[46,3152,3153],{"class":48,"line":562},[46,3154,2723],{},[46,3156,3157],{"class":48,"line":568},[46,3158,408],{"emptyLinePlaceholder":407},[46,3160,3161],{"class":48,"line":573},[46,3162,2586],{},[46,3164,3165],{"class":48,"line":579},[46,3166,3167],{},"     * Get the authenticated User.\n",[46,3169,3170],{"class":48,"line":584},[46,3171,2596],{},[46,3173,3174],{"class":48,"line":590},[46,3175,3104],{},[46,3177,3178],{"class":48,"line":595},[46,3179,2606],{},[46,3181,3182],{"class":48,"line":601},[46,3183,3184],{},"    public function me()\n",[46,3186,3187],{"class":48,"line":606},[46,3188,2713],{},[46,3190,3191],{"class":48,"line":611},[46,3192,3193],{},"        return response()->json(auth()->user());\n",[46,3195,3196],{"class":48,"line":616},[46,3197,2723],{},[46,3199,3200],{"class":48,"line":622},[46,3201,408],{"emptyLinePlaceholder":407},[46,3203,3204],{"class":48,"line":627},[46,3205,2586],{},[46,3207,3208],{"class":48,"line":633},[46,3209,3210],{},"     * Log the user out (Invalidate the token).\n",[46,3212,3213],{"class":48,"line":638},[46,3214,2596],{},[46,3216,3217],{"class":48,"line":2744},[46,3218,3104],{},[46,3220,3221],{"class":48,"line":2749},[46,3222,2606],{},[46,3224,3226],{"class":48,"line":3225},51,[46,3227,3228],{},"    public function logout()\n",[46,3230,3232],{"class":48,"line":3231},52,[46,3233,2713],{},[46,3235,3237],{"class":48,"line":3236},53,[46,3238,3239],{},"        auth()->logout();\n",[46,3241,3243],{"class":48,"line":3242},54,[46,3244,408],{"emptyLinePlaceholder":407},[46,3246,3248],{"class":48,"line":3247},55,[46,3249,3250],{},"        return response()->json(['message' => 'Successfully logged out']);\n",[46,3252,3254],{"class":48,"line":3253},56,[46,3255,2723],{},[46,3257,3259],{"class":48,"line":3258},57,[46,3260,408],{"emptyLinePlaceholder":407},[46,3262,3264],{"class":48,"line":3263},58,[46,3265,2586],{},[46,3267,3269],{"class":48,"line":3268},59,[46,3270,3271],{},"     * Refresh a token.\n",[46,3273,3275],{"class":48,"line":3274},60,[46,3276,2596],{},[46,3278,3280],{"class":48,"line":3279},61,[46,3281,3104],{},[46,3283,3285],{"class":48,"line":3284},62,[46,3286,2606],{},[46,3288,3290],{"class":48,"line":3289},63,[46,3291,3292],{},"    public function refresh()\n",[46,3294,3296],{"class":48,"line":3295},64,[46,3297,2713],{},[46,3299,3301],{"class":48,"line":3300},65,[46,3302,3303],{},"        return $this->respondWithToken(auth()->refresh());\n",[46,3305,3307],{"class":48,"line":3306},66,[46,3308,2723],{},[46,3310,3312],{"class":48,"line":3311},67,[46,3313,408],{"emptyLinePlaceholder":407},[46,3315,3317],{"class":48,"line":3316},68,[46,3318,2586],{},[46,3320,3322],{"class":48,"line":3321},69,[46,3323,3324],{},"     * Get the token array structure.\n",[46,3326,3328],{"class":48,"line":3327},70,[46,3329,2596],{},[46,3331,3333],{"class":48,"line":3332},71,[46,3334,3335],{},"     * @param  string $token\n",[46,3337,3339],{"class":48,"line":3338},72,[46,3340,2596],{},[46,3342,3344],{"class":48,"line":3343},73,[46,3345,3104],{},[46,3347,3349],{"class":48,"line":3348},74,[46,3350,2606],{},[46,3352,3354],{"class":48,"line":3353},75,[46,3355,3356],{},"    protected function respondWithToken($token)\n",[46,3358,3360],{"class":48,"line":3359},76,[46,3361,2713],{},[46,3363,3365],{"class":48,"line":3364},77,[46,3366,3367],{},"        return response()->json([\n",[46,3369,3371],{"class":48,"line":3370},78,[46,3372,3373],{},"            'access_token' => $token,\n",[46,3375,3377],{"class":48,"line":3376},79,[46,3378,3379],{},"            'token_type' => 'bearer',\n",[46,3381,3383],{"class":48,"line":3382},80,[46,3384,3385],{},"            'expires_in' => auth()->factory()->getTTL() * 60\n",[46,3387,3389],{"class":48,"line":3388},81,[46,3390,3391],{},"        ]);\n",[46,3393,3395],{"class":48,"line":3394},82,[46,3396,2723],{},[46,3398,3400],{"class":48,"line":3399},83,[46,3401,2752],{},[13,3403,3404],{},"これでJWT認証に必要なLarvel側の設定が終了しました。",[20,3406,3408],{"id":3407},"talend-api-testerでapiをチェック","Talend API TesterでAPIをチェック",[13,3410,3411,3412,3417],{},"きちんとAPIが存在しているかを確かめるために、chrome拡張の",[725,3413,3416],{"href":3414,"rel":3415},"https:\u002F\u002Fchrome.google.com\u002Fwebstore\u002Fdetail\u002Ftalend-api-tester-free-ed\u002Faejoelaoggembcahagimdiliamlcdmfm",[729],"Talend API Tester","を用いてチェックします。予めシーダーで作成した仮ユーザーデータを用いて以下のように入力してみます。",[1692,3419],{":src":3420,":width":1695},"'_mix\u002Fsch-2020-12-05-14.17.54-768x353.png'",[1692,3422],{":src":3423,":width":1695},"'_mix\u002Fsch-2020-12-05-14.20.21-768x318.png'",[13,3425,3426,3427,3430],{},"JSONでアクセストークン が帰ってきました。このアクセストークン をリクエストヘッダに入れてリクエストすることで認証が必要なルートにアクセスできるようになります。試しに ",[31,3428,3429],{},"\u002Fapi\u002Fv1\u002Fauth\u002Fme","という現在のユーザー情報を確かめるルートにアクセスしてみます。",[13,3432,3433],{},"HEADERSにAuthorizationというものを追加し、先ほどのトークンを入れています。トークン値は「bearer トークン」という形です。",[1692,3435],{":src":3436,":width":1695},"'_mix\u002Fsch-2020-12-05-14.23.34-768x224.png'",[13,3438,3439,3440,3443],{},"ユーザー情報が返ってきましたね。",[31,3441,3442],{},"Authcontroller@me","で定義した返り値がきちんとえられました。ちなみに認証情報がない場合はHTTP 401を返します。",[20,3445,3447],{"id":3446},"nuxtjsからapiを呼ぶ","Nuxt.jsからAPIを呼ぶ",[352,3449,3450],{"id":3450},"前準備",[13,3452,3453],{},"Nuxt.jsの構築に移る前にCORS対策をします。APIサーバーはブラウザからのアクセスを禁止して、XMLHttpRequest（XHR）のみのリクエストを許可するようにします。そしてXHRにはCORSという制約があります。これがあるとローカル以外からのXHRを用いたAPIアクセスが制限されます。特にNuxtを3000ポートで開発している時でもAPIサーバーと通信できるように準備しておきます。",[1337,3455,3457],{"id":3456},"cors設定のライブラリをインストール","CORS設定のライブラリをインストール",[24,3459,3462],{"className":3460,"code":3461,"language":29},[27],"composer require fruitcake\u002Flaravel-cors\n",[31,3463,3461],{"__ignoreMap":33},[1337,3465,3467],{"id":3466},"apphttpkernelphpのミドルウェア-に追加","app\u002FHttp\u002FKernel.phpのミドルウェア に追加",[24,3469,3472],{"className":2394,"code":3470,"filename":3471,"language":882,"meta":33,"style":33},"protected $middleware = [\n    \u002F\u002F ...\n    \\Fruitcake\\Cors\\HandleCors::class,\n];\n","app\u002FHttp\u002FKernel.php",[31,3473,3474,3479,3484,3489],{"__ignoreMap":33},[46,3475,3476],{"class":48,"line":49},[46,3477,3478],{},"protected $middleware = [\n",[46,3480,3481],{"class":48,"line":70},[46,3482,3483],{},"    \u002F\u002F ...\n",[46,3485,3486],{"class":48,"line":82},[46,3487,3488],{},"    \\Fruitcake\\Cors\\HandleCors::class,\n",[46,3490,3491],{"class":48,"line":91},[46,3492,3493],{},"];\n",[1337,3495,3497],{"id":3496},"cors設定ファイルを出力して一部変更","CORS設定ファイルを出力して一部変更",[24,3499,3502],{"className":3500,"code":3501,"language":29},[27],"php artisan vendor:publish --tag=\"cors\"\n",[31,3503,3501],{"__ignoreMap":33},[13,3505,3506,3507,3510,3511,3514,3515,3518,3519,3521],{},"先ほどの",[31,3508,3509],{},"jwt.php","のように",[31,3512,3513],{},"cors.php","が",[31,3516,3517],{},"config\u002F","配下に出現します。その",[31,3520,3513],{},"を以下のように変更します。",[24,3523,3526],{"className":2394,"code":3524,"filename":3525,"language":882,"meta":33,"style":33},"...\n    \u002F*\n     * You can enable CORS for 1 or multiple paths.\n     * Example: ['api\u002F*']\n     *\u002F\n    'paths' => ['api\u002F*'],\n...\n    \u002F*\n     * Matches the request origin. `['*']` allows all origins. Wildcards can be used, eg `*.mydomain.com`\n     *\u002F\n    'allowed_origins' => [\n          'http:\u002F\u002Flocalhost',\n          'http:\u002F\u002Flocalhost:3000',\u002F\u002F nuxt\n          'http:\u002F\u002Flocalhost:9000' \u002F\u002F dockerの9000->80\n        ],\n\n...\n","confog\u002Fcors.php",[31,3527,3528,3532,3536,3541,3546,3550,3555,3559,3563,3568,3572,3577,3582,3587,3595,3599,3603],{"__ignoreMap":33},[46,3529,3530],{"class":48,"line":49},[46,3531,2851],{},[46,3533,3534],{"class":48,"line":70},[46,3535,2776],{},[46,3537,3538],{"class":48,"line":82},[46,3539,3540],{},"     * You can enable CORS for 1 or multiple paths.\n",[46,3542,3543],{"class":48,"line":91},[46,3544,3545],{},"     * Example: ['api\u002F*']\n",[46,3547,3548],{"class":48,"line":102},[46,3549,2606],{},[46,3551,3552],{"class":48,"line":112},[46,3553,3554],{},"    'paths' => ['api\u002F*'],\n",[46,3556,3557],{"class":48,"line":121},[46,3558,2851],{},[46,3560,3561],{"class":48,"line":131},[46,3562,2776],{},[46,3564,3565],{"class":48,"line":139},[46,3566,3567],{},"     * Matches the request origin. `['*']` allows all origins. Wildcards can be used, eg `*.mydomain.com`\n",[46,3569,3570],{"class":48,"line":147},[46,3571,2606],{},[46,3573,3574],{"class":48,"line":157},[46,3575,3576],{},"    'allowed_origins' => [\n",[46,3578,3579],{"class":48,"line":171},[46,3580,3581],{},"          'http:\u002F\u002Flocalhost',\n",[46,3583,3584],{"class":48,"line":183},[46,3585,3586],{},"          'http:\u002F\u002Flocalhost:3000',\u002F\u002F nuxt\n",[46,3588,3589,3592],{"class":48,"line":195},[46,3590,3591],{},"          'http:\u002F\u002Flocalhost:9000'",[46,3593,3594],{}," \u002F\u002F dockerの9000->80\n",[46,3596,3597],{"class":48,"line":206},[46,3598,2876],{},[46,3600,3601],{"class":48,"line":4},[46,3602,408],{"emptyLinePlaceholder":407},[46,3604,3605],{"class":48,"line":223},[46,3606,2851],{},[13,3608,3609,3612,3613,3615,3616,3619],{},[31,3610,3611],{},"api\u002F"," ルート配下に対してcors設定を行い、そして",[31,3614,1447],{},"及び",[31,3617,3618],{},"3000ポート","からの接続を許可しました。",[352,3621,3623],{"id":3622},"nuxt-auth-モジュールを用いて認証系の機能を整える","nuxt auth モジュールを用いて認証系の機能を整える",[13,3625,3626,3627,3632],{},"nuxt.jsの構築は飛ばします。私の環境では nuxt+bootstrap+axiosで始めます。認証系をpluginで構築してもいいのですが、なかなか大変ですのでnuxt authモジュールを使用します。このモジュールがあれば認証つきのNuxtアプリを簡単に作成できます。",[725,3628,3631],{"href":3629,"rel":3630},"https:\u002F\u002Fdev.auth.nuxtjs.org\u002F",[729],"本家のドキュメント","に習いながら進めましょう。",[1337,3634,3635],{"id":3635},"モジュールのインストール",[13,3637,3638],{},"nuxt.jsのプロジェクトルートに移動いして以下のモジュールをインストールします。",[24,3640,3643],{"className":3641,"code":3642,"language":29},[27],"npm install @nuxtjs\u002Fauth-next @nuxtjs\u002Faxios\n",[31,3644,3642],{"__ignoreMap":33},[1337,3646,3648],{"id":3647},"nuxtconfigjsでの設定","nuxt.config.jsでの設定",[13,3650,3651,3653],{},[31,3652,1767],{},"でまずモジュールの読み込みをします。",[24,3655,3658],{"className":3656,"code":3657,"language":29},[27],"...javascript[nuxt.config.js]\n\u002F\u002F Modules (https:\u002F\u002Fgo.nuxtjs.dev\u002Fconfig-modules)\n  modules: [\n    '@nuxtjs\u002Faxios',\n    '@nuxtjs\u002Fauth'\n  ],\n...\n",[31,3659,3657],{"__ignoreMap":33},[13,3661,3662],{},"これでauthモジュールとaxiosモジュールが使えます。authモジュールはstoreを使用しますので、storeにindex.jsがない場合は空でもいいので作っていきます。",[13,3664,3665,3666,3668],{},"そしてauthモジュールの設定を",[31,3667,1767],{},"で行います。",[24,3670,3674],{"className":3671,"code":3672,"filename":1767,"language":3673,"meta":33,"style":33},"language-javascript shiki shiki-themes material-theme-ocean","...\nauth:{\n    localStorage: false,\n    strategies:{\n      local:{\n        tokenType:'bearer',\n        endpoints:{\n          login:{\n            url:'\u002Fauth\u002Flogin',\n            method:'post',\n            propertyName:'access_token'\n          },\n          logout:{\n            url:'\u002Fauth\u002Flogout',\n            method:'post',\n          },\n          user:{\n            url:'\u002Fauth\u002Fme',\n            method:'get',\n            propertyName:false\n          }\n        }\n      },\n      redirect: {\n        login: '\u002Flogin',\n        logout: '\u002F',\n        callback: '\u002Flogin',\n        home: '\u002Fhome'\n      }\n}\n...\n","javascript",[31,3675,3676,3680,3689,3702,3709,3716,3733,3740,3747,3763,3779,3793,3798,3805,3820,3834,3838,3845,3860,3875,3884,3889,3893,3898,3908,3924,3940,3955,3969,3974,3978],{"__ignoreMap":33},[46,3677,3678],{"class":48,"line":49},[46,3679,2851],{"class":56},[46,3681,3682,3686],{"class":48,"line":70},[46,3683,3685],{"class":3684},"s5Dmg","auth",[46,3687,3688],{"class":56},":{\n",[46,3690,3691,3694,3696,3699],{"class":48,"line":82},[46,3692,3693],{"class":3684},"    localStorage",[46,3695,57],{"class":56},[46,3697,3698],{"class":191}," false",[46,3700,3701],{"class":56},",\n",[46,3703,3704,3707],{"class":48,"line":91},[46,3705,3706],{"class":3684},"    strategies",[46,3708,3688],{"class":56},[46,3710,3711,3714],{"class":48,"line":102},[46,3712,3713],{"class":3684},"      local",[46,3715,3688],{"class":56},[46,3717,3718,3721,3723,3726,3729,3731],{"class":48,"line":112},[46,3719,3720],{"class":3684},"        tokenType",[46,3722,57],{"class":56},[46,3724,3725],{"class":56},"'",[46,3727,3728],{"class":63},"bearer",[46,3730,3725],{"class":56},[46,3732,3701],{"class":56},[46,3734,3735,3738],{"class":48,"line":121},[46,3736,3737],{"class":3684},"        endpoints",[46,3739,3688],{"class":56},[46,3741,3742,3745],{"class":48,"line":131},[46,3743,3744],{"class":3684},"          login",[46,3746,3688],{"class":56},[46,3748,3749,3752,3754,3756,3759,3761],{"class":48,"line":139},[46,3750,3751],{"class":3684},"            url",[46,3753,57],{"class":56},[46,3755,3725],{"class":56},[46,3757,3758],{"class":63},"\u002Fauth\u002Flogin",[46,3760,3725],{"class":56},[46,3762,3701],{"class":56},[46,3764,3765,3768,3770,3772,3775,3777],{"class":48,"line":147},[46,3766,3767],{"class":3684},"            method",[46,3769,57],{"class":56},[46,3771,3725],{"class":56},[46,3773,3774],{"class":63},"post",[46,3776,3725],{"class":56},[46,3778,3701],{"class":56},[46,3780,3781,3784,3786,3788,3791],{"class":48,"line":157},[46,3782,3783],{"class":3684},"            propertyName",[46,3785,57],{"class":56},[46,3787,3725],{"class":56},[46,3789,3790],{"class":63},"access_token",[46,3792,67],{"class":56},[46,3794,3795],{"class":48,"line":171},[46,3796,3797],{"class":56},"          },\n",[46,3799,3800,3803],{"class":48,"line":183},[46,3801,3802],{"class":3684},"          logout",[46,3804,3688],{"class":56},[46,3806,3807,3809,3811,3813,3816,3818],{"class":48,"line":195},[46,3808,3751],{"class":3684},[46,3810,57],{"class":56},[46,3812,3725],{"class":56},[46,3814,3815],{"class":63},"\u002Fauth\u002Flogout",[46,3817,3725],{"class":56},[46,3819,3701],{"class":56},[46,3821,3822,3824,3826,3828,3830,3832],{"class":48,"line":206},[46,3823,3767],{"class":3684},[46,3825,57],{"class":56},[46,3827,3725],{"class":56},[46,3829,3774],{"class":63},[46,3831,3725],{"class":56},[46,3833,3701],{"class":56},[46,3835,3836],{"class":48,"line":4},[46,3837,3797],{"class":56},[46,3839,3840,3843],{"class":48,"line":223},[46,3841,3842],{"class":3684},"          user",[46,3844,3688],{"class":56},[46,3846,3847,3849,3851,3853,3856,3858],{"class":48,"line":231},[46,3848,3751],{"class":3684},[46,3850,57],{"class":56},[46,3852,3725],{"class":56},[46,3854,3855],{"class":63},"\u002Fauth\u002Fme",[46,3857,3725],{"class":56},[46,3859,3701],{"class":56},[46,3861,3862,3864,3866,3868,3871,3873],{"class":48,"line":242},[46,3863,3767],{"class":3684},[46,3865,57],{"class":56},[46,3867,3725],{"class":56},[46,3869,3870],{"class":63},"get",[46,3872,3725],{"class":56},[46,3874,3701],{"class":56},[46,3876,3877,3879,3881],{"class":48,"line":253},[46,3878,3783],{"class":3684},[46,3880,57],{"class":56},[46,3882,3883],{"class":191},"false\n",[46,3885,3886],{"class":48,"line":264},[46,3887,3888],{"class":56},"          }\n",[46,3890,3891],{"class":48,"line":275},[46,3892,3141],{"class":56},[46,3894,3895],{"class":48,"line":284},[46,3896,3897],{"class":56},"      },\n",[46,3899,3900,3903,3905],{"class":48,"line":296},[46,3901,3902],{"class":3684},"      redirect",[46,3904,57],{"class":56},[46,3906,3907],{"class":56}," {\n",[46,3909,3910,3913,3915,3917,3920,3922],{"class":48,"line":305},[46,3911,3912],{"class":3684},"        login",[46,3914,57],{"class":56},[46,3916,60],{"class":56},[46,3918,3919],{"class":63},"\u002Flogin",[46,3921,3725],{"class":56},[46,3923,3701],{"class":56},[46,3925,3926,3929,3931,3933,3936,3938],{"class":48,"line":313},[46,3927,3928],{"class":3684},"        logout",[46,3930,57],{"class":56},[46,3932,60],{"class":56},[46,3934,3935],{"class":63},"\u002F",[46,3937,3725],{"class":56},[46,3939,3701],{"class":56},[46,3941,3942,3945,3947,3949,3951,3953],{"class":48,"line":323},[46,3943,3944],{"class":3684},"        callback",[46,3946,57],{"class":56},[46,3948,60],{"class":56},[46,3950,3919],{"class":63},[46,3952,3725],{"class":56},[46,3954,3701],{"class":56},[46,3956,3957,3960,3962,3964,3967],{"class":48,"line":529},[46,3958,3959],{"class":3684},"        home",[46,3961,57],{"class":56},[46,3963,60],{"class":56},[46,3965,3966],{"class":63},"\u002Fhome",[46,3968,67],{"class":56},[46,3970,3971],{"class":48,"line":535},[46,3972,3973],{"class":56},"      }\n",[46,3975,3976],{"class":48,"line":540},[46,3977,2752],{"class":56},[46,3979,3980],{"class":48,"line":546},[46,3981,2851],{"class":56},[13,3983,3984],{},"ここでは予めauthモジュールで使用するログイン用のルートを指定したり、使用する通信パターンを定義します。",[13,3986,3987,3988,3991,3992,3995],{},"JWTトークンをローカルストレージに入れておくのは危ないらしいので、",[31,3989,3990],{},"localStorage: false","としておきます。そして",[31,3993,3994],{},"strategies","の中で通信パターンやルートの定義を行います。",[13,3997,3998,3999,4002],{},"今は",[31,4000,4001],{},"local","という通信パターンしかありませんが、outhとかapi2とか他のapiサーバーに対しての通信パターンを複数定義できます。",[13,4004,4005,4008,4009,4012,4013,4016,4017,4019,4020,4023],{},[31,4006,4007],{},"tokenType","で先ほどの",[31,4010,4011],{},"beare","を指定しておきます。こうすると自動的に",[31,4014,4015],{},"authorization","ヘッダーに",[31,4018,4011],{},"という文字を追加してくれます。",[31,4021,4022],{},"endopoints","でそれぞれのログイン（login）、ログアウト（logout）、ユーザー確認（user）、それぞれのルートを指定します。",[13,4025,4026,4027,4030],{},"指定しないとauthモジュールのデフォルトのURLでアクセスしてしまいます。特に",[31,4028,4029],{},"user","はページが読み込みされた際に、トークンをサーバーに送ってログイン状態かどうかをNuxt.jsに伝える機能があります。ここをキチンと設定しないとNuxt側で現在ログインが必要なページが開けないなどの状態に陥ります。",[1337,4032,4033],{"id":4033},"axiosの設定",[13,4035,4036],{},"最後にaxiosの設定をします。",[24,4038,4040],{"className":3671,"code":4039,"filename":1767,"language":3673,"meta":33,"style":33},"const ENV = require('dotenv').config().parsed;\nexport default {\n... \nenv:ENV,\naxios: {\n    baseURL: ENV.API_BASE_URL,\n  },\n...\n}\n",[31,4041,4042,4088,4099,4106,4113,4122,4139,4144,4148],{"__ignoreMap":33},[46,4043,4044,4048,4051,4054,4058,4061,4063,4066,4068,4071,4074,4077,4080,4082,4085],{"class":48,"line":49},[46,4045,4047],{"class":4046},"sJ14y","const",[46,4049,4050],{"class":78}," ENV ",[46,4052,4053],{"class":56},"=",[46,4055,4057],{"class":4056},"sdLwU"," require",[46,4059,4060],{"class":78},"(",[46,4062,3725],{"class":56},[46,4064,4065],{"class":63},"dotenv",[46,4067,3725],{"class":56},[46,4069,4070],{"class":78},")",[46,4072,4073],{"class":56},".",[46,4075,4076],{"class":4056},"config",[46,4078,4079],{"class":78},"()",[46,4081,4073],{"class":56},[46,4083,4084],{"class":78},"parsed",[46,4086,4087],{"class":56},";\n",[46,4089,4090,4094,4097],{"class":48,"line":70},[46,4091,4093],{"class":4092},"s6cf3","export",[46,4095,4096],{"class":4092}," default",[46,4098,3907],{"class":56},[46,4100,4101,4104],{"class":48,"line":82},[46,4102,4103],{"class":56},"...",[46,4105,79],{"class":78},[46,4107,4108,4111],{"class":48,"line":91},[46,4109,4110],{"class":78},"env:ENV",[46,4112,3701],{"class":56},[46,4114,4115,4118,4120],{"class":48,"line":102},[46,4116,4117],{"class":52},"axios",[46,4119,57],{"class":56},[46,4121,3907],{"class":56},[46,4123,4124,4127,4129,4132,4134,4137],{"class":48,"line":112},[46,4125,4126],{"class":52},"    baseURL",[46,4128,57],{"class":56},[46,4130,4131],{"class":78}," ENV",[46,4133,4073],{"class":56},[46,4135,4136],{"class":78},"API_BASE_URL",[46,4138,3701],{"class":56},[46,4140,4141],{"class":48,"line":121},[46,4142,4143],{"class":56},"  },\n",[46,4145,4146],{"class":48,"line":131},[46,4147,2851],{"class":56},[46,4149,4150],{"class":48,"line":139},[46,4151,2752],{"class":56},[13,4153,4154,4155,4157,4158,4161,4162,4164],{},"nuxtのdotenvモジュールを用いて",[31,4156,1596],{},"ファイルから",[31,4159,4160],{},"baseURL","つまりAPIのアクセス先ルートを指定します。",[31,4163,1596],{},"は以下のようになっています。",[24,4166,4169],{"className":4167,"code":4168,"filename":1596,"language":29,"meta":33},[27],"API_BASE_URL=http:\u002F\u002Flocalhost:8000\u002Fapi\u002Fv1\n",[31,4170,4168],{"__ignoreMap":33},[13,4172,4173,4174,4176,4177,4179,4180,4182],{},"今はローカルの開発環境でやっていますが本番は",[31,4175,1447],{},"ではなくドメインになったりします。環境ごとに異なる値は",[31,4178,1596],{},"に記述して環境ごとに",[31,4181,1596],{},"ファイルを作成してそれをインポートします。",[352,4184,4185],{"id":4185},"ログインフォームを整える",[13,4187,4188],{},"デフォルトのページとかを削除してログインフォームとかを用意します。今回はbootstrapで構築しました。このようにヘッダーがあり、「ログイン」をクリックすると入力モーダルが現れます。",[1692,4190],{":src":4191,":width":1695},"'_mix\u002Fsch-2020-12-05-15.07.38-768x389.png'",[1692,4193],{":src":4194,":width":1695},"'_mix\u002Fsch-2020-12-05-15.09.41-768x529.png'",[13,4196,4197,4198,4201],{},"この送信をクリックするとnuxt authの関数、",[31,4199,4200],{},"loginwith()","が実行されます。コンポーネントレベルですが以下のコードになっています。",[24,4203,4208],{"className":4204,"code":4205,"filename":4206,"language":4207,"meta":33,"style":33},"language-vue shiki shiki-themes material-theme-ocean","\u003Ctemplate>\n    \u003Cb-modal\n      id=\"login-modal\"\n      ref=\"modal\"\n      title=\"ログイン情報を入力してください。\"\n      ok-only\n      :okTitle=\"'送信'\"\n      @show=\"resetModal\"\n      @hidden=\"resetModal\"\n      @ok=\"handleOk\"\n    >\n      \u003Cform @submit.stop.prevent=\"handleSubmit\">\n        \u003Cb-alert v-if=\"loginErrMes\" show variant=\"danger\">{{loginErrMes}}\u003C\u002Fb-alert>\n        \u003Cb-form-group\n          :state=\"emailState\"\n          label=\"メールアドレス\"\n          type=\"email\"\n          label-for=\"name-input\"\n          invalid-feedback=\"メールアドレスは入力必須です。\"\n        >\n          \u003Cb-form-input\n            id=\"login-email-input\"\n            v-model=\"email\"\n            :state=\"emailState\"\n            required\n          \u002F>\n        \u003C\u002Fb-form-group>\n\n        \u003Cb-form-group\n          :state=\"passState\"\n          label=\"パスワード\"\n          label-for=\"name-input\"\n          invalid-feedback=\"パスワードは入力必須です。\"\n          autocomplete=\"username\"\n        >\n          \u003Cb-form-input\n            id=\"login-password-input\"\n            v-model=\"pass\"\n            type=\"password\"\n            :state=\"passState\"\n            autocomplete=\"current-password\"\n            required\n          \u002F>\n        \u003C\u002Fb-form-group>\n      \u003C\u002Fform>\n    \u003C\u002Fb-modal>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\nimport { required, minLength, between } from 'vuelidate\u002Flib\u002Fvalidators'\nexport default {\n    name:'loginModal',\n    data(){\n        return{\n            email:'',\n            pass:'',\n            emailState:null,\n            passState:null,\n            loginErrMes:null,\n        }\n    },\n    validations:{\n      email:{\n        required,\n      },\n      pass:{\n        required\n      },\n    },\n    methods:{\n        checkFormHasError(){\n            this.emailState = !this.$v.email.$invalid;\n            this.passState = !this.$v.pass.$invalid;\n            return this.$v.$invalid;\n        },\n        resetModal(){\n            this.email='';\n            this.pass='';\n            this.emailState=null;\n            this.passState=null;\n            this.loginErrMes=null;\n        },\n        handleOk(bvModalEvt){\n            bvModalEvt.preventDefault()\n            this.handleSubmit();\n        },\n        async handleSubmit() {\n            if(this.checkFormHasError()) return;\n\n            try{\n              await this.$auth.loginWith('local', { data:{\n                email:this.email,\n                password:this.pass\n              }})\n              this.resetModal();\n              this.$store.dispatch('message\u002FsetFlashMessage',{\n                content:'ログインしました。',\n                messageType:'success'\n              })\n              this.$bvModal.hide('login-modal')\n            }catch(error){\n              this.loginErrMes='パスワードまたはメールアドレスが異なります。';\n            }\n        }\n    },\n}\n","component\u002FloginModal","vue",[31,4209,4210,4221,4229,4244,4258,4272,4277,4291,4305,4318,4332,4337,4359,4407,4414,4428,4442,4456,4470,4484,4489,4497,4511,4524,4537,4542,4547,4557,4561,4567,4580,4593,4605,4618,4632,4636,4642,4655,4668,4682,4694,4708,4712,4716,4724,4733,4743,4751,4755,4764,4799,4807,4823,4831,4838,4850,4861,4869,4876,4883,4887,4892,4899,4906,4913,4917,4924,4929,4933,4937,4944,4951,4978,5000,5016,5021,5028,5040,5052,5061,5069,5077,5081,5095,5109,5120,5125,5138,5160,5165,5173,5206,5219,5230,5239,5251,5276,5293,5308,5316,5339,5357,5375,5381,5386,5391],{"__ignoreMap":33},[46,4211,4212,4215,4218],{"class":48,"line":49},[46,4213,4214],{"class":56},"\u003C",[46,4216,4217],{"class":52},"template",[46,4219,4220],{"class":56},">\n",[46,4222,4223,4226],{"class":48,"line":70},[46,4224,4225],{"class":56},"    \u003C",[46,4227,4228],{"class":52},"b-modal\n",[46,4230,4231,4234,4236,4239,4242],{"class":48,"line":82},[46,4232,4233],{"class":4046},"      id",[46,4235,4053],{"class":56},[46,4237,4238],{"class":56},"\"",[46,4240,4241],{"class":63},"login-modal",[46,4243,168],{"class":56},[46,4245,4246,4249,4251,4253,4256],{"class":48,"line":91},[46,4247,4248],{"class":4046},"      ref",[46,4250,4053],{"class":56},[46,4252,4238],{"class":56},[46,4254,4255],{"class":63},"modal",[46,4257,168],{"class":56},[46,4259,4260,4263,4265,4267,4270],{"class":48,"line":102},[46,4261,4262],{"class":4046},"      title",[46,4264,4053],{"class":56},[46,4266,4238],{"class":56},[46,4268,4269],{"class":63},"ログイン情報を入力してください。",[46,4271,168],{"class":56},[46,4273,4274],{"class":48,"line":112},[46,4275,4276],{"class":4046},"      ok-only\n",[46,4278,4279,4282,4284,4286,4289],{"class":48,"line":121},[46,4280,4281],{"class":4046},"      :okTitle",[46,4283,4053],{"class":56},[46,4285,4238],{"class":56},[46,4287,4288],{"class":63},"'送信'",[46,4290,168],{"class":56},[46,4292,4293,4296,4298,4300,4303],{"class":48,"line":131},[46,4294,4295],{"class":4046},"      @show",[46,4297,4053],{"class":56},[46,4299,4238],{"class":56},[46,4301,4302],{"class":63},"resetModal",[46,4304,168],{"class":56},[46,4306,4307,4310,4312,4314,4316],{"class":48,"line":139},[46,4308,4309],{"class":4046},"      @hidden",[46,4311,4053],{"class":56},[46,4313,4238],{"class":56},[46,4315,4302],{"class":63},[46,4317,168],{"class":56},[46,4319,4320,4323,4325,4327,4330],{"class":48,"line":147},[46,4321,4322],{"class":4046},"      @ok",[46,4324,4053],{"class":56},[46,4326,4238],{"class":56},[46,4328,4329],{"class":63},"handleOk",[46,4331,168],{"class":56},[46,4333,4334],{"class":48,"line":157},[46,4335,4336],{"class":56},"    >\n",[46,4338,4339,4342,4345,4348,4350,4352,4355,4357],{"class":48,"line":171},[46,4340,4341],{"class":56},"      \u003C",[46,4343,4344],{"class":52},"form",[46,4346,4347],{"class":4046}," @submit.stop.prevent",[46,4349,4053],{"class":56},[46,4351,4238],{"class":56},[46,4353,4354],{"class":63},"handleSubmit",[46,4356,4238],{"class":56},[46,4358,4220],{"class":56},[46,4360,4361,4364,4367,4370,4372,4374,4377,4379,4382,4385,4387,4389,4392,4394,4397,4400,4403,4405],{"class":48,"line":183},[46,4362,4363],{"class":56},"        \u003C",[46,4365,4366],{"class":52},"b-alert",[46,4368,4369],{"class":4046}," v-if",[46,4371,4053],{"class":56},[46,4373,4238],{"class":56},[46,4375,4376],{"class":63},"loginErrMes",[46,4378,4238],{"class":56},[46,4380,4381],{"class":4046}," show",[46,4383,4384],{"class":4046}," variant",[46,4386,4053],{"class":56},[46,4388,4238],{"class":56},[46,4390,4391],{"class":63},"danger",[46,4393,4238],{"class":56},[46,4395,4396],{"class":56},">",[46,4398,4399],{"class":78},"{{loginErrMes}}",[46,4401,4402],{"class":56},"\u003C\u002F",[46,4404,4366],{"class":52},[46,4406,4220],{"class":56},[46,4408,4409,4411],{"class":48,"line":195},[46,4410,4363],{"class":56},[46,4412,4413],{"class":52},"b-form-group\n",[46,4415,4416,4419,4421,4423,4426],{"class":48,"line":206},[46,4417,4418],{"class":4046},"          :state",[46,4420,4053],{"class":56},[46,4422,4238],{"class":56},[46,4424,4425],{"class":63},"emailState",[46,4427,168],{"class":56},[46,4429,4430,4433,4435,4437,4440],{"class":48,"line":4},[46,4431,4432],{"class":4046},"          label",[46,4434,4053],{"class":56},[46,4436,4238],{"class":56},[46,4438,4439],{"class":63},"メールアドレス",[46,4441,168],{"class":56},[46,4443,4444,4447,4449,4451,4454],{"class":48,"line":223},[46,4445,4446],{"class":4046},"          type",[46,4448,4053],{"class":56},[46,4450,4238],{"class":56},[46,4452,4453],{"class":63},"email",[46,4455,168],{"class":56},[46,4457,4458,4461,4463,4465,4468],{"class":48,"line":231},[46,4459,4460],{"class":4046},"          label-for",[46,4462,4053],{"class":56},[46,4464,4238],{"class":56},[46,4466,4467],{"class":63},"name-input",[46,4469,168],{"class":56},[46,4471,4472,4475,4477,4479,4482],{"class":48,"line":242},[46,4473,4474],{"class":4046},"          invalid-feedback",[46,4476,4053],{"class":56},[46,4478,4238],{"class":56},[46,4480,4481],{"class":63},"メールアドレスは入力必須です。",[46,4483,168],{"class":56},[46,4485,4486],{"class":48,"line":253},[46,4487,4488],{"class":56},"        >\n",[46,4490,4491,4494],{"class":48,"line":264},[46,4492,4493],{"class":56},"          \u003C",[46,4495,4496],{"class":52},"b-form-input\n",[46,4498,4499,4502,4504,4506,4509],{"class":48,"line":275},[46,4500,4501],{"class":4046},"            id",[46,4503,4053],{"class":56},[46,4505,4238],{"class":56},[46,4507,4508],{"class":63},"login-email-input",[46,4510,168],{"class":56},[46,4512,4513,4516,4518,4520,4522],{"class":48,"line":284},[46,4514,4515],{"class":4046},"            v-model",[46,4517,4053],{"class":56},[46,4519,4238],{"class":56},[46,4521,4453],{"class":63},[46,4523,168],{"class":56},[46,4525,4526,4529,4531,4533,4535],{"class":48,"line":296},[46,4527,4528],{"class":4046},"            :state",[46,4530,4053],{"class":56},[46,4532,4238],{"class":56},[46,4534,4425],{"class":63},[46,4536,168],{"class":56},[46,4538,4539],{"class":48,"line":305},[46,4540,4541],{"class":4046},"            required\n",[46,4543,4544],{"class":48,"line":313},[46,4545,4546],{"class":56},"          \u002F>\n",[46,4548,4549,4552,4555],{"class":48,"line":323},[46,4550,4551],{"class":56},"        \u003C\u002F",[46,4553,4554],{"class":52},"b-form-group",[46,4556,4220],{"class":56},[46,4558,4559],{"class":48,"line":529},[46,4560,408],{"emptyLinePlaceholder":407},[46,4562,4563,4565],{"class":48,"line":535},[46,4564,4363],{"class":56},[46,4566,4413],{"class":52},[46,4568,4569,4571,4573,4575,4578],{"class":48,"line":540},[46,4570,4418],{"class":4046},[46,4572,4053],{"class":56},[46,4574,4238],{"class":56},[46,4576,4577],{"class":63},"passState",[46,4579,168],{"class":56},[46,4581,4582,4584,4586,4588,4591],{"class":48,"line":546},[46,4583,4432],{"class":4046},[46,4585,4053],{"class":56},[46,4587,4238],{"class":56},[46,4589,4590],{"class":63},"パスワード",[46,4592,168],{"class":56},[46,4594,4595,4597,4599,4601,4603],{"class":48,"line":551},[46,4596,4460],{"class":4046},[46,4598,4053],{"class":56},[46,4600,4238],{"class":56},[46,4602,4467],{"class":63},[46,4604,168],{"class":56},[46,4606,4607,4609,4611,4613,4616],{"class":48,"line":557},[46,4608,4474],{"class":4046},[46,4610,4053],{"class":56},[46,4612,4238],{"class":56},[46,4614,4615],{"class":63},"パスワードは入力必須です。",[46,4617,168],{"class":56},[46,4619,4620,4623,4625,4627,4630],{"class":48,"line":562},[46,4621,4622],{"class":4046},"          autocomplete",[46,4624,4053],{"class":56},[46,4626,4238],{"class":56},[46,4628,4629],{"class":63},"username",[46,4631,168],{"class":56},[46,4633,4634],{"class":48,"line":568},[46,4635,4488],{"class":56},[46,4637,4638,4640],{"class":48,"line":573},[46,4639,4493],{"class":56},[46,4641,4496],{"class":52},[46,4643,4644,4646,4648,4650,4653],{"class":48,"line":579},[46,4645,4501],{"class":4046},[46,4647,4053],{"class":56},[46,4649,4238],{"class":56},[46,4651,4652],{"class":63},"login-password-input",[46,4654,168],{"class":56},[46,4656,4657,4659,4661,4663,4666],{"class":48,"line":584},[46,4658,4515],{"class":4046},[46,4660,4053],{"class":56},[46,4662,4238],{"class":56},[46,4664,4665],{"class":63},"pass",[46,4667,168],{"class":56},[46,4669,4670,4673,4675,4677,4680],{"class":48,"line":590},[46,4671,4672],{"class":4046},"            type",[46,4674,4053],{"class":56},[46,4676,4238],{"class":56},[46,4678,4679],{"class":63},"password",[46,4681,168],{"class":56},[46,4683,4684,4686,4688,4690,4692],{"class":48,"line":595},[46,4685,4528],{"class":4046},[46,4687,4053],{"class":56},[46,4689,4238],{"class":56},[46,4691,4577],{"class":63},[46,4693,168],{"class":56},[46,4695,4696,4699,4701,4703,4706],{"class":48,"line":601},[46,4697,4698],{"class":4046},"            autocomplete",[46,4700,4053],{"class":56},[46,4702,4238],{"class":56},[46,4704,4705],{"class":63},"current-password",[46,4707,168],{"class":56},[46,4709,4710],{"class":48,"line":606},[46,4711,4541],{"class":4046},[46,4713,4714],{"class":48,"line":611},[46,4715,4546],{"class":56},[46,4717,4718,4720,4722],{"class":48,"line":616},[46,4719,4551],{"class":56},[46,4721,4554],{"class":52},[46,4723,4220],{"class":56},[46,4725,4726,4729,4731],{"class":48,"line":622},[46,4727,4728],{"class":56},"      \u003C\u002F",[46,4730,4344],{"class":52},[46,4732,4220],{"class":56},[46,4734,4735,4738,4741],{"class":48,"line":627},[46,4736,4737],{"class":56},"    \u003C\u002F",[46,4739,4740],{"class":52},"b-modal",[46,4742,4220],{"class":56},[46,4744,4745,4747,4749],{"class":48,"line":633},[46,4746,4402],{"class":56},[46,4748,4217],{"class":52},[46,4750,4220],{"class":56},[46,4752,4753],{"class":48,"line":638},[46,4754,408],{"emptyLinePlaceholder":407},[46,4756,4757,4759,4762],{"class":48,"line":2744},[46,4758,4214],{"class":56},[46,4760,4761],{"class":52},"script",[46,4763,4220],{"class":56},[46,4765,4766,4769,4772,4775,4778,4781,4783,4786,4789,4792,4794,4797],{"class":48,"line":2749},[46,4767,4768],{"class":4092},"import",[46,4770,4771],{"class":56}," {",[46,4773,4774],{"class":78}," required",[46,4776,4777],{"class":56},",",[46,4779,4780],{"class":78}," minLength",[46,4782,4777],{"class":56},[46,4784,4785],{"class":78}," between",[46,4787,4788],{"class":56}," }",[46,4790,4791],{"class":4092}," from",[46,4793,60],{"class":56},[46,4795,4796],{"class":63},"vuelidate\u002Flib\u002Fvalidators",[46,4798,67],{"class":56},[46,4800,4801,4803,4805],{"class":48,"line":3225},[46,4802,4093],{"class":4092},[46,4804,4096],{"class":4092},[46,4806,3907],{"class":56},[46,4808,4809,4812,4814,4816,4819,4821],{"class":48,"line":3231},[46,4810,4811],{"class":52},"    name",[46,4813,57],{"class":56},[46,4815,3725],{"class":56},[46,4817,4818],{"class":63},"loginModal",[46,4820,3725],{"class":56},[46,4822,3701],{"class":56},[46,4824,4825,4828],{"class":48,"line":3236},[46,4826,4827],{"class":52},"    data",[46,4829,4830],{"class":56},"(){\n",[46,4832,4833,4836],{"class":48,"line":3242},[46,4834,4835],{"class":4092},"        return",[46,4837,2572],{"class":56},[46,4839,4840,4843,4845,4848],{"class":48,"line":3247},[46,4841,4842],{"class":52},"            email",[46,4844,57],{"class":56},[46,4846,4847],{"class":56},"''",[46,4849,3701],{"class":56},[46,4851,4852,4855,4857,4859],{"class":48,"line":3253},[46,4853,4854],{"class":52},"            pass",[46,4856,57],{"class":56},[46,4858,4847],{"class":56},[46,4860,3701],{"class":56},[46,4862,4863,4866],{"class":48,"line":3258},[46,4864,4865],{"class":52},"            emailState",[46,4867,4868],{"class":56},":null,\n",[46,4870,4871,4874],{"class":48,"line":3263},[46,4872,4873],{"class":52},"            passState",[46,4875,4868],{"class":56},[46,4877,4878,4881],{"class":48,"line":3268},[46,4879,4880],{"class":52},"            loginErrMes",[46,4882,4868],{"class":56},[46,4884,4885],{"class":48,"line":3274},[46,4886,3141],{"class":56},[46,4888,4889],{"class":48,"line":3279},[46,4890,4891],{"class":56},"    },\n",[46,4893,4894,4897],{"class":48,"line":3284},[46,4895,4896],{"class":52},"    validations",[46,4898,3688],{"class":56},[46,4900,4901,4904],{"class":48,"line":3289},[46,4902,4903],{"class":52},"      email",[46,4905,3688],{"class":56},[46,4907,4908,4911],{"class":48,"line":3295},[46,4909,4910],{"class":78},"        required",[46,4912,3701],{"class":56},[46,4914,4915],{"class":48,"line":3300},[46,4916,3897],{"class":56},[46,4918,4919,4922],{"class":48,"line":3306},[46,4920,4921],{"class":52},"      pass",[46,4923,3688],{"class":56},[46,4925,4926],{"class":48,"line":3311},[46,4927,4928],{"class":78},"        required\n",[46,4930,4931],{"class":48,"line":3316},[46,4932,3897],{"class":56},[46,4934,4935],{"class":48,"line":3321},[46,4936,4891],{"class":56},[46,4938,4939,4942],{"class":48,"line":3327},[46,4940,4941],{"class":52},"    methods",[46,4943,3688],{"class":56},[46,4945,4946,4949],{"class":48,"line":3332},[46,4947,4948],{"class":52},"        checkFormHasError",[46,4950,4830],{"class":56},[46,4952,4953,4956,4958,4961,4964,4967,4969,4971,4973,4976],{"class":48,"line":3338},[46,4954,4955],{"class":56},"            this.",[46,4957,4425],{"class":78},[46,4959,4960],{"class":56}," =",[46,4962,4963],{"class":56}," !this.",[46,4965,4966],{"class":78},"$v",[46,4968,4073],{"class":56},[46,4970,4453],{"class":78},[46,4972,4073],{"class":56},[46,4974,4975],{"class":78},"$invalid",[46,4977,4087],{"class":56},[46,4979,4980,4982,4984,4986,4988,4990,4992,4994,4996,4998],{"class":48,"line":3343},[46,4981,4955],{"class":56},[46,4983,4577],{"class":78},[46,4985,4960],{"class":56},[46,4987,4963],{"class":56},[46,4989,4966],{"class":78},[46,4991,4073],{"class":56},[46,4993,4665],{"class":78},[46,4995,4073],{"class":56},[46,4997,4975],{"class":78},[46,4999,4087],{"class":56},[46,5001,5002,5005,5008,5010,5012,5014],{"class":48,"line":3348},[46,5003,5004],{"class":4092},"            return",[46,5006,5007],{"class":56}," this.",[46,5009,4966],{"class":78},[46,5011,4073],{"class":56},[46,5013,4975],{"class":78},[46,5015,4087],{"class":56},[46,5017,5018],{"class":48,"line":3353},[46,5019,5020],{"class":56},"        },\n",[46,5022,5023,5026],{"class":48,"line":3359},[46,5024,5025],{"class":52},"        resetModal",[46,5027,4830],{"class":56},[46,5029,5030,5032,5034,5036,5038],{"class":48,"line":3364},[46,5031,4955],{"class":56},[46,5033,4453],{"class":78},[46,5035,4053],{"class":56},[46,5037,4847],{"class":56},[46,5039,4087],{"class":56},[46,5041,5042,5044,5046,5048,5050],{"class":48,"line":3370},[46,5043,4955],{"class":56},[46,5045,4665],{"class":78},[46,5047,4053],{"class":56},[46,5049,4847],{"class":56},[46,5051,4087],{"class":56},[46,5053,5054,5056,5058],{"class":48,"line":3376},[46,5055,4955],{"class":56},[46,5057,4425],{"class":78},[46,5059,5060],{"class":56},"=null;\n",[46,5062,5063,5065,5067],{"class":48,"line":3382},[46,5064,4955],{"class":56},[46,5066,4577],{"class":78},[46,5068,5060],{"class":56},[46,5070,5071,5073,5075],{"class":48,"line":3388},[46,5072,4955],{"class":56},[46,5074,4376],{"class":78},[46,5076,5060],{"class":56},[46,5078,5079],{"class":48,"line":3394},[46,5080,5020],{"class":56},[46,5082,5083,5086,5088,5092],{"class":48,"line":3399},[46,5084,5085],{"class":52},"        handleOk",[46,5087,4060],{"class":56},[46,5089,5091],{"class":5090},"s7ZW3","bvModalEvt",[46,5093,5094],{"class":56},"){\n",[46,5096,5098,5101,5103,5106],{"class":48,"line":5097},84,[46,5099,5100],{"class":78},"            bvModalEvt",[46,5102,4073],{"class":56},[46,5104,5105],{"class":4056},"preventDefault",[46,5107,5108],{"class":52},"()\n",[46,5110,5112,5114,5116,5118],{"class":48,"line":5111},85,[46,5113,4955],{"class":56},[46,5115,4354],{"class":4056},[46,5117,4079],{"class":52},[46,5119,4087],{"class":56},[46,5121,5123],{"class":48,"line":5122},86,[46,5124,5020],{"class":56},[46,5126,5128,5131,5134,5136],{"class":48,"line":5127},87,[46,5129,5130],{"class":4046},"        async",[46,5132,5133],{"class":52}," handleSubmit",[46,5135,4079],{"class":56},[46,5137,3907],{"class":56},[46,5139,5141,5144,5146,5149,5152,5155,5158],{"class":48,"line":5140},88,[46,5142,5143],{"class":4092},"            if",[46,5145,4060],{"class":52},[46,5147,5148],{"class":56},"this.",[46,5150,5151],{"class":4056},"checkFormHasError",[46,5153,5154],{"class":52},"()) ",[46,5156,5157],{"class":4092},"return",[46,5159,4087],{"class":56},[46,5161,5163],{"class":48,"line":5162},89,[46,5164,408],{"emptyLinePlaceholder":407},[46,5166,5168,5171],{"class":48,"line":5167},90,[46,5169,5170],{"class":4092},"            try",[46,5172,2572],{"class":56},[46,5174,5176,5179,5181,5184,5186,5189,5191,5193,5195,5197,5199,5201,5204],{"class":48,"line":5175},91,[46,5177,5178],{"class":4092},"              await",[46,5180,5007],{"class":56},[46,5182,5183],{"class":78},"$auth",[46,5185,4073],{"class":56},[46,5187,5188],{"class":4056},"loginWith",[46,5190,4060],{"class":52},[46,5192,3725],{"class":56},[46,5194,4001],{"class":63},[46,5196,3725],{"class":56},[46,5198,4777],{"class":56},[46,5200,4771],{"class":56},[46,5202,5203],{"class":52}," data",[46,5205,3688],{"class":56},[46,5207,5209,5212,5215,5217],{"class":48,"line":5208},92,[46,5210,5211],{"class":52},"                email",[46,5213,5214],{"class":56},":this.",[46,5216,4453],{"class":78},[46,5218,3701],{"class":56},[46,5220,5222,5225,5227],{"class":48,"line":5221},93,[46,5223,5224],{"class":52},"                password",[46,5226,5214],{"class":56},[46,5228,5229],{"class":78},"pass\n",[46,5231,5233,5236],{"class":48,"line":5232},94,[46,5234,5235],{"class":56},"              }}",[46,5237,5238],{"class":52},")\n",[46,5240,5242,5245,5247,5249],{"class":48,"line":5241},95,[46,5243,5244],{"class":56},"              this.",[46,5246,4302],{"class":4056},[46,5248,4079],{"class":52},[46,5250,4087],{"class":56},[46,5252,5254,5256,5259,5261,5264,5266,5268,5271,5273],{"class":48,"line":5253},96,[46,5255,5244],{"class":56},[46,5257,5258],{"class":78},"$store",[46,5260,4073],{"class":56},[46,5262,5263],{"class":4056},"dispatch",[46,5265,4060],{"class":52},[46,5267,3725],{"class":56},[46,5269,5270],{"class":63},"message\u002FsetFlashMessage",[46,5272,3725],{"class":56},[46,5274,5275],{"class":56},",{\n",[46,5277,5279,5282,5284,5286,5289,5291],{"class":48,"line":5278},97,[46,5280,5281],{"class":52},"                content",[46,5283,57],{"class":56},[46,5285,3725],{"class":56},[46,5287,5288],{"class":63},"ログインしました。",[46,5290,3725],{"class":56},[46,5292,3701],{"class":56},[46,5294,5296,5299,5301,5303,5306],{"class":48,"line":5295},98,[46,5297,5298],{"class":52},"                messageType",[46,5300,57],{"class":56},[46,5302,3725],{"class":56},[46,5304,5305],{"class":63},"success",[46,5307,67],{"class":56},[46,5309,5311,5314],{"class":48,"line":5310},99,[46,5312,5313],{"class":56},"              }",[46,5315,5238],{"class":52},[46,5317,5319,5321,5324,5326,5329,5331,5333,5335,5337],{"class":48,"line":5318},100,[46,5320,5244],{"class":56},[46,5322,5323],{"class":78},"$bvModal",[46,5325,4073],{"class":56},[46,5327,5328],{"class":4056},"hide",[46,5330,4060],{"class":52},[46,5332,3725],{"class":56},[46,5334,4241],{"class":63},[46,5336,3725],{"class":56},[46,5338,5238],{"class":52},[46,5340,5342,5345,5348,5350,5353,5355],{"class":48,"line":5341},101,[46,5343,5344],{"class":56},"            }",[46,5346,5347],{"class":4092},"catch",[46,5349,4060],{"class":52},[46,5351,5352],{"class":78},"error",[46,5354,4070],{"class":52},[46,5356,2572],{"class":56},[46,5358,5360,5362,5364,5366,5368,5371,5373],{"class":48,"line":5359},102,[46,5361,5244],{"class":56},[46,5363,4376],{"class":78},[46,5365,4053],{"class":56},[46,5367,3725],{"class":56},[46,5369,5370],{"class":63},"パスワードまたはメールアドレスが異なります。",[46,5372,3725],{"class":56},[46,5374,4087],{"class":56},[46,5376,5378],{"class":48,"line":5377},103,[46,5379,5380],{"class":56},"            }\n",[46,5382,5384],{"class":48,"line":5383},104,[46,5385,3141],{"class":56},[46,5387,5389],{"class":48,"line":5388},105,[46,5390,4891],{"class":56},[46,5392,5394],{"class":48,"line":5393},106,[46,5395,2752],{"class":56},[13,5397,5398,5399,5402,5403,5406],{},"このアプリでは後でいろいろフォームとかある予定なので",[31,5400,5401],{},"vuelidate","というバリデーションライブラリを入れています。このログインフォーム程度であれば必要ありませんけど。ログイン処理をしているのは下の方にある",[31,5404,5405],{},"async handleSubmit()","です。入力値が正規値であれば実行されます。",[13,5408,5409,5410,5412],{},"nuxt authは",[31,5411,1767],{},"で定義した設定を元にログインのルートにデータをPOSTします。超便利です。",[1692,5414],{":src":5415,":width":1695},"'_mix\u002Fsch-2020-12-05-15.14.45-768x518.png'",[13,5417,5418],{},"先ほどテストで入れたようにシーダーのアドレスとパスワードを入れて送信します。",[1692,5420],{":src":5421,":width":1695},"'_mix\u002Fsch-2020-12-05-15.17.31-768x81.png'",[13,5423,5424],{},"ネットワークを見てみるとキチンと8000ポートで待機しているlaravelへ送信されています。レスポンスが200で成功しています。クッキーを見てみるとトークンが保存されているのが分かります。",[1692,5426],{":src":5427,":width":1695},"'_mix\u002Fsch-2020-12-05-15.16.35-768x71.png'",[13,5429,5430],{},"そしてユーザー情報があるか＝ログインしているかで「ログイン」を「ログアウト」を制御しています。",[1692,5432],{":src":5433,":width":1695},"'_mix\u002Fsch-2020-12-05-15.22.55-768x168.png'",[13,5435,5436],{},"authモジュールを入れている場合は以下のコードでログインしているか、ユーザーの情報を取得することができます。",[24,5438,5440],{"className":3671,"code":5439,"language":3673,"meta":33,"style":33},"this.$auth.user\nthis.$auth.loggedIn\n",[31,5441,5442,5453],{"__ignoreMap":33},[46,5443,5444,5446,5448,5450],{"class":48,"line":49},[46,5445,5148],{"class":56},[46,5447,5183],{"class":78},[46,5449,4073],{"class":56},[46,5451,5452],{"class":78},"user\n",[46,5454,5455,5457,5459,5461],{"class":48,"line":70},[46,5456,5148],{"class":56},[46,5458,5183],{"class":78},[46,5460,4073],{"class":56},[46,5462,5463],{"class":78},"loggedIn\n",[20,5465,5467],{"id":5466},"nuxtのspaをビルドする","NuxtのSPAをビルドする",[13,5469,5470],{},"Nuxtは3000ポートの開発サーバで見ていたので、今度はキチンとビルドしてユーザー視点でアクセスしてみましょう。",[24,5472,5475],{"className":5473,"code":5474,"language":29},[27],"npm run build\n",[31,5476,5474],{"__ignoreMap":33},[13,5478,5479,5480,5482,5483,5485],{},"distファイルが出されたのを確認し",[31,5481,1439],{},"へアクセスします。私の環境ではホストの",[31,5484,1439],{},"はコンテナのlocalhost:80につながります。最初の設定の通り、ドキュメントルート はdist配下に通じているのでindex.htmlが返されます。ログインが成功し、ネットワークでもAPIが送信されているのが分かります。",[1692,5487],{":src":5488,":width":1695},"'_mix\u002Fsch-2020-12-05-15.34.55-768x213.png'",[13,5490,5491],{},"後はどんどんAPIルートを作成して、nuxtからはaxiosを用いてトークン付きリクエストを送れば認証ルートにアクセスすることができます。これでJWT認証つきのSPAアプリの設定が完了しました。",[20,5493,5495],{"id":5494},"以上","以上！",[13,5497,5498],{},"以上がLravel6とNuxt.jsで構築するJWT認証つきSPAの構築です。サーバーの構成は人によって様々ですがバーチャルホスト で公開側ページとAPIを分けてしまうのが簡単な気がします。とにかくトークン認証を用いたSPAを構築する際には",[336,5500,5501,5504,5507,5510,5513,5516,5519,5522],{},[339,5502,5503],{},"Nuxt側とAPI側でサーバーを分ける",[339,5505,5506],{},"laravelにJWT認証ライブラリを入れる",[339,5508,5509],{},"ログイン用ルートを整える",[339,5511,5512],{},"CORSの設定を行う",[339,5514,5515],{},"NuxtからのログインルートにPOSTリクエストを送る",[339,5517,5518],{},"トークンをブラウザのクッキーなどに保存",[339,5520,5521],{},"認証ルートにはリクエストヘッダにBeare＋トークンでリクエストをする",[339,5523,5524],{},"おのつど、またはページがリロードされたら \u002Fapi\u002Fauth\u002Fmeでトークンが有効かを確かめてNuxt側に認証状態を知らせる。",[13,5526,5527],{},"以上のまとめを意識すればおおよそのNuxt+API認証はわかってくると思います。ReactとかNext.jsなどもこの部分のエッセンスは同じなのでぜひ応用してください。",[20,5529,5530],{"id":5530},"参考記事",[336,5532,5533,5540,5547],{},[339,5534,5535],{},[725,5536,5539],{"href":5537,"rel":5538},"https:\u002F\u002Fpgmemo.tokyo\u002Fdata\u002Farchives\u002F1703.html",[729],"Laravel6 に jwt-auth をインストールしSPAからログインする（バックエンド Laravel編）",[339,5541,5542],{},[725,5543,5546],{"href":5544,"rel":5545},"https:\u002F\u002Fgithub.com\u002Ftymondesigns\u002Fjwt-auth\u002Fissues\u002F2059",[729],"Tymon\\JWTAuth\\Exceptions\\JWTException: Could not create token: Implicit conversion of keys from strings is deprecated. Please use InMemory or LocalFileReference classes. ",[339,5548,5549],{},[725,5550,5552],{"href":3629,"rel":5551},[729],"Nuxt auth introduction",[1851,5554,5555],{},"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 .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .sbqyR, html code.shiki .sbqyR{--shiki-default:#FF9CAC}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 .s5Dmg, html code.shiki .s5Dmg{--shiki-default:#FFCB6B}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html pre.shiki code .s6cf3, html code.shiki .s6cf3{--shiki-default:#89DDFF;--shiki-default-font-style:italic}html pre.shiki code .s7ZW3, html code.shiki .s7ZW3{--shiki-default:#BABED8;--shiki-default-font-style:italic}",{"title":33,"searchDepth":82,"depth":82,"links":5557},[5558,5563,5567,5576,5577,5590,5591,5592],{"id":1920,"depth":70,"text":1921,"children":5559},[5560,5561,5562],{"id":1953,"depth":82,"text":1953},{"id":2241,"depth":82,"text":2242},{"id":2273,"depth":82,"text":2274},{"id":2280,"depth":70,"text":2281,"children":5564},[5565,5566],{"id":2284,"depth":82,"text":2284},{"id":2306,"depth":82,"text":2307},{"id":2332,"depth":70,"text":2333,"children":5568},[5569,5570,5571,5572,5573,5574,5575],{"id":2347,"depth":82,"text":2348},{"id":2376,"depth":82,"text":2376},{"id":2486,"depth":82,"text":2486},{"id":2513,"depth":82,"text":2514},{"id":2755,"depth":82,"text":2756},{"id":2913,"depth":82,"text":2914},{"id":2978,"depth":82,"text":2978},{"id":3407,"depth":70,"text":3408},{"id":3446,"depth":70,"text":3447,"children":5578},[5579,5584,5589],{"id":3450,"depth":82,"text":3450,"children":5580},[5581,5582,5583],{"id":3456,"depth":91,"text":3457},{"id":3466,"depth":91,"text":3467},{"id":3496,"depth":91,"text":3497},{"id":3622,"depth":82,"text":3623,"children":5585},[5586,5587,5588],{"id":3635,"depth":91,"text":3635},{"id":3647,"depth":91,"text":3648},{"id":4033,"depth":91,"text":4033},{"id":4185,"depth":82,"text":4185},{"id":5466,"depth":70,"text":5467},{"id":5494,"depth":70,"text":5495},{"id":5530,"depth":70,"text":5530},[1881],"2025-09-05",{},"\u002Farticles\u002Flaravel-nuxt-jwt-spa",{"title":1895,"description":1895},"articles\u002Flaravel-nuxt-jwt-spa",[1583,5600],"nuxt","_mix\u002Flaravel-nuxt.jpeg","PoGyyswEs82YuxYkbUxf6PSHnouWa2M9g-hxfKYHTNE",{"id":5604,"title":5605,"body":5606,"category":8261,"createdAt":8262,"description":5605,"extension":1883,"index":1884,"meta":8263,"navigation":407,"path":8264,"publish":407,"seo":8265,"series":1884,"seriesTitle":1884,"stem":8266,"tag":8267,"thumbnail":8269,"updatedAt":8262,"__hash__":8270},"articles\u002Farticles\u002Fzoom-api-laravel.md","Zoom APIとLaravelを使って自動ミーティング作成フォームを構築する。",{"type":10,"value":5607,"toc":8215},[5608,5617,5625,5628,5632,5635,5638,5644,5647,5650,5654,5657,5660,5663,5669,5673,5676,5679,5682,5685,5691,5694,5697,5700,5703,5718,5721,5725,5728,5731,5734,5737,5740,5743,5746,5749,5752,5755,5758,5761,5767,5776,5808,5812,5816,5825,5828,5837,5840,5843,5846,5849,5852,5855,5864,5867,5881,5890,5906,5917,5920,5923,5926,5930,5933,5936,5939,5942,5945,5948,5952,5955,5959,5976,5982,5987,5993,6009,6013,6016,6109,6112,6126,6133,6136,6142,6145,6153,6159,6163,6166,6176,6245,6325,6338,6341,6354,6384,6391,6404,6408,6438,6441,6447,6450,6453,6457,6460,6463,6472,6635,6647,6650,6708,6719,6725,6735,6738,6772,6791,6794,6800,6807,6919,6922,6925,6928,6931,6934,7008,7022,7035,7041,7044,7047,7050,7053,7175,7178,7181,7185,7188,7191,7195,7406,7412,7418,7428,7431,7433,7437,7440,7785,7788,7791,7794,7854,7871,7875,7932,7941,7944,7947,7958,7961,7965,8122,8125,8128,8131,8134,8137,8140,8143,8146,8149,8152,8155,8158,8161,8164,8167,8170,8173,8188,8191,8194,8197,8205,8209,8212],[13,5609,5610,5611,5616],{},"こんにちはJuneです。年明け早々コロナが本気出してきて、ますます外に出れない日々が続きます。まあエンジニアは家にいてもプログラムでいろんなもの作れるのでいい暇つぶしになります。そこでコロナで株が爆上がりの",[725,5612,5615],{"href":5613,"rel":5614},"https:\u002F\u002Fmarketplace.zoom.us\u002Fdocs\u002Fapi-reference\u002Fintroduction",[729],"zoomにAPI","があるということを知って、早速使ってみました。会社でも「zoom APIには金の匂いがする！」とみんなで盛り上がったので是非探索してみます。",[13,5618,5619,5620,5624],{},"zoom API自体は2018年ごろから出ていたらしく、現在はv2がリリースされています。",[725,5621,5623],{"href":5613,"rel":5622},[729],"ドキュメント","を一通り読んだ所、zoomで行えることは一通りAPIを通じて行えるそうです。zoom APIについてわかった箇所を詳細に説明したいですが、この記事では実際の活用例を説明したいので部分省略します。",[13,5626,5627],{},"しかし、zoom APIを使用する認証フローや仕組みについては簡単に解説します。その説明から始めますので、「webアプリのはよ動き見せろや！」「そんなの知っとんじゃ！」という方は「Laravelを立ち上げる」から読み始めてください。",[20,5629,5631],{"id":5630},"zoom-apiの概要","Zoom APIの概要",[13,5633,5634],{},"詳しくはZoom API レファランスを見ればわかりますが、zoomのGUIでできることは基本的に可能です。ミーティングを作ったり、ウェビナーを開催したり、ユーザー情報を取ったり、開催中のzoomに対してチャットを送るなどなんでもできます。他にもwebhookや独自のコードでブラウザ上に映像・音声を出力できるSDKやエンドポイントもあるみたいです。",[13,5636,5637],{},"これらのAPIはRestAPIであり、特定のルートに対してGET\u002FPOST\u002FPUT\u002FDELTEでリクエストし、付属の情報はGETパラメータやPOSTパラメータで送信します。",[13,5639,5640,5641],{},"参考：",[725,5642,5613],{"href":5613,"rel":5643},[729],[352,5645,5646],{"id":5646},"認証方法",[13,5648,5649],{},"zoom APIにアクセスするには、JWTとOAuth2.0が使用できます。二者の違いはアクセスできる機能の量とマーケットプレイスに公開できるかが主になります。",[1337,5651,5653],{"id":5652},"jwt","JWT",[13,5655,5656],{},"ます。JSONで認証情報をやりとりします。",[13,5658,5659],{},"JWTによる認証はOAuthで使用できる権限範囲より狭く、自分自身で簡単にライトに使用したい場合に使うらしいです。また開発したzoom アプリはマーケットプレイスを通じて公開することができますが、JWT認証の場合はその公開ができません。",[13,5661,5662],{},"独自のwebアプリとZoomを連携させたい場合は次のOAuth2.0認証をお勧めします。",[13,5664,5640,5665],{},[725,5666,5667],{"href":5667,"rel":5668},"https:\u002F\u002Fmarketplace.zoom.us\u002Fdocs\u002Fapi-reference\u002Fusing-zoom-apis#using-jwt",[729],[1337,5670,5672],{"id":5671},"oauth-20","OAuth 2.0",[13,5674,5675],{},"OAuthは自身のwebサービスの資格情報を第三者のサービスへ提供する際に使用される認証フローです。ここでいう「webサービス」は「zoom」で「第三者のサービス」は「私のLaravelアプリ」です。",[13,5677,5678],{},"つまりOAuth認証を用いることで「私のLaravelアプリ」は連携させた人の「zoom」の資格情報を利用できる様になります。よって「私のLaravelアプリ」は連携させた人のzoomのミーティング情報を読み取ったり、作成したり、ユーザー情報を取得することができます。",[13,5680,5681],{},"資格情報を与え、操作権限も付与するのでかなり厳重な認証システムが必要となりそこで、秘密鍵や特定のプロトコルなどを用いたOAuthが使用されます。上記のJWTよりセキュアであり、また様々なAPIを利用できる様になります。",[13,5683,5684],{},"OAuthで実装したzoom APIはマーケットプレイスに出して公開することができます。今回の説明ではこのOAuth認証をLaravelを用いて実装していきます。まだ認証・連携のイメージがつかないと思いますが、読んでいくうちにわかると思います（多分）。",[13,5686,5640,5687],{},[725,5688,5689],{"href":5689,"rel":5690},"https:\u002F\u002Fmarketplace.zoom.us\u002Fdocs\u002Fapi-reference\u002Fusing-zoom-apis#using-oauth",[729],[352,5692,5693],{"id":5693},"連携の流れ",[13,5695,5696],{},"OAuthでの認証は以下の様に行われます。",[1692,5698],{":src":5699,":width":1695},"'_mix\u002F1570826762485.png'",[13,5701,5702],{},"もう少し具体的に解説すると",[5704,5705,5706,5709,5712,5715],"ol",{},[339,5707,5708],{},"ユーザー（zoomにログイン済み）を認証画面へリダイレクトさせる",[339,5710,5711],{},"確認後、ユーザーが承認したという証であるcodeを取得。",[339,5713,5714],{},"OAuthプロトコルに従い作成した認証キーとリクエストをzoomに送る",[339,5716,5717],{},"アクセストークンを得る。そのアクセストークン でzoom APIにアクセスする。アクセストークン などはアプリのDBに保存しておく。",[13,5719,5720],{},"こちらも後で実際の画面のスクショ付きで解説しますので、今は「ヘェ〜」程度の理解で大丈夫です。",[352,5722,5724],{"id":5723},"apiへのアクセス方法","APIへのアクセス方法",[13,5726,5727],{},"OAuthでアクセストークン を取得したら、そのトークンをリクエストヘッダーに仕込んでAPIにアクセスします。",[20,5729,5730],{"id":5730},"今回作るアプリ",[13,5732,5733],{},"まずアプリの機能と概要を説明しておきます。今回作るアプリは「匿名ユーザーが入力したフォームの内容に応じてzoomミーティングが作成され、それを管理できるwebアプリ」です。見た目は以下の感じです。",[1692,5735],{":src":5736,":width":1695},"'_mix\u002Fsch-2021-01-10-20.51.27-768x445.png'",[13,5738,5739],{},"アプリを管理し連携させるzoomアカウントを持っている「管理者」と、フォームに入力する「匿名ユーザー（お客さん）」が存在するとします。",[352,5741,5742],{"id":5742},"機能の概要",[13,5744,5745],{},"場面としては「zoomでのご相談はこちら」的な会社用のフォームであると思ってください。最初にお客さんはフォームにて名前・アドレス、zoomの希望開始時間を入力します。",[13,5747,5748],{},"フォーム入力内容が正しければ、zoom APIを通じて指定の時間で始まるzoom ミーティングを連携先のアカウントで作成。",[13,5750,5751],{},"APIからのレスポンスよりミーティングURLを取得し、DBに保存すると共にお客さんへミーティング情報をメールで飛ばす。（メールはローカルの環境でできなかったので、実装は割愛。ソースはあります。）",[13,5753,5754],{},"管理者は管理画面にて誰が・いつミーティングを開くのかを一覧で確認できる。またお客さんは時間変更用・削除用URLにアクセスして時間の変更・ミーティングのキャンセルが可能。",[13,5756,5757],{},"以上の様な機能を持たせたいと思います。とりあえずこれらの機能を実装する程度なので、厳密なバリデーションや細かい機能は割愛します。",[20,5759,5760],{"id":5760},"開発環境",[13,5762,5763,5764,5766],{},"私が慣れているLaravel 6を用いて作成します。Laravelのインストール方法は省略します。一応",[725,5765,738],{"href":1886},"で作ったDocker開発環境を用いています。またzoomへのHTTPアクセスをよく行うので、PHPのHTTPライブラリであるguzzlehttpをインストールしてください。",[13,5768,5769,5770,5775],{},"また、以下詳細な開発環境情報です。",[725,5771,5774],{"href":5772,"rel":5773},"https:\u002F\u002Fgithub.com\u002FjunjiIshii\u002Flaravel_zoom",[729],"開発したリポジトリも開放してます","のでぜひどうぞ。",[336,5777,5778,5781,5784,5787,5790,5793,5796,5799,5802,5805],{},[339,5779,5780],{},"MacOS Catalina 10.15.5",[339,5782,5783],{},"Docker 20.10.0",[339,5785,5786],{},"Docker-compose 1.27.4",[339,5788,5789],{},"（コンテナ内）composer 1.10.19",[339,5791,5792],{},"（コンテナ内）Laravel 6.20",[339,5794,5795],{},"（コンテナ内）guzzlehttp 7.2",[339,5797,5798],{},"（コンテナ内）centod 8",[339,5800,5801],{},"（コンテナ内）httpd 2.4.37",[339,5803,5804],{},"（コンテナ内）php 7.4.7",[339,5806,5807],{},"（コンテナ内・公式イメージ）mysql:5.7",[20,5809,5811],{"id":5810},"zoom-apiキーを手に入れる","Zoom APIキーを手に入れる",[352,5813,5815],{"id":5814},"zoom-アプリの作成","zoom アプリの作成",[13,5817,5818,5819,5824],{},"それでは初めていきましょう。Laravelを実装する前に自身のZoomアカウントにて、zoomアプリを作成していきましょう。",[725,5820,5823],{"href":5821,"rel":5822},"https:\u002F\u002Fmarketplace.zoom.us\u002F",[729],"zoom マーケットプレイス","へ移動します。そして画面上部の「Develop」をクリックし「Build App」をクリックします。",[1692,5826],{":src":5827,":width":1695},"'_mix\u002Fzoom_marget-768x330.jpeg'",[13,5829,5830,5831,5836],{},"すると",[725,5832,5835],{"href":5833,"rel":5834},"https:\u002F\u002Fmarketplace.zoom.us\u002Fdevelop\u002Fcreate",[729],"zoomアプリを選択する以下の様な画面","が表示されますので、「OAuth」の「Create」をクリック",[1692,5838],{":src":5839,":width":1695},"'_mix\u002Fzoom_app_create-768x385.jpeg'",[13,5841,5842],{},"「Create」を押すとアプリの名前などを入力するモーダルが出現します。任意の名前を入力し、アプリのタイプ、マーケットプレイスに公表するかを決定します。とりあえず私は以下の様にしました。",[1692,5844],{":src":5845,":width":1928,":center":1929},"'_mix\u002Fzoom_mordal-768x637.jpeg'",[13,5847,5848],{},"名前とマーケットプレイスへの公開は後から変更できます。ひとまず入力したら「Create」を押します。",[352,5850,5851],{"id":5851},"諸所の設定を入力",[1337,5853,5854],{"id":5854},"アプリの認証情報",[13,5856,5857,5858,5863],{},"作成後には",[725,5859,5862],{"href":5860,"rel":5861},"https:\u002F\u002Fmarketplace.zoom.us\u002Fuser\u002Fbuild",[729],"アプリの管理画面","に飛ばされると思います。そこから作成したアプリを選択して「App Credentials」を選択",[1692,5865],{":src":5866,":width":1695},"'_mix\u002Fzoom_app_info-768x637.jpeg'",[13,5868,5869,5870,5873,5874,5876,5877,5880],{},"Larvel側には ",[1914,5871,5872],{},"「Client ID」「Client Secret」"," が必要となります。後で",[31,5875,1596],{},"ファイルに記載します。ちなみに ",[1914,5878,5879],{},"「Client Secret」"," は絶対に外に漏れてはいけません。",[13,5882,5883,5886,5887,5889],{},[1914,5884,5885],{},"「Redirect URL for OAuth」"," にて承認画面からのリダイレクト先を指定しておきます。承認画面でアプリ連携を許可した際にはトークンなどが ",[1914,5888,5885],{}," あてへ送信され、ユーザーもリダイレクトされます。ここの値が異なっているとエラーで認証が進みません。",[13,5891,5892,5893,5895,5896,5898,5899,5901,5902,5905],{},"今は開発環境なのでドメインに",[31,5894,1447],{},"を指定しています。（私の環境では",[31,5897,1439],{},"でLaravelの画面が表示される様に設定しています。",[31,5900,2214],{},"などの場合は",[31,5903,5904],{},"localhost:8000","になると思いますので、ポートの指定に気をつけてください。）",[13,5907,5908,5909,5912,5913,5916],{},"「whitelist URL」はOAuthのリダイレクト先として許可するURLを指定できます。OAuthリダイレクトのURLに完全一致させるか、前方一致させる必要があります。設定したリダイレクト先は",[31,5910,5911],{},"http:\u002F\u002Flocalhost:9000\u002Fzoomauth\u002Fcheck","としていたので、その前方を含める様に",[31,5914,5915],{},"http:\u002F\u002Flocalhost:9000\u002F","に設定しておきます。",[1337,5918,5919],{"id":5919},"アプリの公開情報",[1692,5921],{":src":5922,":width":1695},"'_mix\u002Fzoom_information-572x1024.jpeg'",[13,5924,5925],{},"「Information」にて「Optional」と書かれている箇所以外を入力し記述します。",[1337,5927,5929],{"id":5928},"アプリのスコープアクセス範囲","アプリのスコープ（アクセス範囲）",[13,5931,5932],{},"ここが結構重要です。「Scopes」という箇所ではアプリの操作権限、アクセス範囲を設定できます。ユーザー情報の取得やミーティングの作成にもそれぞれスコープが用意されて、スコープ外の操作へのアクセスは401と認証エラーとなります。「Add Scopes」でスコープを追加します。",[13,5934,5935],{},"よくわからなければ全部追加してもいいですが、「Meeting」「User」のスコープを全て追加しておけば今回のアプリの実装は可能です。",[1692,5937],{":src":5938,":width":1695},"'_mix\u002Fzoom_scopes-768x431.jpeg'",[1337,5940,5941],{"id":5941},"アプリのアクティベート",[13,5943,5944],{},"最後に「Activation」にて確認します。不足箇所は以下の様にオレンジ文字で指摘されるので直しましょう。全てが入力できていれば後は問題ありません。「Install」などは押さなくても問題ありません。",[1692,5946],{":src":5947,":width":1695},"'_mix\u002Fzoom_activation-768x412.jpeg'",[20,5949,5951],{"id":5950},"lravelを立ち上げるdocker","Lravelを立ち上げる(Docker）",[13,5953,5954],{},"それではLaravelを立ち上げましょう。インストールはされており、ユーザーテーブルのマイグレーションを行う前だと仮定します。",[352,5956,5958],{"id":5957},"clien-id-と-client-secretを設定","Clien ID と Client Secretを設定",[13,5960,5961,5962,5964,5965,5968,5969,1440,5972,5975],{},"Larvelのプロジェクトルートに",[31,5963,1596],{},"という環境変数を記述するファイルがありますので、そちらに ",[1914,5966,5967],{},"「App Credentials」"," で取得できる",[31,5970,5971],{},"Client ID",[31,5973,5974],{},"Client Secret","を設定します。",[24,5977,5980],{"className":5978,"code":5979,"filename":1596,"language":29,"meta":33},[27],"APP_NAME=Laravel\nAPP_ENV=local\n...\nMIX_PUSHER_APP_CLUSTER=\"${PUSHER_APP_CLUSTER}\"\n\nZOOM_CLIENT_ID=clientid\nZOOM_CLIENT_SECRET=clientsecret\n",[31,5981,5979],{"__ignoreMap":33},[13,5983,5984,5986],{},[31,5985,1596],{},"を更新したらキャッシュをクリアして反映させます。",[24,5988,5991],{"className":5989,"code":5990,"language":29},[27],"php artisan config:clear\nphp artisan cache:clear\n",[31,5992,5990],{"__ignoreMap":33},[13,5994,5995,5997,5998,6001,6002,6004,6005,6008],{},[31,5996,1596],{},"に記述することで他のソースコード内で",[31,5999,6000],{},"env('ZOOM_CLIENT_ID')","と行った形で出力できます。また",[31,6003,1596],{},"は基本的に",[31,6006,6007],{},".gitignore","に登録されているので公開リポジトリに秘密鍵が載ってしまうという様な事故を防げます。",[352,6010,6012],{"id":6011},"user-tableをちょっと改造","User tableをちょっと改造",[13,6014,6015],{},"今回のアプリの管理者は一人ですが、もし複数人に使用してもらいたい時に「ユーザーごとにトークンを分けたいな」と思ったのでその改造をします。初期で用意されているユーザーテーブルを以下の様に書き換えます。",[24,6017,6020],{"className":2394,"code":6018,"filename":6019,"language":882,"meta":33,"style":33},"public function up()\n{\n    Schema::create('users', function (Blueprint $table) {\n        $table->bigIncrements('id');\n        $table->string('name');\n        $table->string('email')->unique();\n        $table->timestamp('email_verified_at')->nullable();\n        $table->string('password');\n    \n        \u002F\u002Fここから追加\n        $table->longText('zoom_code')->nullable()->default(null);\n        $table->longText('access_token')->nullable()->default(null);\n        $table->longText('refresh_token')->nullable()->default(null);\n        $table->timestamp('zoom_expires_in', 0)->nullable()->default(null);\n        $table->rememberToken();\n        $table->timestamps();\n    });\n}\n","database\u002Fmigrations\u002F2014_10_12_000000_create_users_table.php",[31,6021,6022,6027,6031,6036,6041,6046,6051,6056,6061,6066,6071,6076,6081,6086,6091,6096,6101,6105],{"__ignoreMap":33},[46,6023,6024],{"class":48,"line":49},[46,6025,6026],{},"public function up()\n",[46,6028,6029],{"class":48,"line":70},[46,6030,2572],{},[46,6032,6033],{"class":48,"line":82},[46,6034,6035],{},"    Schema::create('users', function (Blueprint $table) {\n",[46,6037,6038],{"class":48,"line":91},[46,6039,6040],{},"        $table->bigIncrements('id');\n",[46,6042,6043],{"class":48,"line":102},[46,6044,6045],{},"        $table->string('name');\n",[46,6047,6048],{"class":48,"line":112},[46,6049,6050],{},"        $table->string('email')->unique();\n",[46,6052,6053],{"class":48,"line":121},[46,6054,6055],{},"        $table->timestamp('email_verified_at')->nullable();\n",[46,6057,6058],{"class":48,"line":131},[46,6059,6060],{},"        $table->string('password');\n",[46,6062,6063],{"class":48,"line":139},[46,6064,6065],{},"    \n",[46,6067,6068],{"class":48,"line":147},[46,6069,6070],{},"        \u002F\u002Fここから追加\n",[46,6072,6073],{"class":48,"line":157},[46,6074,6075],{},"        $table->longText('zoom_code')->nullable()->default(null);\n",[46,6077,6078],{"class":48,"line":171},[46,6079,6080],{},"        $table->longText('access_token')->nullable()->default(null);\n",[46,6082,6083],{"class":48,"line":183},[46,6084,6085],{},"        $table->longText('refresh_token')->nullable()->default(null);\n",[46,6087,6088],{"class":48,"line":195},[46,6089,6090],{},"        $table->timestamp('zoom_expires_in', 0)->nullable()->default(null);\n",[46,6092,6093],{"class":48,"line":206},[46,6094,6095],{},"        $table->rememberToken();\n",[46,6097,6098],{"class":48,"line":4},[46,6099,6100],{},"        $table->timestamps();\n",[46,6102,6103],{"class":48,"line":223},[46,6104,2961],{},[46,6106,6107],{"class":48,"line":231},[46,6108,2752],{},[13,6110,6111],{},"それぞれのカラムの説明はこの通り。",[336,6113,6114,6117,6120,6123],{},[339,6115,6116],{},"zoom_code：連携許可の際に得られる許可コード。",[339,6118,6119],{},"access_token：APIにアクセスするためのアクセストークン これを手にしたら勝ち。",[339,6121,6122],{},"refresh_token：access_tokenを更新するためのトークン。",[339,6124,6125],{},"zoom_expires_in：access_tokenの期限を記録しておく。APIにアクセスする前にこれでチェックする。",[13,6127,6128,6129,6132],{},"ユーザーごとのトークンが管理できる様になり、",[31,6130,6131],{},"$user->auth()->access_token","みたいな感じでトークンを使用できます。",[13,6134,6135],{},"それではマイグレーションをしましょう。（仮ユーザーのseedも忘れずに）",[24,6137,6140],{"className":6138,"code":6139,"language":29},[27],"php artisan migrate --seed\nMigrating: 2014_10_12_000000_create_users_table\nMigrated:  2014_10_12_000000_create_users_table (0.02 seconds)\nMigrating: 2014_10_12_100000_create_password_resets_table\nMigrated:  2014_10_12_100000_create_password_resets_table (0.01 seconds)\nMigrating: 2019_08_19_000000_create_failed_jobs_table\nMigrated:  2019_08_19_000000_create_failed_jobs_table (0.01 seconds)\nSeeding: UsersTableSeeder\nSeeded:  UsersTableSeeder (0.06 seconds)\nDatabase seeding completed successfully.\n",[31,6141,6139],{"__ignoreMap":33},[352,6143,6144],{"id":6144},"フォームと管理画面を適当に作る",[13,6146,6147,6148,6152],{},"詳しくは",[725,6149,6151],{"href":5772,"rel":6150},[729],"アップしたリポジトリ","を見てください。スタイルはbootstrapで調整しています。ルート情報だけ載せておきます。",[24,6154,6157],{"className":6155,"code":6156,"language":29},[27],"\u002F                   フォームを表示  \n\u002Fconfirm            フォームの受付完了確認画面\n\u002Fadmin              管理者用ページ\n\u002Flogin              ログインページ\n\u002Flogout             ログアウトルート\n\u002Fzoomoatuh\u002Fcheck    zoom OAuthリダイレクト先\n\u002Fform\u002Falter         ミーティングの時間変更画面\n\u002Fform\u002Fdelete        ミーティングのキャンセル画面\n",[31,6158,6156],{"__ignoreMap":33},[20,6160,6162],{"id":6161},"lravel-と-zoomのoatuh-2-認証連携","Lravel と ZoomのOatuh 2 認証・連携",[352,6164,6165],{"id":6165},"管理画面から連携確認画面へ誘導とユーザー認証",[13,6167,6168,6169,6172,6173,6175],{},"それではLaravelとZoomの連携処理を実装していきます。連携処理はログインした管理者のアクセス配下で行います。管理画面は",[31,6170,6171],{},"\u002Fadmin","です。",[31,6174,6171],{},"のコントローラーとビューは以下の通りです。",[24,6177,6180],{"className":2394,"code":6178,"filename":6179,"language":882,"meta":33,"style":33},"public function index(Request $request){\n    $user = auth()->user();\n    $noZoomCode = $user->zoom_code == null; \u002F\u002F連携を行っているか\n    $zoomOuthLink = 'https:\u002F\u002Fzoom.us\u002Foauth\u002Fauthorize?'.http_build_query([\n        'response_type'=>'code',\n        'redirect_uri'=>env('APP_URL').'\u002Fzoomoatuh\u002Fcheck',\n        'client_id'=>env('ZOOM_CLIENT_ID'),\n    ]);\n    $oauthSuccess=false;\n    $meetings = Meeting::all();\n\n    return view('admin',compact('noZoomCode','zoomOuthLink','oauthSuccess','meetings'));\n}\n","app\u002FHttp\u002FControllers\u002FAdminController.php",[31,6181,6182,6187,6192,6197,6202,6207,6212,6217,6222,6227,6232,6236,6241],{"__ignoreMap":33},[46,6183,6184],{"class":48,"line":49},[46,6185,6186],{},"public function index(Request $request){\n",[46,6188,6189],{"class":48,"line":70},[46,6190,6191],{},"    $user = auth()->user();\n",[46,6193,6194],{"class":48,"line":82},[46,6195,6196],{},"    $noZoomCode = $user->zoom_code == null; \u002F\u002F連携を行っているか\n",[46,6198,6199],{"class":48,"line":91},[46,6200,6201],{},"    $zoomOuthLink = 'https:\u002F\u002Fzoom.us\u002Foauth\u002Fauthorize?'.http_build_query([\n",[46,6203,6204],{"class":48,"line":102},[46,6205,6206],{},"        'response_type'=>'code',\n",[46,6208,6209],{"class":48,"line":112},[46,6210,6211],{},"        'redirect_uri'=>env('APP_URL').'\u002Fzoomoatuh\u002Fcheck',\n",[46,6213,6214],{"class":48,"line":121},[46,6215,6216],{},"        'client_id'=>env('ZOOM_CLIENT_ID'),\n",[46,6218,6219],{"class":48,"line":131},[46,6220,6221],{},"    ]);\n",[46,6223,6224],{"class":48,"line":139},[46,6225,6226],{},"    $oauthSuccess=false;\n",[46,6228,6229],{"class":48,"line":147},[46,6230,6231],{},"    $meetings = Meeting::all();\n",[46,6233,6234],{"class":48,"line":157},[46,6235,408],{"emptyLinePlaceholder":407},[46,6237,6238],{"class":48,"line":171},[46,6239,6240],{},"    return view('admin',compact('noZoomCode','zoomOuthLink','oauthSuccess','meetings'));\n",[46,6242,6243],{"class":48,"line":183},[46,6244,2752],{},[24,6246,6249],{"className":2394,"code":6247,"filename":6248,"language":882,"meta":33,"style":33},"@extends('layouts.layout')\n\n@section('main-content')\n    \u003Cdiv class=\"main-content\">\n        @if($noZoomCode)\n        \u003Cdiv class=\"alert alert-danger mb-3\" role=\"alert\">\n            \u003Ch4 class=\"alert-heading\">Zoomとの連携が行われていません。\u003C\u002Fh4>\n            \u003Cp>このシステムをご利用する場合、Zoomとの連携を行ってください。\u003C\u002Fp>\n            \u003Ca href=\"{{$zoomOuthLink}}\" class=\"btn btn-danger\">Zoomと連携\u003C\u002Fa>\n        \u003C\u002Fdiv>\n        @else\n        \u003Ch1>予約一覧\u003C\u002Fh1>\n        @endif\n    \u003C\u002Fdiv>\n@endsection\n","resources\u002Fviews\u002Fadmin.blade.php",[31,6250,6251,6256,6260,6265,6270,6275,6280,6285,6290,6295,6300,6305,6310,6315,6320],{"__ignoreMap":33},[46,6252,6253],{"class":48,"line":49},[46,6254,6255],{},"@extends('layouts.layout')\n",[46,6257,6258],{"class":48,"line":70},[46,6259,408],{"emptyLinePlaceholder":407},[46,6261,6262],{"class":48,"line":82},[46,6263,6264],{},"@section('main-content')\n",[46,6266,6267],{"class":48,"line":91},[46,6268,6269],{},"    \u003Cdiv class=\"main-content\">\n",[46,6271,6272],{"class":48,"line":102},[46,6273,6274],{},"        @if($noZoomCode)\n",[46,6276,6277],{"class":48,"line":112},[46,6278,6279],{},"        \u003Cdiv class=\"alert alert-danger mb-3\" role=\"alert\">\n",[46,6281,6282],{"class":48,"line":121},[46,6283,6284],{},"            \u003Ch4 class=\"alert-heading\">Zoomとの連携が行われていません。\u003C\u002Fh4>\n",[46,6286,6287],{"class":48,"line":131},[46,6288,6289],{},"            \u003Cp>このシステムをご利用する場合、Zoomとの連携を行ってください。\u003C\u002Fp>\n",[46,6291,6292],{"class":48,"line":139},[46,6293,6294],{},"            \u003Ca href=\"{{$zoomOuthLink}}\" class=\"btn btn-danger\">Zoomと連携\u003C\u002Fa>\n",[46,6296,6297],{"class":48,"line":147},[46,6298,6299],{},"        \u003C\u002Fdiv>\n",[46,6301,6302],{"class":48,"line":157},[46,6303,6304],{},"        @else\n",[46,6306,6307],{"class":48,"line":171},[46,6308,6309],{},"        \u003Ch1>予約一覧\u003C\u002Fh1>\n",[46,6311,6312],{"class":48,"line":183},[46,6313,6314],{},"        @endif\n",[46,6316,6317],{"class":48,"line":195},[46,6318,6319],{},"    \u003C\u002Fdiv>\n",[46,6321,6322],{"class":48,"line":206},[46,6323,6324],{},"@endsection\n",[13,6326,6327,6328,6330,6331,3514,6334,6337],{},"管理画面では管理者がzoomと連携しているかで表示を変更しています。連携しているかは",[31,6329,4029],{},"テーブルの",[31,6332,6333],{},"zoom_code",[31,6335,6336],{},"null","かで確認しています。",[13,6339,6340],{},"連携が済んでいない場合はzoomの連携確認画面へ飛ばすリンクボタンを表示させています。",[13,6342,6343,6344,6347,6348,6353],{},"この",[31,6345,6346],{},"$zoomOuthLink","の作成は",[725,6349,6352],{"href":6350,"rel":6351},"https:\u002F\u002Fmarketplace.zoom.us\u002Fdocs\u002Fguides\u002Fauth\u002Foauth#getting-access-token",[729],"こちらのドキュメント","にある通り、ルールがあります。",[24,6355,6357],{"className":2394,"code":6356,"filename":6179,"language":882,"meta":33,"style":33},"$zoomOuthLink = 'https:\u002F\u002Fzoom.us\u002Foauth\u002Fauthorize?'.http_build_query([\n    'response_type'=>'code',\n    'redirect_uri'=>env('APP_URL').'\u002Fzoomoatuh\u002Fcheck',\n    'client_id'=>env('ZOOM_CLIENT_ID'),\n]);\n",[31,6358,6359,6364,6369,6374,6379],{"__ignoreMap":33},[46,6360,6361],{"class":48,"line":49},[46,6362,6363],{},"$zoomOuthLink = 'https:\u002F\u002Fzoom.us\u002Foauth\u002Fauthorize?'.http_build_query([\n",[46,6365,6366],{"class":48,"line":70},[46,6367,6368],{},"    'response_type'=>'code',\n",[46,6370,6371],{"class":48,"line":82},[46,6372,6373],{},"    'redirect_uri'=>env('APP_URL').'\u002Fzoomoatuh\u002Fcheck',\n",[46,6375,6376],{"class":48,"line":91},[46,6377,6378],{},"    'client_id'=>env('ZOOM_CLIENT_ID'),\n",[46,6380,6381],{"class":48,"line":102},[46,6382,6383],{},"]);\n",[13,6385,6386,6387,6390],{},"今はOAuthのステップの中で「ユーザー認証」というユーザーへ「このアプリ（Laravel）とzoomを連携してもいい？」とzoomが聞いている段階です。そのユーザー認証にはまず",[31,6388,6389],{},"https:\u002F\u002Fzoom.us\u002Foauth\u002Fauthorize","へGETで管理者本人がアクセスします。",[13,6392,6393,6394,1448,6397,1448,6400,6403],{},"その際にGETパラメータに",[31,6395,6396],{},"response_type",[31,6398,6399],{},"redirect_uri",[31,6401,6402],{},"client_id","を入力します。",[808,6405,6407],{"className":6406},[811,812],"\nresponse_typeAccess response type being requested. The supported authorization workflow requires the value `code`.\n",[13,6409,6410,6411,6413,6414,6416,6417,6419,6420,6425,6426,6428,6429,1612,6431,6433,6434,6437],{},"とある様に",[31,6412,6396],{},"には",[31,6415,31],{},"という文字を設定します。そして",[31,6418,6399],{},"はzoom ",[725,6421,6424],{"href":6422,"rel":6423},"https:\u002F\u002Fjun-app.com\u002Fzoom-api-laravel\u002F#zoom-redirect-url",[729],"アプリ作成時にも設定した通りのURL","を入力しますので、",[31,6427,5911],{},"を設定。",[31,6430,6402],{},[31,6432,1596],{},"で設定値を",[31,6435,6436],{},"env()","で呼び出します。",[13,6439,6440],{},"それらをGETパラメータとして一つのURLにまとめます。以下の様な感じです。",[24,6442,6445],{"className":6443,"code":6444,"language":29},[27],"https:\u002F\u002Fzoom.us\u002Foauth\u002Fauthorize?response_type=code&redirect_uri=http:\u002F\u002Flocalhost:9000\u002Fzoomauth\u002Fcheck&client_id=clientid\n",[31,6446,6444],{"__ignoreMap":33},[13,6448,6449],{},"予めサーバーサイドで作っておき、ボタンのリンクにはめ込んでおきます。画面では以下の様に表示されます。",[1692,6451],{":src":6452,":width":1695},"'_mix\u002Fsch-2021-01-10-18.08.06.png'",[352,6454,6456],{"id":6455},"認証画面からのリダイレクトurlでの処理","認証画面からのリダイレクトURLでの処理",[13,6458,6459],{},"ボタンをクリックすると以下の画面が表示されます。（正確には承認画面のGETを叩く）",[1692,6461],{":src":6462,":width":1928,":center":1929},"'_mix\u002Fzoom_approve.jpeg'",[13,6464,6465,6466,6468,6469,6471],{},"管理者に対してこのアプリが自身のzoomアカウントに対して、何をするのかが書かれています。管理者はこの「認可」を押すと、",[31,6467,6399],{},"のリダイレクト先に飛ばされます。OAuthではこのリダイレクト先の処理が大切です！",[31,6470,5911],{},"のコントローラーは以下の通りです。（ビューはなし）",[24,6473,6475],{"className":2394,"code":6474,"filename":6179,"language":882,"meta":33,"style":33},"public function zoomOauth(Request $request){\n    $user = auth()->user();\n\n    if($user->zoom_code==null){\n        $code = $request['code'];\n\n        $user->zoom_code = $code;\n        $user->save();\n\n        $basic = base64_encode(env('ZOOM_CLIENT_ID').':'.env('ZOOM_CLIENT_SECRET'));\n        $client = new \\GuzzleHttp\\Client([\n            'headers' => ['Authorization' => 'Basic '.$basic]\n        ]);\n        $res = $client->request('POST','https:\u002F\u002Fzoom.us\u002Foauth\u002Ftoken',[\n            'query' => [\n                'grant_type'=>'authorization_code',\n                'code'=>$code,\n                'redirect_uri'=>'http:\u002F\u002Flocalhost:9000\u002Fzoomoatuh\u002Fcheck'\n            ]\n        ]);\n        $result = json_decode($res->getBody()->getContents());\n\n        $user->access_token= $result->access_token;\n        $user->refresh_token= $result->refresh_token;\n        $unixTime = time();\n        $user->zoom_expires_in= date(\"Y-m-d H:i:s\",$unixTime+$result->expires_in);\n        $user->save();\n\n        return redirect()->route('amdin')->with([\n            'noZoomCode'=>false,\n            'oauthSuccess'=>true\n        ]);\n    }\n}\n",[31,6476,6477,6482,6486,6490,6495,6500,6504,6509,6514,6518,6523,6528,6533,6537,6542,6547,6552,6557,6562,6567,6571,6576,6580,6585,6590,6595,6600,6604,6608,6613,6618,6623,6627,6631],{"__ignoreMap":33},[46,6478,6479],{"class":48,"line":49},[46,6480,6481],{},"public function zoomOauth(Request $request){\n",[46,6483,6484],{"class":48,"line":70},[46,6485,6191],{},[46,6487,6488],{"class":48,"line":82},[46,6489,408],{"emptyLinePlaceholder":407},[46,6491,6492],{"class":48,"line":91},[46,6493,6494],{},"    if($user->zoom_code==null){\n",[46,6496,6497],{"class":48,"line":102},[46,6498,6499],{},"        $code = $request['code'];\n",[46,6501,6502],{"class":48,"line":112},[46,6503,408],{"emptyLinePlaceholder":407},[46,6505,6506],{"class":48,"line":121},[46,6507,6508],{},"        $user->zoom_code = $code;\n",[46,6510,6511],{"class":48,"line":131},[46,6512,6513],{},"        $user->save();\n",[46,6515,6516],{"class":48,"line":139},[46,6517,408],{"emptyLinePlaceholder":407},[46,6519,6520],{"class":48,"line":147},[46,6521,6522],{},"        $basic = base64_encode(env('ZOOM_CLIENT_ID').':'.env('ZOOM_CLIENT_SECRET'));\n",[46,6524,6525],{"class":48,"line":157},[46,6526,6527],{},"        $client = new \\GuzzleHttp\\Client([\n",[46,6529,6530],{"class":48,"line":171},[46,6531,6532],{},"            'headers' => ['Authorization' => 'Basic '.$basic]\n",[46,6534,6535],{"class":48,"line":183},[46,6536,3391],{},[46,6538,6539],{"class":48,"line":195},[46,6540,6541],{},"        $res = $client->request('POST','https:\u002F\u002Fzoom.us\u002Foauth\u002Ftoken',[\n",[46,6543,6544],{"class":48,"line":206},[46,6545,6546],{},"            'query' => [\n",[46,6548,6549],{"class":48,"line":4},[46,6550,6551],{},"                'grant_type'=>'authorization_code',\n",[46,6553,6554],{"class":48,"line":223},[46,6555,6556],{},"                'code'=>$code,\n",[46,6558,6559],{"class":48,"line":231},[46,6560,6561],{},"                'redirect_uri'=>'http:\u002F\u002Flocalhost:9000\u002Fzoomoatuh\u002Fcheck'\n",[46,6563,6564],{"class":48,"line":242},[46,6565,6566],{},"            ]\n",[46,6568,6569],{"class":48,"line":253},[46,6570,3391],{},[46,6572,6573],{"class":48,"line":264},[46,6574,6575],{},"        $result = json_decode($res->getBody()->getContents());\n",[46,6577,6578],{"class":48,"line":275},[46,6579,408],{"emptyLinePlaceholder":407},[46,6581,6582],{"class":48,"line":284},[46,6583,6584],{},"        $user->access_token= $result->access_token;\n",[46,6586,6587],{"class":48,"line":296},[46,6588,6589],{},"        $user->refresh_token= $result->refresh_token;\n",[46,6591,6592],{"class":48,"line":305},[46,6593,6594],{},"        $unixTime = time();\n",[46,6596,6597],{"class":48,"line":313},[46,6598,6599],{},"        $user->zoom_expires_in= date(\"Y-m-d H:i:s\",$unixTime+$result->expires_in);\n",[46,6601,6602],{"class":48,"line":323},[46,6603,6513],{},[46,6605,6606],{"class":48,"line":529},[46,6607,408],{"emptyLinePlaceholder":407},[46,6609,6610],{"class":48,"line":535},[46,6611,6612],{},"        return redirect()->route('amdin')->with([\n",[46,6614,6615],{"class":48,"line":540},[46,6616,6617],{},"            'noZoomCode'=>false,\n",[46,6619,6620],{"class":48,"line":546},[46,6621,6622],{},"            'oauthSuccess'=>true\n",[46,6624,6625],{"class":48,"line":551},[46,6626,3391],{},[46,6628,6629],{"class":48,"line":557},[46,6630,2723],{},[46,6632,6633],{"class":48,"line":562},[46,6634,2752],{},[13,6636,6637,6639,6640,6642,6643,6646],{},[31,6638,6389],{}," から ",[31,6641,5911],{}," へリダイレクトされると自動的にGETパラメータに",[31,6644,6645],{},"?code=~~~~","というものが付与されています。このcodeは後の認証に必要になります。",[13,6648,6649],{},"リダイレクトURLからcodeの値を取得します。いったんDBに保存してから、実際にAPIへリクエストするのに必要なアクセストークンの取得処理を行います。そこで以下の様なリクエストを行います。",[24,6651,6653],{"className":2394,"code":6652,"filename":6179,"language":882,"meta":33,"style":33},"$basic = base64_encode(env('ZOOM_CLIENT_ID').':'.env('ZOOM_CLIENT_SECRET'));\n$client = new \\GuzzleHttp\\Client([\n    'headers' => ['Authorization' => 'Basic '.$basic]\n]);\n$res = $client->request('POST','https:\u002F\u002Fzoom.us\u002Foauth\u002Ftoken',[\n    'query' => [\n        'grant_type'=>'authorization_code',\n        'code'=>$code,\n        'redirect_uri'=>'http:\u002F\u002Flocalhost:9000\u002Fzoomoatuh\u002Fcheck'\n    ]\n]);\n",[31,6654,6655,6660,6665,6670,6674,6679,6684,6689,6694,6699,6704],{"__ignoreMap":33},[46,6656,6657],{"class":48,"line":49},[46,6658,6659],{},"$basic = base64_encode(env('ZOOM_CLIENT_ID').':'.env('ZOOM_CLIENT_SECRET'));\n",[46,6661,6662],{"class":48,"line":70},[46,6663,6664],{},"$client = new \\GuzzleHttp\\Client([\n",[46,6666,6667],{"class":48,"line":82},[46,6668,6669],{},"    'headers' => ['Authorization' => 'Basic '.$basic]\n",[46,6671,6672],{"class":48,"line":91},[46,6673,6383],{},[46,6675,6676],{"class":48,"line":102},[46,6677,6678],{},"$res = $client->request('POST','https:\u002F\u002Fzoom.us\u002Foauth\u002Ftoken',[\n",[46,6680,6681],{"class":48,"line":112},[46,6682,6683],{},"    'query' => [\n",[46,6685,6686],{"class":48,"line":121},[46,6687,6688],{},"        'grant_type'=>'authorization_code',\n",[46,6690,6691],{"class":48,"line":131},[46,6692,6693],{},"        'code'=>$code,\n",[46,6695,6696],{"class":48,"line":139},[46,6697,6698],{},"        'redirect_uri'=>'http:\u002F\u002Flocalhost:9000\u002Fzoomoatuh\u002Fcheck'\n",[46,6700,6701],{"class":48,"line":147},[46,6702,6703],{},"    ]\n",[46,6705,6706],{"class":48,"line":157},[46,6707,6383],{},[13,6709,6710,6711,6714,6715,6718],{},"Zoomにも書いてある通りの処理ですが、アクセストークンを得る ",[31,6712,6713],{},"https:\u002F\u002Fzoom.us\u002Foauth\u002Ftoken"," というルートにアクセスするときは、まずリクエストヘッダーを付与します。リクエストヘッダーは ",[31,6716,6717],{},"'headers' => ['Authorization' => 'Basic '.$basic]"," です。ここに client IDとclient secretをコロンで付けて一つの文字列にし、それをbase64エンコードをします。つまり明示的に処理を表示すると以下の様な感じです。",[24,6720,6723],{"className":6721,"code":6722,"language":29},[27],"client_id:cilent_secret \u002F\u002Fこれで一行の文字列\n↓\nこの値を64base encode\n↓\nsi84nf7435934jdfsdfi... \u002F\u002Fエンコード化された文字。これを送る\n",[31,6724,6722],{"__ignoreMap":33},[13,6726,6727,6728,6730,6731,6734],{},"そしてそれをリクエストヘッダーに付与します。",[31,6729,6717],{}," これを文字列として表示すると、",[31,6732,6733],{},"Authorization: Basic si84nf7435934jdfsdfi…"," みたいな感じです。ちなみに Basicとエンコード文字の間は半角が空いていますので注意。",[13,6736,6737],{},"リクエストヘッダーを付けたら先ほどと似た感じでGETパラメータを以下の様に設定します。",[24,6739,6741],{"className":2394,"code":6740,"language":882,"meta":33,"style":33},"$res = $client->request('POST','https:\u002F\u002Fzoom.us\u002Foauth\u002Ftoken',[\n    'query' => [\n        'grant_type'=>'authorization_code',\n        'code'=>$code,\n        'redirect_uri'=>'http:\u002F\u002Flocalhost:9000\u002Fzoomoatuh\u002Fcheck'\n    ]\n])\n",[31,6742,6743,6747,6751,6755,6759,6763,6767],{"__ignoreMap":33},[46,6744,6745],{"class":48,"line":49},[46,6746,6678],{},[46,6748,6749],{"class":48,"line":70},[46,6750,6683],{},[46,6752,6753],{"class":48,"line":82},[46,6754,6688],{},[46,6756,6757],{"class":48,"line":91},[46,6758,6693],{},[46,6760,6761],{"class":48,"line":102},[46,6762,6698],{},[46,6764,6765],{"class":48,"line":112},[46,6766,6703],{},[46,6768,6769],{"class":48,"line":121},[46,6770,6771],{},"])\n",[13,6773,6774,3514,6777,6780,6781,6784,6785,6787,6788,6790],{},[31,6775,6776],{},"grant_type",[31,6778,6779],{},"authorization_code","という文字とし、codeにはリダイレクト時についてきた値である",[31,6782,6783],{},"$request['code']","を用います。",[31,6786,6399],{},"は先ほどと同じです。（",[31,6789,6399],{},"を別にすると認証が通りません！）",[13,6792,6793],{},"これでセットアップが完了です。実際のURLとしては以下の感じです。",[24,6795,6798],{"className":6796,"code":6797,"language":29},[27],"https:\u002F\u002Fzoom.us\u002Foauth\u002Ftoken?grant_type=authorization_code&code=~~~~~&redirect_uri=http:\u002F\u002Flocalhost:9000\u002Fzoomoatuh\u002Fcheck\n（そして直接は見えないですが、リクエストヘッダーには 「Authorization: Basic si84nf7435934jdfsdfi…」 という値がついています！\n",[31,6799,6797],{"__ignoreMap":33},[13,6801,6802,6803,6806],{},"リクエストが成功するとアクセストークン を含んだレスポンスがJSONで戻ってきます。それを展開してDBへ保存します。",[31,6804,6805],{},"zoom_expires_in","は現在時刻と足し合わせて、期限切れ時刻を計算してから格納しています。",[24,6808,6810],{"className":2394,"code":6809,"language":882,"meta":33,"style":33},"\u002F*\n$resultの中身の例\n{\n    \"access_token\": \"eyJhbGciOiJIUz...\",\n    \"token_type\": \"bearer\",\n    \"refresh_token\": \"eyJhbGciOiJI..\",\n    \"expires_in\": 3599,\n    \"scope\": \"user:read\"\n}\n*\u002F\n\n$result = json_decode($res->getBody()->getContents());\n\n$user->access_token= $result->access_token;\n$user->refresh_token= $result->refresh_token;\n$unixTime = time();\n$user->zoom_expires_in= date(\"Y-m-d H:i:s\",$unixTime+$result->expires_in);\n$user->save();\n\nreturn redirect()->route('amdin')->with([\n         'noZoomCode'=>false,\n         'oauthSuccess'=>true\n]);\n",[31,6811,6812,6816,6821,6825,6830,6835,6840,6845,6850,6854,6858,6862,6867,6871,6876,6881,6886,6891,6896,6900,6905,6910,6915],{"__ignoreMap":33},[46,6813,6814],{"class":48,"line":49},[46,6815,2411],{},[46,6817,6818],{"class":48,"line":70},[46,6819,6820],{},"$resultの中身の例\n",[46,6822,6823],{"class":48,"line":82},[46,6824,2572],{},[46,6826,6827],{"class":48,"line":91},[46,6828,6829],{},"    \"access_token\": \"eyJhbGciOiJIUz...\",\n",[46,6831,6832],{"class":48,"line":102},[46,6833,6834],{},"    \"token_type\": \"bearer\",\n",[46,6836,6837],{"class":48,"line":112},[46,6838,6839],{},"    \"refresh_token\": \"eyJhbGciOiJI..\",\n",[46,6841,6842],{"class":48,"line":121},[46,6843,6844],{},"    \"expires_in\": 3599,\n",[46,6846,6847],{"class":48,"line":131},[46,6848,6849],{},"    \"scope\": \"user:read\"\n",[46,6851,6852],{"class":48,"line":139},[46,6853,2752],{},[46,6855,6856],{"class":48,"line":147},[46,6857,2444],{},[46,6859,6860],{"class":48,"line":157},[46,6861,408],{"emptyLinePlaceholder":407},[46,6863,6864],{"class":48,"line":171},[46,6865,6866],{},"$result = json_decode($res->getBody()->getContents());\n",[46,6868,6869],{"class":48,"line":183},[46,6870,408],{"emptyLinePlaceholder":407},[46,6872,6873],{"class":48,"line":195},[46,6874,6875],{},"$user->access_token= $result->access_token;\n",[46,6877,6878],{"class":48,"line":206},[46,6879,6880],{},"$user->refresh_token= $result->refresh_token;\n",[46,6882,6883],{"class":48,"line":4},[46,6884,6885],{},"$unixTime = time();\n",[46,6887,6888],{"class":48,"line":223},[46,6889,6890],{},"$user->zoom_expires_in= date(\"Y-m-d H:i:s\",$unixTime+$result->expires_in);\n",[46,6892,6893],{"class":48,"line":231},[46,6894,6895],{},"$user->save();\n",[46,6897,6898],{"class":48,"line":242},[46,6899,408],{"emptyLinePlaceholder":407},[46,6901,6902],{"class":48,"line":253},[46,6903,6904],{},"return redirect()->route('amdin')->with([\n",[46,6906,6907],{"class":48,"line":264},[46,6908,6909],{},"         'noZoomCode'=>false,\n",[46,6911,6912],{"class":48,"line":275},[46,6913,6914],{},"         'oauthSuccess'=>true\n",[46,6916,6917],{"class":48,"line":284},[46,6918,6383],{},[13,6920,6921],{},"そして最後は管理画面へリダイレクトしてあげます。管理者からしてみるとzoomの画面で「許可」を押すと元のサイトに戻って、グルグルローディングしてるなーと思ったら管理画面に戻ってきた感覚となります。実際の画面ではzoom連携の警告がなくなり以下の様な感じになります。",[1692,6923],{":src":6924,":width":1695},"'_mix\u002Fsch-2021-01-10-20.30.08-768x155.png'",[13,6926,6927],{},"これでOAuthは完了です。意外と簡単ですね。access_tokenは1時間で切れてしまうので、APIアクセスの際は期限切れでないかをチェック、そしてダメならtokenをリフレッシュする機能が必要となります。次はaccess_tokenのチェック方ら連携した人のユーザー情報を取得するとともに、リフレッシュ機能を実装します。",[20,6929,6930],{"id":6930},"ユーザー情報を取得",[13,6932,6933],{},"ミーティングを作成したりなどはユーザーIDが必要となります。他のAPIで使用するのでコントローラー内の共通メソッドとして分離しておきましょう。以下の様にします。",[24,6935,6938],{"className":2394,"code":6936,"filename":6937,"language":882,"meta":33,"style":33},"class ZoomApiController extends Controller\n{\n    \u002F\u002F\n    protected function me(){\n        $user = auth()->user();\n        $client = new \\GuzzleHttp\\Client([\n            'headers' => ['Authorization' => 'Bearer '.$user->access_token]\n        ]);\n        $res = $client->request('GET','https:\u002F\u002Fapi.zoom.us\u002Fv2\u002Fusers\u002Fme');\n        $result = json_decode($res->getBody()->getContents());\n        \u002F\u002F dd($result);\n        return $result;\n    }\n...\n}\n","app\u002FHttp\u002FControllers\u002FZoomApiController.php",[31,6939,6940,6945,6949,6954,6959,6964,6968,6973,6977,6982,6986,6991,6996,7000,7004],{"__ignoreMap":33},[46,6941,6942],{"class":48,"line":49},[46,6943,6944],{},"class ZoomApiController extends Controller\n",[46,6946,6947],{"class":48,"line":70},[46,6948,2572],{},[46,6950,6951],{"class":48,"line":82},[46,6952,6953],{},"    \u002F\u002F\n",[46,6955,6956],{"class":48,"line":91},[46,6957,6958],{},"    protected function me(){\n",[46,6960,6961],{"class":48,"line":102},[46,6962,6963],{},"        $user = auth()->user();\n",[46,6965,6966],{"class":48,"line":112},[46,6967,6527],{},[46,6969,6970],{"class":48,"line":121},[46,6971,6972],{},"            'headers' => ['Authorization' => 'Bearer '.$user->access_token]\n",[46,6974,6975],{"class":48,"line":131},[46,6976,3391],{},[46,6978,6979],{"class":48,"line":139},[46,6980,6981],{},"        $res = $client->request('GET','https:\u002F\u002Fapi.zoom.us\u002Fv2\u002Fusers\u002Fme');\n",[46,6983,6984],{"class":48,"line":147},[46,6985,6575],{},[46,6987,6988],{"class":48,"line":157},[46,6989,6990],{},"        \u002F\u002F dd($result);\n",[46,6992,6993],{"class":48,"line":171},[46,6994,6995],{},"        return $result;\n",[46,6997,6998],{"class":48,"line":183},[46,6999,2723],{},[46,7001,7002],{"class":48,"line":195},[46,7003,2851],{},[46,7005,7006],{"class":48,"line":206},[46,7007,2752],{},[13,7009,7010,7011,7013,7014,7017,7018,7021],{},"ユーザーテーブルに",[31,7012,3790],{},"があるので",[31,7015,7016],{},"$user = auth()->user();","で現在のログインユーザーを取り出して、",[31,7019,7020],{},"$user->access_token","にて出力します。",[13,7023,7024,7026,7027,7030,7031,7034],{},[31,7025,3790],{},"があればAPIへのアクセスはリクエストヘッダーにトークンを入れるだけでアクセスできます。リクエストヘッダーは",[31,7028,7029],{},"'headers' => ['Authorization' => 'Bearer '.$user->access_token]","です。さっきはBasicだったのが、",[31,7032,7033],{},"Bearer（ベアラー）","になっていますのでタイポに注意。",[13,7036,7037,7040],{},[31,7038,7039],{},"dd($result)","を有効にして出力してみると",[1692,7042],{":src":7043,":width":1928,":center":1929},"'_mix\u002Fzoom_me.jpeg'",[13,7045,7046],{},"こんな感じのJSONが返ってきますので、適宜IDなどを使用します。",[20,7048,7049],{"id":7049},"リフレッシュ機能を実装",[13,7051,7052],{},"access tokenは1時間しか持たないのでもし期限切れになった際にはリフレッシュトークンを使用してトークンを更新します。ちなみにリフレッシュトークンの有効期限は15年です笑私の場合は以下の様に実装しました。",[24,7054,7056],{"className":2394,"code":7055,"filename":6937,"language":882,"meta":33,"style":33},"protected function checkRefresh(){\n    $user = auth()->user();\n    $token_expires =  new \\DateTime($user->zoom_expires_in);\n    $now = new \\DateTime();\n\n    if($now >= $token_expires){\n        $basic = base64_encode(env('ZOOM_CLIENT_ID').':'.env('ZOOM_CLIENT_SECRET'));\n        $client = new \\GuzzleHttp\\Client([\n            'headers' => ['Authorization' => 'Basic '.$basic]\n        ]);\n        $res = $client->request('POST','https:\u002F\u002Fzoom.us\u002Foauth\u002Ftoken',[\n            'query' => [\n                'grant_type'=>'refresh_token',\n                'refresh_token'=>$user->refresh_token\n            ]\n        ]);\n        $result = json_decode($res->getBody()->getContents());\n\n        $user->access_token= $result->access_token;\n        $user->refresh_token= $result->access_token;\n        $unixTime = time();\n        $user->zoom_expires_in= date(\"Y-m-d H:i:s\",$unixTime+$result->expires_in);\n        $user->save();\n        return $user;\n    }\n    return $user;\n}\n",[31,7057,7058,7063,7067,7072,7077,7081,7086,7090,7094,7098,7102,7106,7110,7115,7120,7124,7128,7132,7136,7140,7145,7149,7153,7157,7162,7166,7171],{"__ignoreMap":33},[46,7059,7060],{"class":48,"line":49},[46,7061,7062],{},"protected function checkRefresh(){\n",[46,7064,7065],{"class":48,"line":70},[46,7066,6191],{},[46,7068,7069],{"class":48,"line":82},[46,7070,7071],{},"    $token_expires =  new \\DateTime($user->zoom_expires_in);\n",[46,7073,7074],{"class":48,"line":91},[46,7075,7076],{},"    $now = new \\DateTime();\n",[46,7078,7079],{"class":48,"line":102},[46,7080,408],{"emptyLinePlaceholder":407},[46,7082,7083],{"class":48,"line":112},[46,7084,7085],{},"    if($now >= $token_expires){\n",[46,7087,7088],{"class":48,"line":121},[46,7089,6522],{},[46,7091,7092],{"class":48,"line":131},[46,7093,6527],{},[46,7095,7096],{"class":48,"line":139},[46,7097,6532],{},[46,7099,7100],{"class":48,"line":147},[46,7101,3391],{},[46,7103,7104],{"class":48,"line":157},[46,7105,6541],{},[46,7107,7108],{"class":48,"line":171},[46,7109,6546],{},[46,7111,7112],{"class":48,"line":183},[46,7113,7114],{},"                'grant_type'=>'refresh_token',\n",[46,7116,7117],{"class":48,"line":195},[46,7118,7119],{},"                'refresh_token'=>$user->refresh_token\n",[46,7121,7122],{"class":48,"line":206},[46,7123,6566],{},[46,7125,7126],{"class":48,"line":4},[46,7127,3391],{},[46,7129,7130],{"class":48,"line":223},[46,7131,6575],{},[46,7133,7134],{"class":48,"line":231},[46,7135,408],{"emptyLinePlaceholder":407},[46,7137,7138],{"class":48,"line":242},[46,7139,6584],{},[46,7141,7142],{"class":48,"line":253},[46,7143,7144],{},"        $user->refresh_token= $result->access_token;\n",[46,7146,7147],{"class":48,"line":264},[46,7148,6594],{},[46,7150,7151],{"class":48,"line":275},[46,7152,6599],{},[46,7154,7155],{"class":48,"line":284},[46,7156,6513],{},[46,7158,7159],{"class":48,"line":296},[46,7160,7161],{},"        return $user;\n",[46,7163,7164],{"class":48,"line":305},[46,7165,2723],{},[46,7167,7168],{"class":48,"line":313},[46,7169,7170],{},"    return $user;\n",[46,7172,7173],{"class":48,"line":323},[46,7174,2752],{},[13,7176,7177],{},"APIリクエストごとにトークンをチェックできる様にしています。有効期限をテーブルに保存してあるのでそれを比較して、現在時刻が有効期限を過ぎていたらリフレッシュ処理を行う様します。そして戻ってきたトークンをテーブルで更新させ、ユーザーモデルをreturnします。",[13,7179,7180],{},"有効期限ないであればそのままユーザーモデルを返却するという感じです。",[20,7182,7184],{"id":7183},"apiからミーティングを作成","APIからミーティングを作成",[13,7186,7187],{},"それではフォームから入力された値を元にミーティングを作れる様にしましょう。メール通知は機能してはいませんが、実装したコードはコメントアウトさせてますので、頑張れる人はメールも実装してみてください。",[13,7189,7190],{},"まずフォームから取得したミーティング情報を格納するテーブルを以下の様に定義して、マイグレーションを実施します。",[352,7192,7194],{"id":7193},"フォームミーティング管理テーブルを作成","フォーム＆ミーティング管理テーブルを作成",[24,7196,7199],{"className":2394,"code":7197,"filename":7198,"language":882,"meta":33,"style":33},"\u003C?php\n\nuse Illuminate\\Database\\Migrations\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\nuse Illuminate\\Support\\Facades\\DB;\n\nclass CreateMeeting extends Migration\n{\n    \u002F**\n     * Run the migrations.\n     *\n     * @return void\n     *\u002F\n    public function up()\n    {\n        Schema::create('meeting', function (Blueprint $table) {\n            $table->bigIncrements('id');\n            $table->string('name');\n            $table->string('company_name');\n            $table->string('email');\n            $table->longText('content');\n\n            $table->timestamp('start_at', 0)->default(DB::raw('CURRENT_TIMESTAMP'));\n            $table->longText('hash');\n            $table->boolean('is_canceled');\n\n            $table->longText('zoom_meeting_id');\n            $table->longText('zoom_join_url');\n            $table->longText('zoom_start_url');\n            $table->longText('zoom_password');\n            $table->timestamps();\n        });\n    }\n\n    \u002F**\n     * Reverse the migrations.\n     *\n     * @return void\n     *\u002F\n    public function down()\n    {\n        Schema::dropIfExists('meeting');\n    }\n}\n","database\u002Fmigrations\u002F2021_01_10_005145_create_meeting.php",[31,7200,7201,7205,7209,7214,7219,7224,7229,7233,7238,7242,7246,7251,7255,7259,7263,7268,7272,7277,7282,7287,7292,7297,7302,7306,7311,7316,7321,7325,7330,7335,7340,7345,7350,7355,7359,7363,7367,7372,7376,7380,7384,7389,7393,7398,7402],{"__ignoreMap":33},[46,7202,7203],{"class":48,"line":49},[46,7204,2525],{},[46,7206,7207],{"class":48,"line":70},[46,7208,408],{"emptyLinePlaceholder":407},[46,7210,7211],{"class":48,"line":82},[46,7212,7213],{},"use Illuminate\\Database\\Migrations\\Migration;\n",[46,7215,7216],{"class":48,"line":91},[46,7217,7218],{},"use Illuminate\\Database\\Schema\\Blueprint;\n",[46,7220,7221],{"class":48,"line":102},[46,7222,7223],{},"use Illuminate\\Support\\Facades\\Schema;\n",[46,7225,7226],{"class":48,"line":112},[46,7227,7228],{},"use Illuminate\\Support\\Facades\\DB;\n",[46,7230,7231],{"class":48,"line":121},[46,7232,408],{"emptyLinePlaceholder":407},[46,7234,7235],{"class":48,"line":131},[46,7236,7237],{},"class CreateMeeting extends Migration\n",[46,7239,7240],{"class":48,"line":139},[46,7241,2572],{},[46,7243,7244],{"class":48,"line":147},[46,7245,2586],{},[46,7247,7248],{"class":48,"line":157},[46,7249,7250],{},"     * Run the migrations.\n",[46,7252,7253],{"class":48,"line":171},[46,7254,2596],{},[46,7256,7257],{"class":48,"line":183},[46,7258,3060],{},[46,7260,7261],{"class":48,"line":195},[46,7262,2606],{},[46,7264,7265],{"class":48,"line":206},[46,7266,7267],{},"    public function up()\n",[46,7269,7270],{"class":48,"line":4},[46,7271,2713],{},[46,7273,7274],{"class":48,"line":223},[46,7275,7276],{},"        Schema::create('meeting', function (Blueprint $table) {\n",[46,7278,7279],{"class":48,"line":231},[46,7280,7281],{},"            $table->bigIncrements('id');\n",[46,7283,7284],{"class":48,"line":242},[46,7285,7286],{},"            $table->string('name');\n",[46,7288,7289],{"class":48,"line":253},[46,7290,7291],{},"            $table->string('company_name');\n",[46,7293,7294],{"class":48,"line":264},[46,7295,7296],{},"            $table->string('email');\n",[46,7298,7299],{"class":48,"line":275},[46,7300,7301],{},"            $table->longText('content');\n",[46,7303,7304],{"class":48,"line":284},[46,7305,408],{"emptyLinePlaceholder":407},[46,7307,7308],{"class":48,"line":296},[46,7309,7310],{},"            $table->timestamp('start_at', 0)->default(DB::raw('CURRENT_TIMESTAMP'));\n",[46,7312,7313],{"class":48,"line":305},[46,7314,7315],{},"            $table->longText('hash');\n",[46,7317,7318],{"class":48,"line":313},[46,7319,7320],{},"            $table->boolean('is_canceled');\n",[46,7322,7323],{"class":48,"line":323},[46,7324,408],{"emptyLinePlaceholder":407},[46,7326,7327],{"class":48,"line":529},[46,7328,7329],{},"            $table->longText('zoom_meeting_id');\n",[46,7331,7332],{"class":48,"line":535},[46,7333,7334],{},"            $table->longText('zoom_join_url');\n",[46,7336,7337],{"class":48,"line":540},[46,7338,7339],{},"            $table->longText('zoom_start_url');\n",[46,7341,7342],{"class":48,"line":546},[46,7343,7344],{},"            $table->longText('zoom_password');\n",[46,7346,7347],{"class":48,"line":551},[46,7348,7349],{},"            $table->timestamps();\n",[46,7351,7352],{"class":48,"line":557},[46,7353,7354],{},"        });\n",[46,7356,7357],{"class":48,"line":562},[46,7358,2723],{},[46,7360,7361],{"class":48,"line":568},[46,7362,408],{"emptyLinePlaceholder":407},[46,7364,7365],{"class":48,"line":573},[46,7366,2586],{},[46,7368,7369],{"class":48,"line":579},[46,7370,7371],{},"     * Reverse the migrations.\n",[46,7373,7374],{"class":48,"line":584},[46,7375,2596],{},[46,7377,7378],{"class":48,"line":590},[46,7379,3060],{},[46,7381,7382],{"class":48,"line":595},[46,7383,2606],{},[46,7385,7386],{"class":48,"line":601},[46,7387,7388],{},"    public function down()\n",[46,7390,7391],{"class":48,"line":606},[46,7392,2713],{},[46,7394,7395],{"class":48,"line":611},[46,7396,7397],{},"        Schema::dropIfExists('meeting');\n",[46,7399,7400],{"class":48,"line":616},[46,7401,2723],{},[46,7403,7404],{"class":48,"line":622},[46,7405,2752],{},[13,7407,7408,7411],{},[31,7409,7410],{},"start_at","はお客さんが入力した希望zoom開催時間です。本来は管理側の都合を合わせた機能にすべきですが今回はプロトタイプなので割愛。フロントからはdatetime形式で来たものを受け取ります。",[13,7413,7414,7417],{},[31,7415,7416],{},"hash","は後でお客さんがミーティングをキャンセルしたり、時間を変更するときにアクセスするURLに付けるランダムな文字列です。一種のパスワードみたいなものです。後ほど使い方を解説します。",[13,7419,7420,7421,1440,7424,7427],{},"そしてzoomへCreate Meeting API を送信すると、ミーティングURLなどが返ってきますのでそれを",[31,7422,7423],{},"zoom_join_url",[31,7425,7426],{},"zoom_start_url","に入れておきます 。他にフォームの内容を格納する箇所を定義してマイグレーションします。",[13,7429,7430],{},"フォームの画面以下の様に実装しました。",[1692,7432],{":src":5736,":width":1695},[352,7434,7436],{"id":7435},"バリデーションとmeeting-apiをリクエスト","バリデーションとmeeting apiをリクエスト",[13,7438,7439],{},"フォームのビューがPOSTリクエストを受けたら以下のコントローラーが実行されます。",[24,7441,7443],{"className":2394,"code":7442,"filename":6937,"language":882,"meta":33,"style":33},"public function createMeeting(Request $request){\n    $validator = Validator::make($request->all(),[\n        'email'=>'required|email:rfc',\n        'yourname'=>'required',\n        'companyname'=>'required',\n        'startAt'=>'date|required',\n        'content'=>'required|max:1000',\n    ]);\n\n    $error = $validator->getMessageBag()->toArray();\n    \n    \u002F\u002Fバリデーションエラーがあれば元の画面へ\n    if ($validator->fails()) {\n        return view('form',compact('error'));\n    }\n    \n    $user = $this->checkRefresh();\n    $user = auth()->user();\n\n    $zoom_user = $this->me();\n\n    $url = 'https:\u002F\u002Fapi.zoom.us\u002Fv2\u002Fusers\u002F'.$zoom_user->id.'\u002Fmeetings';\n    $client = new \\GuzzleHttp\\Client([\n        'headers' => [\n            'Authorization' => 'Bearer '.$user->access_token,\n            'Content-Type'=>'application\u002Fjson'\n        ],\n    ]);\n\n    $topic = $request->companyname.' '.$request->yourname.'様 ご相談';\n    $meeting_password = substr(base_convert(bin2hex(openssl_random_pseudo_bytes(9)),16,36),0,9);\n    $res = $client->request('POST',$url,[\n        \\GuzzleHttp\\RequestOptions::JSON => [\n            'topic'=>$topic,\n            'type'=>2,\n            'start_time'=>$request->startAt,\n            'password'=>$meeting_password\n        ]\n    ]);\n    $result = json_decode($res->getBody()->getContents());\n\n    $meeting = new Meeting();\n    $meeting->name=$request->yourname;\n    $meeting->company_name=$request->companyname;\n    $meeting->email=$request->email;\n    $meeting->content=$request->content;\n\n    $start = new \\DateTime($result->start_time);\n    $meeting->start_at=$start;\n    $meeting->hash=substr(base_convert(bin2hex(openssl_random_pseudo_bytes(64)),16,36),0,64);\n    $meeting->is_canceled=false;\n\n    $meeting->zoom_meeting_id=$result->id;\n    $meeting->zoom_join_url=$result->join_url;\n    $meeting->zoom_start_url=$result->start_url;\n    $meeting->zoom_password=$result->password;\n    $meeting->save();\n\n    $format = $start->format('Y年m月d日 H時i分');\n    \u002F\u002F $meeting->start_at = $format;\n    \u002F\u002F $mail = new ContactMail($meeting);\n    \u002F\u002F Mail::to($request->email)->send($mail);\n\n\n    return redirect('\u002Fconfirm')->with([\n        'form_id'=>$meeting->id,\n        'name'=>$request->yourname,\n        'companyname'=>$request->companyname,\n        'content'=>$request->content,\n        'start_time'=>$format\n    ]);\n}\n",[31,7444,7445,7450,7455,7460,7465,7470,7475,7480,7484,7488,7493,7497,7502,7507,7512,7516,7520,7525,7529,7533,7538,7542,7547,7552,7557,7562,7567,7571,7575,7579,7584,7589,7594,7599,7604,7609,7614,7619,7624,7628,7633,7637,7642,7647,7652,7657,7662,7666,7671,7676,7681,7686,7690,7695,7700,7705,7710,7715,7719,7724,7729,7734,7739,7743,7747,7752,7757,7762,7767,7772,7777,7781],{"__ignoreMap":33},[46,7446,7447],{"class":48,"line":49},[46,7448,7449],{},"public function createMeeting(Request $request){\n",[46,7451,7452],{"class":48,"line":70},[46,7453,7454],{},"    $validator = Validator::make($request->all(),[\n",[46,7456,7457],{"class":48,"line":82},[46,7458,7459],{},"        'email'=>'required|email:rfc',\n",[46,7461,7462],{"class":48,"line":91},[46,7463,7464],{},"        'yourname'=>'required',\n",[46,7466,7467],{"class":48,"line":102},[46,7468,7469],{},"        'companyname'=>'required',\n",[46,7471,7472],{"class":48,"line":112},[46,7473,7474],{},"        'startAt'=>'date|required',\n",[46,7476,7477],{"class":48,"line":121},[46,7478,7479],{},"        'content'=>'required|max:1000',\n",[46,7481,7482],{"class":48,"line":131},[46,7483,6221],{},[46,7485,7486],{"class":48,"line":139},[46,7487,408],{"emptyLinePlaceholder":407},[46,7489,7490],{"class":48,"line":147},[46,7491,7492],{},"    $error = $validator->getMessageBag()->toArray();\n",[46,7494,7495],{"class":48,"line":157},[46,7496,6065],{},[46,7498,7499],{"class":48,"line":171},[46,7500,7501],{},"    \u002F\u002Fバリデーションエラーがあれば元の画面へ\n",[46,7503,7504],{"class":48,"line":183},[46,7505,7506],{},"    if ($validator->fails()) {\n",[46,7508,7509],{"class":48,"line":195},[46,7510,7511],{},"        return view('form',compact('error'));\n",[46,7513,7514],{"class":48,"line":206},[46,7515,2723],{},[46,7517,7518],{"class":48,"line":4},[46,7519,6065],{},[46,7521,7522],{"class":48,"line":223},[46,7523,7524],{},"    $user = $this->checkRefresh();\n",[46,7526,7527],{"class":48,"line":231},[46,7528,6191],{},[46,7530,7531],{"class":48,"line":242},[46,7532,408],{"emptyLinePlaceholder":407},[46,7534,7535],{"class":48,"line":253},[46,7536,7537],{},"    $zoom_user = $this->me();\n",[46,7539,7540],{"class":48,"line":264},[46,7541,408],{"emptyLinePlaceholder":407},[46,7543,7544],{"class":48,"line":275},[46,7545,7546],{},"    $url = 'https:\u002F\u002Fapi.zoom.us\u002Fv2\u002Fusers\u002F'.$zoom_user->id.'\u002Fmeetings';\n",[46,7548,7549],{"class":48,"line":284},[46,7550,7551],{},"    $client = new \\GuzzleHttp\\Client([\n",[46,7553,7554],{"class":48,"line":296},[46,7555,7556],{},"        'headers' => [\n",[46,7558,7559],{"class":48,"line":305},[46,7560,7561],{},"            'Authorization' => 'Bearer '.$user->access_token,\n",[46,7563,7564],{"class":48,"line":313},[46,7565,7566],{},"            'Content-Type'=>'application\u002Fjson'\n",[46,7568,7569],{"class":48,"line":323},[46,7570,2876],{},[46,7572,7573],{"class":48,"line":529},[46,7574,6221],{},[46,7576,7577],{"class":48,"line":535},[46,7578,408],{"emptyLinePlaceholder":407},[46,7580,7581],{"class":48,"line":540},[46,7582,7583],{},"    $topic = $request->companyname.' '.$request->yourname.'様 ご相談';\n",[46,7585,7586],{"class":48,"line":546},[46,7587,7588],{},"    $meeting_password = substr(base_convert(bin2hex(openssl_random_pseudo_bytes(9)),16,36),0,9);\n",[46,7590,7591],{"class":48,"line":551},[46,7592,7593],{},"    $res = $client->request('POST',$url,[\n",[46,7595,7596],{"class":48,"line":557},[46,7597,7598],{},"        \\GuzzleHttp\\RequestOptions::JSON => [\n",[46,7600,7601],{"class":48,"line":562},[46,7602,7603],{},"            'topic'=>$topic,\n",[46,7605,7606],{"class":48,"line":568},[46,7607,7608],{},"            'type'=>2,\n",[46,7610,7611],{"class":48,"line":573},[46,7612,7613],{},"            'start_time'=>$request->startAt,\n",[46,7615,7616],{"class":48,"line":579},[46,7617,7618],{},"            'password'=>$meeting_password\n",[46,7620,7621],{"class":48,"line":584},[46,7622,7623],{},"        ]\n",[46,7625,7626],{"class":48,"line":590},[46,7627,6221],{},[46,7629,7630],{"class":48,"line":595},[46,7631,7632],{},"    $result = json_decode($res->getBody()->getContents());\n",[46,7634,7635],{"class":48,"line":601},[46,7636,408],{"emptyLinePlaceholder":407},[46,7638,7639],{"class":48,"line":606},[46,7640,7641],{},"    $meeting = new Meeting();\n",[46,7643,7644],{"class":48,"line":611},[46,7645,7646],{},"    $meeting->name=$request->yourname;\n",[46,7648,7649],{"class":48,"line":616},[46,7650,7651],{},"    $meeting->company_name=$request->companyname;\n",[46,7653,7654],{"class":48,"line":622},[46,7655,7656],{},"    $meeting->email=$request->email;\n",[46,7658,7659],{"class":48,"line":627},[46,7660,7661],{},"    $meeting->content=$request->content;\n",[46,7663,7664],{"class":48,"line":633},[46,7665,408],{"emptyLinePlaceholder":407},[46,7667,7668],{"class":48,"line":638},[46,7669,7670],{},"    $start = new \\DateTime($result->start_time);\n",[46,7672,7673],{"class":48,"line":2744},[46,7674,7675],{},"    $meeting->start_at=$start;\n",[46,7677,7678],{"class":48,"line":2749},[46,7679,7680],{},"    $meeting->hash=substr(base_convert(bin2hex(openssl_random_pseudo_bytes(64)),16,36),0,64);\n",[46,7682,7683],{"class":48,"line":3225},[46,7684,7685],{},"    $meeting->is_canceled=false;\n",[46,7687,7688],{"class":48,"line":3231},[46,7689,408],{"emptyLinePlaceholder":407},[46,7691,7692],{"class":48,"line":3236},[46,7693,7694],{},"    $meeting->zoom_meeting_id=$result->id;\n",[46,7696,7697],{"class":48,"line":3242},[46,7698,7699],{},"    $meeting->zoom_join_url=$result->join_url;\n",[46,7701,7702],{"class":48,"line":3247},[46,7703,7704],{},"    $meeting->zoom_start_url=$result->start_url;\n",[46,7706,7707],{"class":48,"line":3253},[46,7708,7709],{},"    $meeting->zoom_password=$result->password;\n",[46,7711,7712],{"class":48,"line":3258},[46,7713,7714],{},"    $meeting->save();\n",[46,7716,7717],{"class":48,"line":3263},[46,7718,408],{"emptyLinePlaceholder":407},[46,7720,7721],{"class":48,"line":3268},[46,7722,7723],{},"    $format = $start->format('Y年m月d日 H時i分');\n",[46,7725,7726],{"class":48,"line":3274},[46,7727,7728],{},"    \u002F\u002F $meeting->start_at = $format;\n",[46,7730,7731],{"class":48,"line":3279},[46,7732,7733],{},"    \u002F\u002F $mail = new ContactMail($meeting);\n",[46,7735,7736],{"class":48,"line":3284},[46,7737,7738],{},"    \u002F\u002F Mail::to($request->email)->send($mail);\n",[46,7740,7741],{"class":48,"line":3289},[46,7742,408],{"emptyLinePlaceholder":407},[46,7744,7745],{"class":48,"line":3295},[46,7746,408],{"emptyLinePlaceholder":407},[46,7748,7749],{"class":48,"line":3300},[46,7750,7751],{},"    return redirect('\u002Fconfirm')->with([\n",[46,7753,7754],{"class":48,"line":3306},[46,7755,7756],{},"        'form_id'=>$meeting->id,\n",[46,7758,7759],{"class":48,"line":3311},[46,7760,7761],{},"        'name'=>$request->yourname,\n",[46,7763,7764],{"class":48,"line":3316},[46,7765,7766],{},"        'companyname'=>$request->companyname,\n",[46,7768,7769],{"class":48,"line":3321},[46,7770,7771],{},"        'content'=>$request->content,\n",[46,7773,7774],{"class":48,"line":3327},[46,7775,7776],{},"        'start_time'=>$format\n",[46,7778,7779],{"class":48,"line":3332},[46,7780,6221],{},[46,7782,7783],{"class":48,"line":3338},[46,7784,2752],{},[13,7786,7787],{},"長いですが、バリデーションからAPIのアクセスまで一通り行われています。",[1337,7789,7790],{"id":7790},"有効期限チェックとエンドポイントリクエストの作成",[13,7792,7793],{},"まずは最初の方では有効期限のチェックを行い、そしてミーティングを作成するユーザー情報を取得しています。",[24,7795,7797],{"className":2394,"code":7796,"filename":6937,"language":882,"meta":33,"style":33},"$user = $this->checkRefresh();\n$user = auth()->user();\n\n$zoom_user = $this->me();\n\n$url = 'https:\u002F\u002Fapi.zoom.us\u002Fv2\u002Fusers\u002F'.$zoom_user->id.'\u002Fmeetings';\n$client = new \\GuzzleHttp\\Client([\n    'headers' => [\n        'Authorization' => 'Bearer '.$user->access_token,\n        'Content-Type'=>'application\u002Fjson'\n    ],\n]);\n",[31,7798,7799,7804,7809,7813,7818,7822,7827,7831,7836,7841,7846,7850],{"__ignoreMap":33},[46,7800,7801],{"class":48,"line":49},[46,7802,7803],{},"$user = $this->checkRefresh();\n",[46,7805,7806],{"class":48,"line":70},[46,7807,7808],{},"$user = auth()->user();\n",[46,7810,7811],{"class":48,"line":82},[46,7812,408],{"emptyLinePlaceholder":407},[46,7814,7815],{"class":48,"line":91},[46,7816,7817],{},"$zoom_user = $this->me();\n",[46,7819,7820],{"class":48,"line":102},[46,7821,408],{"emptyLinePlaceholder":407},[46,7823,7824],{"class":48,"line":112},[46,7825,7826],{},"$url = 'https:\u002F\u002Fapi.zoom.us\u002Fv2\u002Fusers\u002F'.$zoom_user->id.'\u002Fmeetings';\n",[46,7828,7829],{"class":48,"line":121},[46,7830,6664],{},[46,7832,7833],{"class":48,"line":131},[46,7834,7835],{},"    'headers' => [\n",[46,7837,7838],{"class":48,"line":139},[46,7839,7840],{},"        'Authorization' => 'Bearer '.$user->access_token,\n",[46,7842,7843],{"class":48,"line":147},[46,7844,7845],{},"        'Content-Type'=>'application\u002Fjson'\n",[46,7847,7848],{"class":48,"line":157},[46,7849,2846],{},[46,7851,7852],{"class":48,"line":171},[46,7853,6383],{},[13,7855,7856,7857,6172,7860,6413,7863,7866,7867,7870],{},"ミーティングの作成を行うエンドポイントは ",[31,7858,7859],{},"https:\u002F\u002Fapi.zoom.us\u002Fv2\u002Fusers\u002F{zoom_user_id}\u002Fmeetings",[31,7861,7862],{},"{zoom_user_id}",[31,7864,7865],{},"me","で取得した",[31,7868,7869],{},"user_id","（連携したzoomアカウントのuser id）を挿入します。そしてリクエストヘッダーを付けてひとまず、GuzzleHttpのインスタンスを作成します。",[1337,7872,7874],{"id":7873},"ミーティングパスワードを設定してapiへリクエスト","ミーティングパスワードを設定してAPIへリクエスト",[24,7876,7878],{"className":2394,"code":7877,"filename":6937,"language":882,"meta":33,"style":33},"$topic = $request->companyname.' '.$request->yourname.'様 ご相談';        \n$meeting_password = substr(base_convert(bin2hex(openssl_random_pseudo_bytes(9)),16,36),0,9);\n$res = $client->request('POST',$url,[\n    \\GuzzleHttp\\RequestOptions::JSON => [\n        'topic'=>$topic,\n        'type'=>2,\n        'start_time'=>$request->startAt,\n        'password'=>$meeting_password\n    ]\n]);\n$result = json_decode($res->getBody()->getContents());\n",[31,7879,7880,7885,7890,7895,7900,7905,7910,7915,7920,7924,7928],{"__ignoreMap":33},[46,7881,7882],{"class":48,"line":49},[46,7883,7884],{},"$topic = $request->companyname.' '.$request->yourname.'様 ご相談';        \n",[46,7886,7887],{"class":48,"line":70},[46,7888,7889],{},"$meeting_password = substr(base_convert(bin2hex(openssl_random_pseudo_bytes(9)),16,36),0,9);\n",[46,7891,7892],{"class":48,"line":82},[46,7893,7894],{},"$res = $client->request('POST',$url,[\n",[46,7896,7897],{"class":48,"line":91},[46,7898,7899],{},"    \\GuzzleHttp\\RequestOptions::JSON => [\n",[46,7901,7902],{"class":48,"line":102},[46,7903,7904],{},"        'topic'=>$topic,\n",[46,7906,7907],{"class":48,"line":112},[46,7908,7909],{},"        'type'=>2,\n",[46,7911,7912],{"class":48,"line":121},[46,7913,7914],{},"        'start_time'=>$request->startAt,\n",[46,7916,7917],{"class":48,"line":131},[46,7918,7919],{},"        'password'=>$meeting_password\n",[46,7921,7922],{"class":48,"line":139},[46,7923,6703],{},[46,7925,7926],{"class":48,"line":147},[46,7927,6383],{},[46,7929,7930],{"class":48,"line":157},[46,7931,6866],{},[13,7933,7934,7935,7940],{},"お客さんに入力してもらう様のパスワードを生成し、そして指定したエンドポイントへPOSTします。ここでPOSTパラメーター内にミーティング設定情報をJSONで記入します。どんな値が設定できるかは",[725,7936,7939],{"href":7937,"rel":7938},"https:\u002F\u002Fmarketplace.zoom.us\u002Fdocs\u002Fapi-reference\u002Fzoom-api\u002Fmeetings\u002Fmeetingcreate",[729],"ここで確認","できます。",[13,7942,7943],{},"上手く想像できない方は以下のGUIで行うzoom画面を参考にするといいです",[1692,7945],{":src":7946,":width":1928,":center":1929},"'_mix\u002Fsch-2021-01-10-21.02.47-768x480.png'",[13,7948,7949,7950,7953,7954,7957],{},"ここで入力できる値は全て、APIでも入力できますのでリファランスでフォーマットなど確認しながら自分なりの設定をしましょう。私の場合、まずトピックを",[31,7951,7952],{},"「{会社名}　{客名様}　ご相談」","として必ず定義しており、そこは",[31,7955,7956],{},"'topic'=>$topic,","と定義してます。",[13,7959,7960],{},"start_timeは開催日時であり、フォームで入力された値を入れています。passwordは念のため付与しています。そしてAPIをリクエストします。",[1337,7962,7964],{"id":7963},"api処理終了後","API処理終了後",[24,7966,7968],{"className":2394,"code":7967,"filename":6937,"language":882,"meta":33,"style":33},"$result = json_decode($res->getBody()->getContents());\n\n$meeting = new Meeting();\n$meeting->name=$request->yourname;\n$meeting->company_name=$request->companyname;\n$meeting->email=$request->email;\n$meeting->content=$request->content;\n\n$start = new \\DateTime($result->start_time);\n$meeting->start_at=$start;\n$meeting->hash=substr(base_convert(bin2hex(openssl_random_pseudo_bytes(64)),16,36),0,64);\n$meeting->is_canceled=false;\n\n$meeting->zoom_meeting_id=$result->id;\n$meeting->zoom_join_url=$result->join_url;\n$meeting->zoom_start_url=$result->start_url;\n$meeting->zoom_password=$result->password;\n$meeting->save();\n\n$format = $start->format('Y年m月d日 H時i分');\n\u002F\u002F $meeting->start_at = $format;\n\u002F\u002F $mail = new ContactMail($meeting);\n\u002F\u002F Mail::to($request->email)->send($mail);\n\n\nreturn redirect('\u002Fconfirm')->with([\n    'form_id'=>$meeting->id,\n    'name'=>$request->yourname,\n    'companyname'=>$request->companyname,\n    'content'=>$request->content,\n    'start_time'=>$format\n]);\n",[31,7969,7970,7974,7978,7983,7988,7993,7998,8003,8007,8012,8017,8022,8027,8031,8036,8041,8046,8051,8056,8060,8065,8070,8075,8080,8084,8088,8093,8098,8103,8108,8113,8118],{"__ignoreMap":33},[46,7971,7972],{"class":48,"line":49},[46,7973,6866],{},[46,7975,7976],{"class":48,"line":70},[46,7977,408],{"emptyLinePlaceholder":407},[46,7979,7980],{"class":48,"line":82},[46,7981,7982],{},"$meeting = new Meeting();\n",[46,7984,7985],{"class":48,"line":91},[46,7986,7987],{},"$meeting->name=$request->yourname;\n",[46,7989,7990],{"class":48,"line":102},[46,7991,7992],{},"$meeting->company_name=$request->companyname;\n",[46,7994,7995],{"class":48,"line":112},[46,7996,7997],{},"$meeting->email=$request->email;\n",[46,7999,8000],{"class":48,"line":121},[46,8001,8002],{},"$meeting->content=$request->content;\n",[46,8004,8005],{"class":48,"line":131},[46,8006,408],{"emptyLinePlaceholder":407},[46,8008,8009],{"class":48,"line":139},[46,8010,8011],{},"$start = new \\DateTime($result->start_time);\n",[46,8013,8014],{"class":48,"line":147},[46,8015,8016],{},"$meeting->start_at=$start;\n",[46,8018,8019],{"class":48,"line":157},[46,8020,8021],{},"$meeting->hash=substr(base_convert(bin2hex(openssl_random_pseudo_bytes(64)),16,36),0,64);\n",[46,8023,8024],{"class":48,"line":171},[46,8025,8026],{},"$meeting->is_canceled=false;\n",[46,8028,8029],{"class":48,"line":183},[46,8030,408],{"emptyLinePlaceholder":407},[46,8032,8033],{"class":48,"line":195},[46,8034,8035],{},"$meeting->zoom_meeting_id=$result->id;\n",[46,8037,8038],{"class":48,"line":206},[46,8039,8040],{},"$meeting->zoom_join_url=$result->join_url;\n",[46,8042,8043],{"class":48,"line":4},[46,8044,8045],{},"$meeting->zoom_start_url=$result->start_url;\n",[46,8047,8048],{"class":48,"line":223},[46,8049,8050],{},"$meeting->zoom_password=$result->password;\n",[46,8052,8053],{"class":48,"line":231},[46,8054,8055],{},"$meeting->save();\n",[46,8057,8058],{"class":48,"line":242},[46,8059,408],{"emptyLinePlaceholder":407},[46,8061,8062],{"class":48,"line":253},[46,8063,8064],{},"$format = $start->format('Y年m月d日 H時i分');\n",[46,8066,8067],{"class":48,"line":264},[46,8068,8069],{},"\u002F\u002F $meeting->start_at = $format;\n",[46,8071,8072],{"class":48,"line":275},[46,8073,8074],{},"\u002F\u002F $mail = new ContactMail($meeting);\n",[46,8076,8077],{"class":48,"line":284},[46,8078,8079],{},"\u002F\u002F Mail::to($request->email)->send($mail);\n",[46,8081,8082],{"class":48,"line":296},[46,8083,408],{"emptyLinePlaceholder":407},[46,8085,8086],{"class":48,"line":305},[46,8087,408],{"emptyLinePlaceholder":407},[46,8089,8090],{"class":48,"line":313},[46,8091,8092],{},"return redirect('\u002Fconfirm')->with([\n",[46,8094,8095],{"class":48,"line":323},[46,8096,8097],{},"    'form_id'=>$meeting->id,\n",[46,8099,8100],{"class":48,"line":529},[46,8101,8102],{},"    'name'=>$request->yourname,\n",[46,8104,8105],{"class":48,"line":535},[46,8106,8107],{},"    'companyname'=>$request->companyname,\n",[46,8109,8110],{"class":48,"line":540},[46,8111,8112],{},"    'content'=>$request->content,\n",[46,8114,8115],{"class":48,"line":546},[46,8116,8117],{},"    'start_time'=>$format\n",[46,8119,8120],{"class":48,"line":551},[46,8121,6383],{},[13,8123,8124],{},"$resultにzoomからのレスポンスがあるので適宜Meetingモデルやメールに格納します。そして最後にユーザーは確認画面が表示されます。",[20,8126,8127],{"id":8127},"自分のアカウントで実験",[13,8129,8130],{},"では実験してみます。連携した管理者のzoomアカウントでミーティング一覧をみています。リクエスト前はこの様に何もありません。",[1692,8132],{":src":8133,":width":1695},"'_mix\u002Fzoom_meeting_index-768x322.jpeg'",[13,8135,8136],{},"そこでフォームにこの様に入力していきます。（今回は管理者自身が入力）",[1692,8138],{":src":8139,":width":1695},"'_mix\u002Fsch-2021-01-10-21.10.26-768x699.png'",[13,8141,8142],{},"そして送信を押すとちょっとロードして、こちらの画面にリダイレクトされます。",[1692,8144],{":src":8145,":width":1695},"'_mix\u002Fzoom_confirm-768x902.jpeg'",[13,8147,8148],{},"そして先ほどのzoom一覧を見てみると、",[1692,8150],{":src":8151,":width":1695},"'_mix\u002Fzoom_meeting_create-768x412.jpeg'",[13,8153,8154],{},"JUNE様ですね。きちんとミーティングが作られています。（時間がずれているのはコンテナ側のタイムゾーンの設定をすっかり忘れていたからです。)そしてテーブルを見てみると",[1692,8156],{":src":8157,":width":1695},"'_mix\u002Fsch-2021-01-10-21.20.37-768x75.png'",[13,8159,8160],{},"きちんと作られていました。zoom_join_urlの値にアクセスすると",[1692,8162],{":src":8163,":width":1695},"'_mix\u002Fzoom_url_test-768x467.jpeg'",[13,8165,8166],{},"管理者が直接言っているのでミーティングの開始となっていますが、きちんと有効なミーティングURLを取得し保存できています。本来であればメールでお客様にこのURLとパスワードをお知らせします。",[13,8168,8169],{},"また管理画面では",[1692,8171],{":src":8172,":width":1695},"'_mix\u002Fsch-2021-01-10-21.24.07-768x143.png'",[13,8174,8175,8176,8179,8180,8183,8184,8187],{},"この様にして一覧で確認ができます。ちなみに「ミーティングを削除」などのボタンは",[31,8177,8178],{},"http:\u002F\u002Flocalhost:9000\u002Fform\u002Fdelete?hash=cgckkwc040okko..","へリンクされています。",[31,8181,8182],{},"form\u002Fdelete","でミーティングを削除する確認画面へ飛べます。そして照合のために",[31,8185,8186],{},"hash=cgckkwc040okko..","の値を用いています。（途中にあった$hashの値です。）",[13,8189,8190],{},"お客様が匿名であり、ユーザーセッションによる識別ができない時は、予測困難なハッシュ付きURLをお客様だけのメールに渡してミーティングの制御が可能です。",[20,8192,8193],{"id":8193},"まとめ",[13,8195,8196],{},"以上がアプリの実装の流れです。今回作成したOAuth認証zoom アプリはプライベートなので作った本人しか今は利用できませんが、しっかり実装して審査を受けることでマーケットプレイスに出店して自由に使用してもらうことが可能になります。",[13,8198,8199,8200,8204],{},"OAuthも意外と簡単でしたが、公式のAPIリクエストライブラリが出ているわけでないので本格的な開発の際には独自のzoom APIライブラリを作っておくといいかもしれません。zoomでできることはこれだけでないので、もっと色々調べてみようと思います。ひとまず今回のzoomアプリはここまでとします。",[725,8201,8203],{"href":5772,"rel":8202},[729],"一応リポジトリに公開してある","のでぜひクローンして遊んでみてください。",[352,8206,8208],{"id":8207},"追記-2021-3-31","追記 2021 3 31",[13,8210,8211],{},"なんか、これぐらいの規模で特定のアプリでの利用であればJWTでも十分でした汗。JWT編もそのうちやろうと思います。",[1851,8213,8214],{},"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":33,"searchDepth":82,"depth":82,"links":8216},[8217,8225,8228,8229,8238,8243,8247,8248,8249,8257,8258],{"id":5630,"depth":70,"text":5631,"children":8218},[8219,8223,8224],{"id":5646,"depth":82,"text":5646,"children":8220},[8221,8222],{"id":5652,"depth":91,"text":5653},{"id":5671,"depth":91,"text":5672},{"id":5693,"depth":82,"text":5693},{"id":5723,"depth":82,"text":5724},{"id":5730,"depth":70,"text":5730,"children":8226},[8227],{"id":5742,"depth":82,"text":5742},{"id":5760,"depth":70,"text":5760},{"id":5810,"depth":70,"text":5811,"children":8230},[8231,8232],{"id":5814,"depth":82,"text":5815},{"id":5851,"depth":82,"text":5851,"children":8233},[8234,8235,8236,8237],{"id":5854,"depth":91,"text":5854},{"id":5919,"depth":91,"text":5919},{"id":5928,"depth":91,"text":5929},{"id":5941,"depth":91,"text":5941},{"id":5950,"depth":70,"text":5951,"children":8239},[8240,8241,8242],{"id":5957,"depth":82,"text":5958},{"id":6011,"depth":82,"text":6012},{"id":6144,"depth":82,"text":6144},{"id":6161,"depth":70,"text":6162,"children":8244},[8245,8246],{"id":6165,"depth":82,"text":6165},{"id":6455,"depth":82,"text":6456},{"id":6930,"depth":70,"text":6930},{"id":7049,"depth":70,"text":7049},{"id":7183,"depth":70,"text":7184,"children":8250},[8251,8252],{"id":7193,"depth":82,"text":7194},{"id":7435,"depth":82,"text":7436,"children":8253},[8254,8255,8256],{"id":7790,"depth":91,"text":7790},{"id":7873,"depth":91,"text":7874},{"id":7963,"depth":91,"text":7964},{"id":8127,"depth":70,"text":8127},{"id":8193,"depth":70,"text":8193,"children":8259},[8260],{"id":8207,"depth":82,"text":8208},[1881],"2025-08-14",{},"\u002Farticles\u002Fzoom-api-laravel",{"title":5605,"description":5605},"articles\u002Fzoom-api-laravel",[1583,8268],"zoom","_mix\u002Ftop-768x463.jpeg","jNr1TCK61sAidVSCnsx9vzTvrRaOg6Bur8MSs3PYh8I",{"id":8272,"title":8273,"body":8274,"category":8674,"createdAt":8676,"description":8677,"extension":1883,"index":1884,"meta":8678,"navigation":407,"path":8679,"publish":407,"seo":8680,"series":1884,"seriesTitle":1884,"stem":8681,"tag":8682,"thumbnail":1884,"updatedAt":1884,"__hash__":8683},"articles\u002Farticles\u002F2024_06_28_laravel-guard-igonore-on-seeder.md","LaravelのSeederではModelのguardが無効になることで場合によって存在しないカラムへの挿入でエラーとなる",{"type":10,"value":8275,"toc":8669},[8276,8287,8294,8297,8300,8303,8384,8387,8390,8499,8502,8531,8534,8596,8610,8627,8630,8636,8655,8658,8661,8667],[13,8277,8278,8279,8282,8283,8286],{},"こんにちはjunです。Laravelのseederでfactoryの",[31,8280,8281],{},"create()","を使用せず、",[31,8284,8285],{},"new Model()","からなるモデルメソッドからデータ挿入を行った時に詰まったことがあり、その忘備録です。",[13,8288,8289,8290,8293],{},"結論としてSeederではモデルのguardが無効になり、",[31,8291,8292],{},"fill()","を使用した挿入で存在しないカラムへのinsertが走ってエラーになることが原因でした。背景から話すので、もしさっさと解決策を知りたい方は「解決方法」のとこまで移動してください。",[20,8295,8296],{"id":8296},"モデルベースで挿入処理をしたい",[13,8298,8299],{},"Laravelにはダミーデータを挿入するためにseederと呼ばれる挿入スクリプトファイル、factoryというfakerを利用して指定モデルへのダミーデータの定義を作成できます。とりあえずデータを作成してページングや表示の様子を確かめる時に非常に便利です。",[13,8301,8302],{},"ただしこのfacotryで挿入するときは「DBのinsert処理を走らせる」方法です。factoryと連結したモデルのキャストなどは考慮してくれますが、基本的にはDBのinsert文を作成するような感じです。",[24,8304,8306],{"className":2394,"code":8305,"language":882,"meta":33,"style":33},"\u002F\u002F ※ Laravel6での記述方法です！\n\n\u002F** @var \\Illuminate\\Database\\Eloquent\\Factory $factory *\u002F\n\nuse App\\Models\\Article;\nuse Faker\\Generator as Faker;\n\n$factory->define(Article::class, function (Faker $faker) {\n    return [\n        'title'=>$faker->text(),\n        'content'=>$faker->realText(),\n        'is_active'=>true, \u002F\u002F キャストしてくれる。true => 1\n        'author'=> User::inRandomOrder()->first()->id \u002F\u002F ID番号を指定しないとダメ。\n    ];\n});\n\n",[31,8307,8308,8313,8317,8322,8326,8331,8336,8340,8345,8350,8355,8360,8368,8376,8380],{"__ignoreMap":33},[46,8309,8310],{"class":48,"line":49},[46,8311,8312],{},"\u002F\u002F ※ Laravel6での記述方法です！\n",[46,8314,8315],{"class":48,"line":70},[46,8316,408],{"emptyLinePlaceholder":407},[46,8318,8319],{"class":48,"line":82},[46,8320,8321],{},"\u002F** @var \\Illuminate\\Database\\Eloquent\\Factory $factory *\u002F\n",[46,8323,8324],{"class":48,"line":91},[46,8325,408],{"emptyLinePlaceholder":407},[46,8327,8328],{"class":48,"line":102},[46,8329,8330],{},"use App\\Models\\Article;\n",[46,8332,8333],{"class":48,"line":112},[46,8334,8335],{},"use Faker\\Generator as Faker;\n",[46,8337,8338],{"class":48,"line":121},[46,8339,408],{"emptyLinePlaceholder":407},[46,8341,8342],{"class":48,"line":131},[46,8343,8344],{},"$factory->define(Article::class, function (Faker $faker) {\n",[46,8346,8347],{"class":48,"line":139},[46,8348,8349],{},"    return [\n",[46,8351,8352],{"class":48,"line":147},[46,8353,8354],{},"        'title'=>$faker->text(),\n",[46,8356,8357],{"class":48,"line":157},[46,8358,8359],{},"        'content'=>$faker->realText(),\n",[46,8361,8362,8365],{"class":48,"line":171},[46,8363,8364],{},"        'is_active'=>true,",[46,8366,8367],{}," \u002F\u002F キャストしてくれる。true => 1\n",[46,8369,8370,8373],{"class":48,"line":183},[46,8371,8372],{},"        'author'=> User::inRandomOrder()->first()->id",[46,8374,8375],{}," \u002F\u002F ID番号を指定しないとダメ。\n",[46,8377,8378],{"class":48,"line":195},[46,8379,2621],{},[46,8381,8382],{"class":48,"line":206},[46,8383,2966],{},[13,8385,8386],{},"複雑なリレーションがあったり、モデル内でなんやかんやして他のモデルやデータに変更を与える場合は上記の方法では面倒なことがあります。そのため、facotryで仮のhttpリクエストに相当する連想配列を作成しておき、リクエストボディから挿入処理を行うときのメソッドを経由してデータを作成したい時があります。",[13,8388,8389],{},"例ではこんな感じ..",[24,8391,8393],{"className":2394,"code":8392,"language":882,"meta":33,"style":33},"\u002F\u002F Controller.php\npublic function createData(HogeRequest $request){\n    $m = new TargetModel();\n    $m->add($request->validated());\n    return $m\n}\n\n\n\u002F\u002F TargetModel.php\npublic function add(array $val){\n    $this->fill($val);\n    $this->save();\n    \u002F\u002F complex process...\n    return $this;\n}\n\u002F\u002F ↑このメソッドを使用したい！\n\n\n\u002F\u002F seeder\u002FinsertDummy.php\n$f = factory(App\\Models\\TargetModel.php)->make(); \u002F\u002F データの挿入はせず、fakerで作成した連想配列が取得できます..!\n$m = new TargetModel();\n$m->add($f);\n",[31,8394,8395,8400,8405,8410,8415,8420,8424,8428,8432,8437,8442,8447,8452,8457,8462,8466,8471,8475,8479,8484,8489,8494],{"__ignoreMap":33},[46,8396,8397],{"class":48,"line":49},[46,8398,8399],{},"\u002F\u002F Controller.php\n",[46,8401,8402],{"class":48,"line":70},[46,8403,8404],{},"public function createData(HogeRequest $request){\n",[46,8406,8407],{"class":48,"line":82},[46,8408,8409],{},"    $m = new TargetModel();\n",[46,8411,8412],{"class":48,"line":91},[46,8413,8414],{},"    $m->add($request->validated());\n",[46,8416,8417],{"class":48,"line":102},[46,8418,8419],{},"    return $m\n",[46,8421,8422],{"class":48,"line":112},[46,8423,2752],{},[46,8425,8426],{"class":48,"line":121},[46,8427,408],{"emptyLinePlaceholder":407},[46,8429,8430],{"class":48,"line":131},[46,8431,408],{"emptyLinePlaceholder":407},[46,8433,8434],{"class":48,"line":139},[46,8435,8436],{},"\u002F\u002F TargetModel.php\n",[46,8438,8439],{"class":48,"line":147},[46,8440,8441],{},"public function add(array $val){\n",[46,8443,8444],{"class":48,"line":157},[46,8445,8446],{},"    $this->fill($val);\n",[46,8448,8449],{"class":48,"line":171},[46,8450,8451],{},"    $this->save();\n",[46,8453,8454],{"class":48,"line":183},[46,8455,8456],{},"    \u002F\u002F complex process...\n",[46,8458,8459],{"class":48,"line":195},[46,8460,8461],{},"    return $this;\n",[46,8463,8464],{"class":48,"line":206},[46,8465,2752],{},[46,8467,8468],{"class":48,"line":4},[46,8469,8470],{},"\u002F\u002F ↑このメソッドを使用したい！\n",[46,8472,8473],{"class":48,"line":223},[46,8474,408],{"emptyLinePlaceholder":407},[46,8476,8477],{"class":48,"line":231},[46,8478,408],{"emptyLinePlaceholder":407},[46,8480,8481],{"class":48,"line":242},[46,8482,8483],{},"\u002F\u002F seeder\u002FinsertDummy.php\n",[46,8485,8486],{"class":48,"line":253},[46,8487,8488],{},"$f = factory(App\\Models\\TargetModel.php)->make(); \u002F\u002F データの挿入はせず、fakerで作成した連想配列が取得できます..!\n",[46,8490,8491],{"class":48,"line":264},[46,8492,8493],{},"$m = new TargetModel();\n",[46,8495,8496],{"class":48,"line":275},[46,8497,8498],{},"$m->add($f);\n",[13,8500,8501],{},"例として、id,title,contentのカラムを持つArticleテーブルとManyToManyのリレーションをもつTagsテーブルがあるとしましょう。作成する際のhttp bodyでは",[24,8503,8505],{"className":2394,"code":8504,"language":882,"meta":33,"style":33},"[\n    'title'=>'タイトルだよ',\n    'content'=>'文章文章...'\n    'tags'=>[1,3]\n]\n",[31,8506,8507,8512,8517,8522,8527],{"__ignoreMap":33},[46,8508,8509],{"class":48,"line":49},[46,8510,8511],{},"[\n",[46,8513,8514],{"class":48,"line":70},[46,8515,8516],{},"    'title'=>'タイトルだよ',\n",[46,8518,8519],{"class":48,"line":82},[46,8520,8521],{},"    'content'=>'文章文章...'\n",[46,8523,8524],{"class":48,"line":91},[46,8525,8526],{},"    'tags'=>[1,3]\n",[46,8528,8529],{"class":48,"line":102},[46,8530,2467],{},[13,8532,8533],{},"このようにフロントエンドから送信され、対象のArticleが作成されたのちに指定のTagsとのIDが連携されます。",[24,8535,8537],{"className":2394,"code":8536,"language":882,"meta":33,"style":33},"\u002F\u002F Article.php\nprotected $guarded = ['id'];\n\npublic function tag(){\n    return $this->hasMany(\\App\\Models\\Tags);\n}\n\npublic function add(array $val){\n    $this->fill($val);\n    $this->save();\n    $this->sync($val['tags'])\n    return $this;\n}\n",[31,8538,8539,8544,8549,8553,8558,8563,8567,8571,8575,8579,8583,8588,8592],{"__ignoreMap":33},[46,8540,8541],{"class":48,"line":49},[46,8542,8543],{},"\u002F\u002F Article.php\n",[46,8545,8546],{"class":48,"line":70},[46,8547,8548],{},"protected $guarded = ['id'];\n",[46,8550,8551],{"class":48,"line":82},[46,8552,408],{"emptyLinePlaceholder":407},[46,8554,8555],{"class":48,"line":91},[46,8556,8557],{},"public function tag(){\n",[46,8559,8560],{"class":48,"line":102},[46,8561,8562],{},"    return $this->hasMany(\\App\\Models\\Tags);\n",[46,8564,8565],{"class":48,"line":112},[46,8566,2752],{},[46,8568,8569],{"class":48,"line":121},[46,8570,408],{"emptyLinePlaceholder":407},[46,8572,8573],{"class":48,"line":131},[46,8574,8441],{},[46,8576,8577],{"class":48,"line":139},[46,8578,8446],{},[46,8580,8581],{"class":48,"line":147},[46,8582,8451],{},[46,8584,8585],{"class":48,"line":157},[46,8586,8587],{},"    $this->sync($val['tags'])\n",[46,8589,8590],{"class":48,"line":171},[46,8591,8461],{},[46,8593,8594],{"class":48,"line":183},[46,8595,2752],{},[13,8597,8598,8599,8602,8603,8605,8606,8609],{},"モデルでは",[31,8600,8601],{},"guarded","を指定して、fillが使用できるようにします。リレーション部分のカラムはlaravelが対応してくれます。そのため、",[31,8604,8292],{},"では",[31,8607,8608],{},"tags","というカラムに挿入させるようなSQLは生成されなくなります。",[13,8611,8612,8613,8616,8617,8619,8620,8623,8624,8626],{},"しかしseederでこのメソッドを使用した時に ",[31,8614,8615],{},"Array to srting convertion"," というエラーが発生し詰まってしまいました。よくよくエラートレースを見ると、insert文のカラムに",[31,8618,8608],{},"が存在しているのがわかりました。「",[31,8621,8622],{},"filable","が正常に機能していないのか？」と予測をたてて調べたらどうやら「seeder中はfactory通りの内容が挿入されるように、",[31,8625,8601],{},"が無効になりfillの値が全て考慮される」とのことでした。fillableがワイルドカードのような状態となり、tagsがArticlesのカラムとして認識されたことが原因です。",[20,8628,8629],{"id":8629},"解決方法",[13,8631,8632,8633,8635],{},"ではseeder中でもモデルのfillable,",[31,8634,8601],{},"を有効にすれば解決します。その専用のメソッドがあります。",[24,8637,8639],{"className":2394,"code":8638,"language":882,"meta":33,"style":33},"use Illuminate\\Database\\Eloquent\\Model;\n\nModel::reguard(); \u002F\u002F←これをfillの前に入れる\n",[31,8640,8641,8646,8650],{"__ignoreMap":33},[46,8642,8643],{"class":48,"line":49},[46,8644,8645],{},"use Illuminate\\Database\\Eloquent\\Model;\n",[46,8647,8648],{"class":48,"line":70},[46,8649,408],{"emptyLinePlaceholder":407},[46,8651,8652],{"class":48,"line":82},[46,8653,8654],{},"Model::reguard(); \u002F\u002F←これをfillの前に入れる\n",[13,8656,8657],{},"この静的メソッドを呼び出すことでguardedを再度有効にでき、指定・存在するカラムのみにfillが行われます。",[20,8659,8660],{"id":8660},"参考",[13,8662,8663],{},[725,8664,8665],{"href":8665,"rel":8666},"https:\u002F\u002Fstackoverflow.com\u002Fquestions\u002F67092809\u002Ffillable-doesnt-work-when-creating-model-from-seeder",[729],[1851,8668,8214],{},{"title":33,"searchDepth":82,"depth":82,"links":8670},[8671,8672,8673],{"id":8296,"depth":70,"text":8296},{"id":8629,"depth":70,"text":8629},{"id":8660,"depth":70,"text":8660},[8675],"ministack","2024-06-28","LaravelのSeederでModelメソッドを用いて挿入する場合の注意点",{},"\u002Farticles\u002F2024_06_28_laravel-guard-igonore-on-seeder",{"title":8273,"description":8677},"articles\u002F2024_06_28_laravel-guard-igonore-on-seeder",[1583],"V44O9yNCFMlfPQN4Yjg8vBnRzTGUlYiKV2q4aF6CG4U",{"id":8685,"title":8686,"body":8687,"category":8826,"createdAt":8827,"description":8686,"extension":1883,"index":1884,"meta":8828,"navigation":407,"path":8829,"publish":407,"seo":8830,"series":1884,"seriesTitle":1884,"stem":8831,"tag":8832,"thumbnail":8833,"updatedAt":1884,"__hash__":8834},"articles\u002Farticles\u002Flaravel-socialite-scope-careless.md","LaravelのSociateでscopeで間違えてちょっと詰まった話",{"type":10,"value":8688,"toc":8824},[8689,8692,8695,8698,8732,8735,8742,8760,8770,8776,8781,8786,8789,8792,8822],[13,8690,8691],{},"こんにちはjunです。LaravelでGoogleとのログイン機能、連携機能を作っていた時にscopeメソッドの使い方でちょっと詰まりました。というよりか自分がよくドキュメントを読まなかったケアレスミスでしたが、どこかの開発者が詰まってこの記事を見つけられるようにインターネットの海にこの情報を書いておきます。",[13,8693,8694],{},"Laravelは本当に素晴らしいライブラリで、Laravel socialiteというライブラリを使用すれば外部サービスでのログイン機能があっという間に使用できます\n一通りログイン周りの機能を整えて、Google Driveとの連携の実装を行おうとして諸所の設定を行いました。",[13,8696,8697],{},"GCPの管理画面でGoogle Driveを追加してリファランスにしたがって以下のようにログイン時の処理にDriveへのアクセスを求めるようにスコープを追加しました。",[24,8699,8701],{"className":2394,"code":8700,"language":882,"meta":33,"style":33},"public function redirectToGoogle(Request $request){\n    return Socialite::driver('google')\n    ->with([\"access_type\" => \"offline\", \"prompt\" => \"consent select_account\"])\n    ->setScopes([\"https:\u002F\u002Fwww.googleapis.com\u002Fauth\u002Fdrive\"])\n    ->redirect();\n}\n",[31,8702,8703,8708,8713,8718,8723,8728],{"__ignoreMap":33},[46,8704,8705],{"class":48,"line":49},[46,8706,8707],{},"public function redirectToGoogle(Request $request){\n",[46,8709,8710],{"class":48,"line":70},[46,8711,8712],{},"    return Socialite::driver('google')\n",[46,8714,8715],{"class":48,"line":82},[46,8716,8717],{},"    ->with([\"access_type\" => \"offline\", \"prompt\" => \"consent select_account\"])\n",[46,8719,8720],{"class":48,"line":91},[46,8721,8722],{},"    ->setScopes([\"https:\u002F\u002Fwww.googleapis.com\u002Fauth\u002Fdrive\"])\n",[46,8724,8725],{"class":48,"line":102},[46,8726,8727],{},"    ->redirect();\n",[46,8729,8730],{"class":48,"line":112},[46,8731,2752],{},[13,8733,8734],{},"必要なAPIへのスコープを入力することで、取得できるアクセストークンでそのサービスにアクセスできます。しかし上記の実装でテストをしてみると、連携画面からローカルにリダイレクトする際に401が発生してしまい、連携が失敗しました。",[13,8736,8737,8738,8741],{},"なんでだろうとAPIの設定などをよく確認しましたが、原因は",[31,8739,8740],{},"setScopes()","というメソッドでした。",[13,8743,8744,8745,4777,8748,8750,8751,8753,8754,8759],{},"Laravel socialiteはOauthのスコープを設定する際に２つのメソッドをしようできます。",[31,8746,8747],{},"scopes()",[31,8749,8740],{},"です。メソッドの名前的に",[31,8752,8740],{},"を使っていたのですが、これは",[725,8755,8758],{"href":8756,"rel":8757},"https:\u002F\u002Flaravel.com\u002Fdocs\u002F9.x\u002Fsocialite#access-scopes",[729],"ドキュメントにもある通り"," に",[8761,8762,8763],"blockquote",{},[13,8764,8765,8766,8769],{},"You can ",[1914,8767,8768],{},"overwrite all existing scopes"," on the authentication request using the setScopes method:",[13,8771,1440,8772,8775],{},[1914,8773,8774],{},"すべての存在するスコープを上書きする","と書いてあります。",[13,8777,8778,8780],{},[31,8779,8747],{},"はドキュメントでは",[8761,8782,8783],{},[13,8784,8785],{},"This method will merge all previously specified scopes with the scopes that you specify:",[13,8787,8788],{},"と前々にあったスコープにマージすると書かれています。",[13,8790,8791],{},"細かい理由は分かりませんが、多分socialiteが提供する標準のスコープが消されてしまったのが原因かもしれません。とりあえず以下のように修正したら直りました。",[24,8793,8795],{"className":2394,"code":8794,"language":882,"meta":33,"style":33},"public function redirectToGoogle(Request $request){\n    return Socialite::driver('google')\n    ->with([\"access_type\" => \"offline\", \"prompt\" => \"consent select_account\"])\n    ->scopes([\"https:\u002F\u002Fwww.googleapis.com\u002Fauth\u002Fdrive\"]) \u002F\u002F scopesに変更\n    ->redirect();\n}\n",[31,8796,8797,8801,8805,8809,8814,8818],{"__ignoreMap":33},[46,8798,8799],{"class":48,"line":49},[46,8800,8707],{},[46,8802,8803],{"class":48,"line":70},[46,8804,8712],{},[46,8806,8807],{"class":48,"line":82},[46,8808,8717],{},[46,8810,8811],{"class":48,"line":91},[46,8812,8813],{},"    ->scopes([\"https:\u002F\u002Fwww.googleapis.com\u002Fauth\u002Fdrive\"]) \u002F\u002F scopesに変更\n",[46,8815,8816],{"class":48,"line":102},[46,8817,8727],{},[46,8819,8820],{"class":48,"line":112},[46,8821,2752],{},[1851,8823,8214],{},{"title":33,"searchDepth":82,"depth":82,"links":8825},[],[8675],"2022-05-23",{},"\u002Farticles\u002Flaravel-socialite-scope-careless",{"title":8686,"description":8686},"articles\u002Flaravel-socialite-scope-careless",[1583],"_common\u002Flaravel.png","tdenFwXM57aAqHk4siNzWCo8FXhh1YpEFCNAWNUhrnw",{"id":8836,"title":8837,"body":8838,"category":10446,"createdAt":10447,"description":8837,"extension":1883,"index":1884,"meta":10448,"navigation":407,"path":10449,"publish":407,"seo":10450,"series":1884,"seriesTitle":1884,"stem":10451,"tag":10452,"thumbnail":8833,"updatedAt":1884,"__hash__":10454},"articles\u002Farticles\u002Flaravel-i18n-json-dump.md","Laravelで多言語用のJSONを出力するコマンドを作る",{"type":10,"value":8839,"toc":10434},[8840,8854,8883,8890,8902,8915,8919,8922,8928,8998,9005,9008,9016,9019,9021,9028,9031,9034,9044,9050,9053,9056,9059,9065,9072,9259,9266,9272,9275,9278,9285,9445,9472,9475,9478,9575,9595,9599,9633,9648,9651,9666,9680,9683,9686,9987,9990,10003,10011,10016,10019,10224,10422,10428,10431],[13,8841,8842,8843,8846,8847,1448,8850,8853],{},"こんにちはjunです。個人開発で多言語対応のLaravelアプリを作っています。多言語では各種言語の単語・文章のマッピング（辞書）をする配列・JSONを作成し、レンダリング時にメソッドを使用して文字を表示します。Laravelでは",[31,8844,8845],{},"resources\u002Flang"," 配下に",[31,8848,8849],{},"en",[31,8851,8852],{},"ja","のようなディレクトリを作成し、その中に言語のマッピングをします。大体は以下のような配列を作ります。",[24,8855,8858],{"className":2394,"code":8856,"filename":8857,"language":882,"meta":33,"style":33},"\u003C?php\nreturn [\n    'login'=>'ログイン',\n    'logout'=>'ログアウト'\n]\n\n","resources\u002Flang\u002Fja\u002Fwords.php",[31,8859,8860,8864,8869,8874,8879],{"__ignoreMap":33},[46,8861,8862],{"class":48,"line":49},[46,8863,2525],{},[46,8865,8866],{"class":48,"line":70},[46,8867,8868],{},"return [\n",[46,8870,8871],{"class":48,"line":82},[46,8872,8873],{},"    'login'=>'ログイン',\n",[46,8875,8876],{"class":48,"line":91},[46,8877,8878],{},"    'logout'=>'ログアウト'\n",[46,8880,8881],{"class":48,"line":102},[46,8882,2467],{},[13,8884,8885,8886,8889],{},"そしてビューファイルなどで",[31,8887,8888],{},"__('words.login')","のように使用します。",[808,8891,8893,8894,8897,8898,8901],{"className":8892},[811,812],"\n多言語のメソッドが",[31,8895,8896],{},"__","みたいな名称である理由としては、使いまくるので簡単な名称になっています。Vueとかでは",[31,8899,8900],{},"$t()","みたいなものを使用します。\n",[13,8903,8904,8905,8907,8908,1448,8911,8914],{},"上記のようなPHPファイルを作成してもできますが、",[31,8906,8845],{}," 直下に",[31,8909,8910],{},"en.json",[31,8912,8913],{},"ja.json","のようなJSONファイルを作成しても、多言語メソッドで呼び出せます。",[20,8916,8918],{"id":8917},"jsonの弱点","JSONの弱点",[13,8920,8921],{},"JSONで作るメリットはマッピングのデータを他のアプリでも利用できることです。例えば、Vue・ReactではVuei18n、react-i18nextなどを使用します。同じように多言語メソッドを使用します。その際のマッピングデータとしてJSONを使用します。であればJSONファイルを作っておくことで、Vueや外部へマッピングデータを提供しやすくなります。",[13,8923,8924,8925,8927],{},"ただしJSONで作成するとLaravel側で",[31,8926,8888],{},"のような呼び出しができません。この時JSONは以下のようになっています。",[24,8929,8933],{"className":8930,"code":8931,"language":8932,"meta":33,"style":33},"language-json shiki shiki-themes material-theme-ocean","{\n    \"words\":{\n        \"login\":\"ログイン\",\n        \"logout\":\"ログアウト\"\n    }\n}\n","json",[31,8934,8935,8939,8951,8972,8990,8994],{"__ignoreMap":33},[46,8936,8937],{"class":48,"line":49},[46,8938,2572],{"class":56},[46,8940,8941,8944,8947,8949],{"class":48,"line":70},[46,8942,8943],{"class":56},"    \"",[46,8945,8946],{"class":4046},"words",[46,8948,4238],{"class":56},[46,8950,3688],{"class":56},[46,8952,8953,8956,8959,8961,8963,8965,8968,8970],{"class":48,"line":82},[46,8954,8955],{"class":56},"        \"",[46,8957,8958],{"class":3684},"login",[46,8960,4238],{"class":56},[46,8962,57],{"class":56},[46,8964,4238],{"class":56},[46,8966,8967],{"class":63},"ログイン",[46,8969,4238],{"class":56},[46,8971,3701],{"class":56},[46,8973,8974,8976,8979,8981,8983,8985,8988],{"class":48,"line":91},[46,8975,8955],{"class":56},[46,8977,8978],{"class":3684},"logout",[46,8980,4238],{"class":56},[46,8982,57],{"class":56},[46,8984,4238],{"class":56},[46,8986,8987],{"class":63},"ログアウト",[46,8989,168],{"class":56},[46,8991,8992],{"class":48,"line":102},[46,8993,2723],{"class":56},[46,8995,8996],{"class":48,"line":112},[46,8997,2752],{"class":56},[13,8999,9000,9001,9004],{},"すべて一次元にしてしまうと後で混乱してしまうので、カスケードさせておくと良いです。Vuei18nでは",[31,9002,9003],{},"$t('words.login')","で呼び出せますが、なぜかLaravelではJSONのカスケード配下のキーを呼び出すことができません。",[13,9006,9007],{},"そのため",[336,9009,9010,9013],{},[339,9011,9012],{},"Laravel以外のアプリへの提供→JSON",[339,9014,9015],{},"Laravelの多言語対応→PHP",[13,9017,9018],{},"という２重管理でそれぞれ設定しないといけなくなり、非効率的です。",[20,9020,8629],{"id":8629},[13,9022,9023,9024,9027],{},"解決方法としてはPHPで作成した配列をJSONにダンプすることです。そうすることでPHPファイルとJSONの言語ファイルを作成することができます。そこで今回の記事ではPHPの言語ファイルをカスケードさせたJSONに出力するような",[31,9025,9026],{},"artisan","コマンドを作ってみたいと思います。",[20,9029,9030],{"id":9030},"コマンドの実装",[352,9032,9033],{"id":9033},"対応する言語ディレクトリ",[13,9035,9036,9037,8846,9039,1448,9041,9043],{},"まずはLaravelの言語ファイルのドキュメントの通り、",[31,9038,8845],{},[31,9040,8849],{},[31,9042,8852],{},"のようなディレクトリを作成しておきましょう。今回は英語と日本語にしておきます。",[24,9045,9048],{"className":9046,"code":9047,"language":29},[27],"resources\n├── lang\n│   ├── en\n│   │   ├── auth.php\n│   │   ├── words.php\n│   │   └── exceptions.php\n│   └── ja\n│       ├── auth.php\n│       ├── words.php\n│       └── exceptions.php\n...\n",[31,9049,9047],{"__ignoreMap":33},[13,9051,9052],{},"そしてそのディレクトリごとにphpファイルを分けます。とりあえずこのようにしておきます。",[352,9054,9055],{"id":9055},"コマンドのファイルを作成",[13,9057,9058],{},"それではカスタムのコマンドを作成しましょう。",[24,9060,9063],{"className":9061,"code":9062,"language":29},[27],"php artisan make:command Dumplang\n",[31,9064,9062],{"__ignoreMap":33},[13,9066,9067,9068,9071],{},"コマンドもartisanで作成できます。",[31,9069,9070],{},"app\u002FConsole\u002FCommands","というディレクトリが作成され、そこにファイルが作成されます。",[24,9073,9075],{"className":2394,"code":9074,"language":882,"meta":33,"style":33},"\u003C?php\n\nnamespace App\\Console\\Commands;\n\nuse Illuminate\\Console\\Command;\n\nclass Dumblang extends Command\n{\n    \u002F**\n     * The name and signature of the console command.\n     *\n     * @var string\n     *\u002F\n    protected $signature = 'lang:dump';\n\n    \u002F**\n     * The console command description.\n     *\n     * @var string\n     *\u002F\n    protected $description = 'Convert each php lang files to JSON files.';\n\n    \u002F**\n     * Create a new command instance.\n     *\n     * @return void\n     *\u002F\n    public function __construct()\n    {\n        parent::__construct();\n    }\n\n    \u002F**\n     * Execute the console command.\n     *\n     * @return int\n     *\u002F\n    public function handle()\n    {\n        \n    }\n}\n",[31,9076,9077,9081,9085,9090,9094,9099,9103,9108,9112,9116,9121,9125,9130,9134,9139,9143,9147,9152,9156,9160,9164,9169,9173,9177,9182,9186,9190,9194,9198,9202,9207,9211,9215,9219,9224,9228,9233,9237,9242,9246,9251,9255],{"__ignoreMap":33},[46,9078,9079],{"class":48,"line":49},[46,9080,2525],{},[46,9082,9083],{"class":48,"line":70},[46,9084,408],{"emptyLinePlaceholder":407},[46,9086,9087],{"class":48,"line":82},[46,9088,9089],{},"namespace App\\Console\\Commands;\n",[46,9091,9092],{"class":48,"line":91},[46,9093,408],{"emptyLinePlaceholder":407},[46,9095,9096],{"class":48,"line":102},[46,9097,9098],{},"use Illuminate\\Console\\Command;\n",[46,9100,9101],{"class":48,"line":112},[46,9102,408],{"emptyLinePlaceholder":407},[46,9104,9105],{"class":48,"line":121},[46,9106,9107],{},"class Dumblang extends Command\n",[46,9109,9110],{"class":48,"line":131},[46,9111,2572],{},[46,9113,9114],{"class":48,"line":139},[46,9115,2586],{},[46,9117,9118],{"class":48,"line":147},[46,9119,9120],{},"     * The name and signature of the console command.\n",[46,9122,9123],{"class":48,"line":157},[46,9124,2596],{},[46,9126,9127],{"class":48,"line":171},[46,9128,9129],{},"     * @var string\n",[46,9131,9132],{"class":48,"line":183},[46,9133,2606],{},[46,9135,9136],{"class":48,"line":195},[46,9137,9138],{},"    protected $signature = 'lang:dump';\n",[46,9140,9141],{"class":48,"line":206},[46,9142,408],{"emptyLinePlaceholder":407},[46,9144,9145],{"class":48,"line":4},[46,9146,2586],{},[46,9148,9149],{"class":48,"line":223},[46,9150,9151],{},"     * The console command description.\n",[46,9153,9154],{"class":48,"line":231},[46,9155,2596],{},[46,9157,9158],{"class":48,"line":242},[46,9159,9129],{},[46,9161,9162],{"class":48,"line":253},[46,9163,2606],{},[46,9165,9166],{"class":48,"line":264},[46,9167,9168],{},"    protected $description = 'Convert each php lang files to JSON files.';\n",[46,9170,9171],{"class":48,"line":275},[46,9172,408],{"emptyLinePlaceholder":407},[46,9174,9175],{"class":48,"line":284},[46,9176,2586],{},[46,9178,9179],{"class":48,"line":296},[46,9180,9181],{},"     * Create a new command instance.\n",[46,9183,9184],{"class":48,"line":305},[46,9185,2596],{},[46,9187,9188],{"class":48,"line":313},[46,9189,3060],{},[46,9191,9192],{"class":48,"line":323},[46,9193,2606],{},[46,9195,9196],{"class":48,"line":529},[46,9197,3069],{},[46,9199,9200],{"class":48,"line":535},[46,9201,2713],{},[46,9203,9204],{"class":48,"line":540},[46,9205,9206],{},"        parent::__construct();\n",[46,9208,9209],{"class":48,"line":546},[46,9210,2723],{},[46,9212,9213],{"class":48,"line":551},[46,9214,408],{"emptyLinePlaceholder":407},[46,9216,9217],{"class":48,"line":557},[46,9218,2586],{},[46,9220,9221],{"class":48,"line":562},[46,9222,9223],{},"     * Execute the console command.\n",[46,9225,9226],{"class":48,"line":568},[46,9227,2596],{},[46,9229,9230],{"class":48,"line":573},[46,9231,9232],{},"     * @return int\n",[46,9234,9235],{"class":48,"line":579},[46,9236,2606],{},[46,9238,9239],{"class":48,"line":584},[46,9240,9241],{},"    public function handle()\n",[46,9243,9244],{"class":48,"line":590},[46,9245,2713],{},[46,9247,9248],{"class":48,"line":595},[46,9249,9250],{},"        \n",[46,9252,9253],{"class":48,"line":601},[46,9254,2723],{},[46,9256,9257],{"class":48,"line":606},[46,9258,2752],{},[13,9260,9261,9262,9265],{},"まずは上記のように、シグネチャと説明を記述します。シグネチャでは",[31,9263,9264],{},"lang:dump"," とすると",[24,9267,9270],{"className":9268,"code":9269,"language":29},[27],"php artisan lang:dump\n",[31,9271,9269],{"__ignoreMap":33},[13,9273,9274],{},"と入力するとこのコマンドを実行できます。",[352,9276,9277],{"id":9277},"言語ディレクトリからファイルを読み込む",[13,9279,9280,9281,9284],{},"コマンドの内容は",[31,9282,9283],{},"handle()","に記述します。全体は以下の通りです。",[24,9286,9288],{"className":2394,"code":9287,"language":882,"meta":33,"style":33},"use Illuminate\\Support\\Facades\\File;\nuse Illuminate\\Support\\Facades\\Lang;\n\npublic function handle()\n{\n    $suports = [\"ja\",\"en\"];\n    \n    foreach($suports as $lng){\n        $langdir = resource_path('lang\u002F'.$lng);\n        if(is_dir($langdir)){\n            $files = scandir($langdir);\n            if($files === false) continue;\n\n            $files = array_filter($files,function($f){\n                return strpos($f,'.php') !== false;\n            });\n\n            $trans = [];\n            foreach($files as $f){\n                $content_key = str_replace('.php','',$f);\n                $content = include resource_path(\"lang\u002F$lng\u002F$f\");\n                $trans[$content_key] = $content;\n            }\n\n            $json = json_encode($trans,JSON_UNESCAPED_UNICODE);\n            File::put(resource_path('lang\u002F'.$lng.'.json'),$json);\n            File::put(base_path('nuxt\u002Flang\u002F'.$lng.'.json'),$json);\n        }else{\n            print(\"Lang directory for ${$lng} dose not exisits\");\n        }\n    }\n    return Command::SUCCESS;\n}\n",[31,9289,9290,9295,9300,9304,9309,9313,9318,9322,9327,9332,9337,9342,9347,9351,9356,9361,9366,9370,9375,9380,9385,9390,9395,9399,9403,9408,9413,9418,9423,9428,9432,9436,9441],{"__ignoreMap":33},[46,9291,9292],{"class":48,"line":49},[46,9293,9294],{},"use Illuminate\\Support\\Facades\\File;\n",[46,9296,9297],{"class":48,"line":70},[46,9298,9299],{},"use Illuminate\\Support\\Facades\\Lang;\n",[46,9301,9302],{"class":48,"line":82},[46,9303,408],{"emptyLinePlaceholder":407},[46,9305,9306],{"class":48,"line":91},[46,9307,9308],{},"public function handle()\n",[46,9310,9311],{"class":48,"line":102},[46,9312,2572],{},[46,9314,9315],{"class":48,"line":112},[46,9316,9317],{},"    $suports = [\"ja\",\"en\"];\n",[46,9319,9320],{"class":48,"line":121},[46,9321,6065],{},[46,9323,9324],{"class":48,"line":131},[46,9325,9326],{},"    foreach($suports as $lng){\n",[46,9328,9329],{"class":48,"line":139},[46,9330,9331],{},"        $langdir = resource_path('lang\u002F'.$lng);\n",[46,9333,9334],{"class":48,"line":147},[46,9335,9336],{},"        if(is_dir($langdir)){\n",[46,9338,9339],{"class":48,"line":157},[46,9340,9341],{},"            $files = scandir($langdir);\n",[46,9343,9344],{"class":48,"line":171},[46,9345,9346],{},"            if($files === false) continue;\n",[46,9348,9349],{"class":48,"line":183},[46,9350,408],{"emptyLinePlaceholder":407},[46,9352,9353],{"class":48,"line":195},[46,9354,9355],{},"            $files = array_filter($files,function($f){\n",[46,9357,9358],{"class":48,"line":206},[46,9359,9360],{},"                return strpos($f,'.php') !== false;\n",[46,9362,9363],{"class":48,"line":4},[46,9364,9365],{},"            });\n",[46,9367,9368],{"class":48,"line":223},[46,9369,408],{"emptyLinePlaceholder":407},[46,9371,9372],{"class":48,"line":231},[46,9373,9374],{},"            $trans = [];\n",[46,9376,9377],{"class":48,"line":242},[46,9378,9379],{},"            foreach($files as $f){\n",[46,9381,9382],{"class":48,"line":253},[46,9383,9384],{},"                $content_key = str_replace('.php','',$f);\n",[46,9386,9387],{"class":48,"line":264},[46,9388,9389],{},"                $content = include resource_path(\"lang\u002F$lng\u002F$f\");\n",[46,9391,9392],{"class":48,"line":275},[46,9393,9394],{},"                $trans[$content_key] = $content;\n",[46,9396,9397],{"class":48,"line":284},[46,9398,5380],{},[46,9400,9401],{"class":48,"line":296},[46,9402,408],{"emptyLinePlaceholder":407},[46,9404,9405],{"class":48,"line":305},[46,9406,9407],{},"            $json = json_encode($trans,JSON_UNESCAPED_UNICODE);\n",[46,9409,9410],{"class":48,"line":313},[46,9411,9412],{},"            File::put(resource_path('lang\u002F'.$lng.'.json'),$json);\n",[46,9414,9415],{"class":48,"line":323},[46,9416,9417],{},"            File::put(base_path('nuxt\u002Flang\u002F'.$lng.'.json'),$json);\n",[46,9419,9420],{"class":48,"line":529},[46,9421,9422],{},"        }else{\n",[46,9424,9425],{"class":48,"line":535},[46,9426,9427],{},"            print(\"Lang directory for ${$lng} dose not exisits\");\n",[46,9429,9430],{"class":48,"line":540},[46,9431,3141],{},[46,9433,9434],{"class":48,"line":546},[46,9435,2723],{},[46,9437,9438],{"class":48,"line":551},[46,9439,9440],{},"    return Command::SUCCESS;\n",[46,9442,9443],{"class":48,"line":557},[46,9444,2752],{},[24,9446,9448],{"className":2394,"code":9447,"language":882,"meta":33,"style":33},"$suports = [\"ja\",\"en\"];\n\nforeach($suports as $lng){\n\n}\n",[31,9449,9450,9455,9459,9464,9468],{"__ignoreMap":33},[46,9451,9452],{"class":48,"line":49},[46,9453,9454],{},"$suports = [\"ja\",\"en\"];\n",[46,9456,9457],{"class":48,"line":70},[46,9458,408],{"emptyLinePlaceholder":407},[46,9460,9461],{"class":48,"line":82},[46,9462,9463],{},"foreach($suports as $lng){\n",[46,9465,9466],{"class":48,"line":91},[46,9467,408],{"emptyLinePlaceholder":407},[46,9469,9470],{"class":48,"line":102},[46,9471,2752],{},[13,9473,9474],{},"まずは取得予定の言語の配列を用意します。それを再帰的に処理します。",[13,9476,9477],{},"元となる言語ディレクトリからファイルの一覧を取得します。",[24,9479,9481],{"className":2394,"code":9480,"language":882,"meta":33,"style":33},"foreach($suports as $lng){\n    $langdir = resource_path('lang\u002F'.$lng);\n    if(is_dir($langdir)){\n        $files = scandir($langdir);\n        if($files === false) continue;\n\n        $files = array_filter($files,function($f){\n            return strpos($f,'.php') !== false;\n        });\n\n        $trans = [];\n        foreach($files as $f){\n            $content_key = str_replace('.php','',$f);\n            $content = include resource_path(\"lang\u002F$lng\u002F$f\");\n            $trans[$content_key] = $content;\n        }\n\n        \u002F\u002F ....\n    }\n}\n",[31,9482,9483,9487,9492,9497,9502,9507,9511,9516,9521,9525,9529,9534,9539,9544,9549,9554,9558,9562,9567,9571],{"__ignoreMap":33},[46,9484,9485],{"class":48,"line":49},[46,9486,9463],{},[46,9488,9489],{"class":48,"line":70},[46,9490,9491],{},"    $langdir = resource_path('lang\u002F'.$lng);\n",[46,9493,9494],{"class":48,"line":82},[46,9495,9496],{},"    if(is_dir($langdir)){\n",[46,9498,9499],{"class":48,"line":91},[46,9500,9501],{},"        $files = scandir($langdir);\n",[46,9503,9504],{"class":48,"line":102},[46,9505,9506],{},"        if($files === false) continue;\n",[46,9508,9509],{"class":48,"line":112},[46,9510,408],{"emptyLinePlaceholder":407},[46,9512,9513],{"class":48,"line":121},[46,9514,9515],{},"        $files = array_filter($files,function($f){\n",[46,9517,9518],{"class":48,"line":131},[46,9519,9520],{},"            return strpos($f,'.php') !== false;\n",[46,9522,9523],{"class":48,"line":139},[46,9524,7354],{},[46,9526,9527],{"class":48,"line":147},[46,9528,408],{"emptyLinePlaceholder":407},[46,9530,9531],{"class":48,"line":157},[46,9532,9533],{},"        $trans = [];\n",[46,9535,9536],{"class":48,"line":171},[46,9537,9538],{},"        foreach($files as $f){\n",[46,9540,9541],{"class":48,"line":183},[46,9542,9543],{},"            $content_key = str_replace('.php','',$f);\n",[46,9545,9546],{"class":48,"line":195},[46,9547,9548],{},"            $content = include resource_path(\"lang\u002F$lng\u002F$f\");\n",[46,9550,9551],{"class":48,"line":206},[46,9552,9553],{},"            $trans[$content_key] = $content;\n",[46,9555,9556],{"class":48,"line":4},[46,9557,3141],{},[46,9559,9560],{"class":48,"line":223},[46,9561,408],{"emptyLinePlaceholder":407},[46,9563,9564],{"class":48,"line":231},[46,9565,9566],{},"        \u002F\u002F ....\n",[46,9568,9569],{"class":48,"line":242},[46,9570,2723],{},[46,9572,9573],{"class":48,"line":253},[46,9574,2752],{},[13,9576,9577,9580,9581,9584,9585,9587,9588,9590,9591,9594],{},[31,9578,9579],{},"resource_path('lang\u002F'.$lng)","で先程の言語ディレクトリのパスを取得し、",[31,9582,9583],{},"scandir()","を用いて内部のファイルを配列で取得します。",[31,9586,9583],{},"はphp以外のファイルや",[31,9589,4073],{},"みたいなSELinuxが勝手に作る謎ファイルも読み取ってしまうので、",[31,9592,9593],{},"array_filter()","を用いてフィルターします。",[352,9596,9598],{"id":9597},"一つの配列に打ち込んでjson化する","一つの配列に打ち込んでJSON化する",[24,9600,9602],{"className":2394,"code":9601,"language":882,"meta":33,"style":33},"$trans = [];\nforeach($files as $f){\n    $content_key = str_replace('.php','',$f);\n    $content = include resource_path(\"lang\u002F$lng\u002F$f\");\n    $trans[$content_key] = $content;\n}\n",[31,9603,9604,9609,9614,9619,9624,9629],{"__ignoreMap":33},[46,9605,9606],{"class":48,"line":49},[46,9607,9608],{},"$trans = [];\n",[46,9610,9611],{"class":48,"line":70},[46,9612,9613],{},"foreach($files as $f){\n",[46,9615,9616],{"class":48,"line":82},[46,9617,9618],{},"    $content_key = str_replace('.php','',$f);\n",[46,9620,9621],{"class":48,"line":91},[46,9622,9623],{},"    $content = include resource_path(\"lang\u002F$lng\u002F$f\");\n",[46,9625,9626],{"class":48,"line":102},[46,9627,9628],{},"    $trans[$content_key] = $content;\n",[46,9630,9631],{"class":48,"line":112},[46,9632,2752],{},[13,9634,9635,9636,9639,9640,9643,9644,9647],{},"JSONではphpファイル名を一次キーとして利用したいので ",[31,9637,9638],{},"str_replace('.php','',$f)","でファイル名を取得します。",[31,9641,9642],{},"include resource_path(\"lang\u002F$lng\u002F$f\")","でphpファイルの記述を取得ます。そしてJSONにする配列に、ファイル名をキーとして打ち込みます。",[31,9645,9646],{},"$trans[$content_key] = $content;"," それをスキャンした言語PHPファイル全てに行います。",[352,9649,9650],{"id":9650},"ファイルを出力",[24,9652,9654],{"className":2394,"code":9653,"language":882,"meta":33,"style":33},"$json = json_encode($trans,JSON_UNESCAPED_UNICODE);\nFile::put(resource_path('lang\u002F'.$lng.'.json'),$json);\n",[31,9655,9656,9661],{"__ignoreMap":33},[46,9657,9658],{"class":48,"line":49},[46,9659,9660],{},"$json = json_encode($trans,JSON_UNESCAPED_UNICODE);\n",[46,9662,9663],{"class":48,"line":70},[46,9664,9665],{},"File::put(resource_path('lang\u002F'.$lng.'.json'),$json);\n",[13,9667,9668,9669,9672,9673,9675,9676,9679],{},"そして１つにまとめた配列を",[31,9670,9671],{},"json_encode","をしておき、それを",[31,9674,8845],{}," 配下します。その際には",[31,9677,9678],{},"言語名.json","となるようにしておきます。",[20,9681,9682],{"id":9682},"全容と実行",[13,9684,9685],{},"コードは以下の通りです。",[24,9687,9689],{"className":2394,"code":9688,"language":882,"meta":33,"style":33},"\u003C?php\n\nnamespace App\\Console\\Commands;\n\nuse Illuminate\\Console\\Command;\nuse Illuminate\\Support\\Facades\\File;\nuse Illuminate\\Support\\Facades\\Lang;\n\nclass Dumblang extends Command\n{\n    \u002F**\n     * The name and signature of the console command.\n     *\n     * @var string\n     *\u002F\n    protected $signature = 'lang:dump';\n\n    \u002F**\n     * The console command description.\n     *\n     * @var string\n     *\u002F\n    protected $description = 'Convert each php lang files to JSON files.';\n\n    \u002F**\n     * Create a new command instance.\n     *\n     * @return void\n     *\u002F\n    public function __construct()\n    {\n        parent::__construct();\n    }\n\n    \u002F**\n     * Execute the console command.\n     *\n     * @return int\n     *\u002F\n    public function handle()\n    {\n        $suports = config('app.support_langs');\n        \n        foreach($suports as $lng){\n            $langdir = resource_path('lang\u002F'.$lng);\n            if(is_dir($langdir)){\n                $files = scandir($langdir);\n                if($files === false) continue;\n\n                $files = array_filter($files,function($f){\n                    return strpos($f,'.php') !== false;\n                });\n\n                $trans = [];\n                foreach($files as $f){\n                    $content_key = str_replace('.php','',$f);\n                    $content = include resource_path(\"lang\u002F$lng\u002F$f\");\n                    $trans[$content_key] = $content;\n                }\n\n                $json = json_encode($trans,JSON_UNESCAPED_UNICODE);\n                File::put(resource_path('lang\u002F'.$lng.'.json'),$json);\n            }else{\n                print(\"Lang directory for ${$lng} dose not exisits\");\n            }\n        }\n        return Command::SUCCESS;\n    }\n}\n",[31,9690,9691,9695,9699,9703,9707,9711,9715,9719,9723,9727,9731,9735,9739,9743,9747,9751,9755,9759,9763,9767,9771,9775,9779,9783,9787,9791,9795,9799,9803,9807,9811,9815,9819,9823,9827,9831,9835,9839,9843,9847,9851,9855,9860,9864,9869,9874,9879,9884,9889,9893,9898,9903,9908,9912,9917,9922,9927,9932,9937,9942,9946,9951,9956,9961,9966,9970,9974,9979,9983],{"__ignoreMap":33},[46,9692,9693],{"class":48,"line":49},[46,9694,2525],{},[46,9696,9697],{"class":48,"line":70},[46,9698,408],{"emptyLinePlaceholder":407},[46,9700,9701],{"class":48,"line":82},[46,9702,9089],{},[46,9704,9705],{"class":48,"line":91},[46,9706,408],{"emptyLinePlaceholder":407},[46,9708,9709],{"class":48,"line":102},[46,9710,9098],{},[46,9712,9713],{"class":48,"line":112},[46,9714,9294],{},[46,9716,9717],{"class":48,"line":121},[46,9718,9299],{},[46,9720,9721],{"class":48,"line":131},[46,9722,408],{"emptyLinePlaceholder":407},[46,9724,9725],{"class":48,"line":139},[46,9726,9107],{},[46,9728,9729],{"class":48,"line":147},[46,9730,2572],{},[46,9732,9733],{"class":48,"line":157},[46,9734,2586],{},[46,9736,9737],{"class":48,"line":171},[46,9738,9120],{},[46,9740,9741],{"class":48,"line":183},[46,9742,2596],{},[46,9744,9745],{"class":48,"line":195},[46,9746,9129],{},[46,9748,9749],{"class":48,"line":206},[46,9750,2606],{},[46,9752,9753],{"class":48,"line":4},[46,9754,9138],{},[46,9756,9757],{"class":48,"line":223},[46,9758,408],{"emptyLinePlaceholder":407},[46,9760,9761],{"class":48,"line":231},[46,9762,2586],{},[46,9764,9765],{"class":48,"line":242},[46,9766,9151],{},[46,9768,9769],{"class":48,"line":253},[46,9770,2596],{},[46,9772,9773],{"class":48,"line":264},[46,9774,9129],{},[46,9776,9777],{"class":48,"line":275},[46,9778,2606],{},[46,9780,9781],{"class":48,"line":284},[46,9782,9168],{},[46,9784,9785],{"class":48,"line":296},[46,9786,408],{"emptyLinePlaceholder":407},[46,9788,9789],{"class":48,"line":305},[46,9790,2586],{},[46,9792,9793],{"class":48,"line":313},[46,9794,9181],{},[46,9796,9797],{"class":48,"line":323},[46,9798,2596],{},[46,9800,9801],{"class":48,"line":529},[46,9802,3060],{},[46,9804,9805],{"class":48,"line":535},[46,9806,2606],{},[46,9808,9809],{"class":48,"line":540},[46,9810,3069],{},[46,9812,9813],{"class":48,"line":546},[46,9814,2713],{},[46,9816,9817],{"class":48,"line":551},[46,9818,9206],{},[46,9820,9821],{"class":48,"line":557},[46,9822,2723],{},[46,9824,9825],{"class":48,"line":562},[46,9826,408],{"emptyLinePlaceholder":407},[46,9828,9829],{"class":48,"line":568},[46,9830,2586],{},[46,9832,9833],{"class":48,"line":573},[46,9834,9223],{},[46,9836,9837],{"class":48,"line":579},[46,9838,2596],{},[46,9840,9841],{"class":48,"line":584},[46,9842,9232],{},[46,9844,9845],{"class":48,"line":590},[46,9846,2606],{},[46,9848,9849],{"class":48,"line":595},[46,9850,9241],{},[46,9852,9853],{"class":48,"line":601},[46,9854,2713],{},[46,9856,9857],{"class":48,"line":606},[46,9858,9859],{},"        $suports = config('app.support_langs');\n",[46,9861,9862],{"class":48,"line":611},[46,9863,9250],{},[46,9865,9866],{"class":48,"line":616},[46,9867,9868],{},"        foreach($suports as $lng){\n",[46,9870,9871],{"class":48,"line":622},[46,9872,9873],{},"            $langdir = resource_path('lang\u002F'.$lng);\n",[46,9875,9876],{"class":48,"line":627},[46,9877,9878],{},"            if(is_dir($langdir)){\n",[46,9880,9881],{"class":48,"line":633},[46,9882,9883],{},"                $files = scandir($langdir);\n",[46,9885,9886],{"class":48,"line":638},[46,9887,9888],{},"                if($files === false) continue;\n",[46,9890,9891],{"class":48,"line":2744},[46,9892,408],{"emptyLinePlaceholder":407},[46,9894,9895],{"class":48,"line":2749},[46,9896,9897],{},"                $files = array_filter($files,function($f){\n",[46,9899,9900],{"class":48,"line":3225},[46,9901,9902],{},"                    return strpos($f,'.php') !== false;\n",[46,9904,9905],{"class":48,"line":3231},[46,9906,9907],{},"                });\n",[46,9909,9910],{"class":48,"line":3236},[46,9911,408],{"emptyLinePlaceholder":407},[46,9913,9914],{"class":48,"line":3242},[46,9915,9916],{},"                $trans = [];\n",[46,9918,9919],{"class":48,"line":3247},[46,9920,9921],{},"                foreach($files as $f){\n",[46,9923,9924],{"class":48,"line":3253},[46,9925,9926],{},"                    $content_key = str_replace('.php','',$f);\n",[46,9928,9929],{"class":48,"line":3258},[46,9930,9931],{},"                    $content = include resource_path(\"lang\u002F$lng\u002F$f\");\n",[46,9933,9934],{"class":48,"line":3263},[46,9935,9936],{},"                    $trans[$content_key] = $content;\n",[46,9938,9939],{"class":48,"line":3268},[46,9940,9941],{},"                }\n",[46,9943,9944],{"class":48,"line":3274},[46,9945,408],{"emptyLinePlaceholder":407},[46,9947,9948],{"class":48,"line":3279},[46,9949,9950],{},"                $json = json_encode($trans,JSON_UNESCAPED_UNICODE);\n",[46,9952,9953],{"class":48,"line":3284},[46,9954,9955],{},"                File::put(resource_path('lang\u002F'.$lng.'.json'),$json);\n",[46,9957,9958],{"class":48,"line":3289},[46,9959,9960],{},"            }else{\n",[46,9962,9963],{"class":48,"line":3295},[46,9964,9965],{},"                print(\"Lang directory for ${$lng} dose not exisits\");\n",[46,9967,9968],{"class":48,"line":3300},[46,9969,5380],{},[46,9971,9972],{"class":48,"line":3306},[46,9973,3141],{},[46,9975,9976],{"class":48,"line":3311},[46,9977,9978],{},"        return Command::SUCCESS;\n",[46,9980,9981],{"class":48,"line":3316},[46,9982,2723],{},[46,9984,9985],{"class":48,"line":3321},[46,9986,2752],{},[13,9988,9989],{},"ちょっと気をつける点としては",[336,9991,9992,9998],{},[339,9993,9994,9997],{},[31,9995,9996],{},"is_dir($langdir)"," で言語ディレクトリの存在チェック",[339,9999,10000,10002],{},[31,10001,9583],{}," で取得したファイルをフィルタする",[13,10004,10005,10006,1448,10008,10010],{},"実行してみると",[31,10007,8913],{},[31,10009,8910],{},"というのが作成され、みてみると",[24,10012,10014],{"className":10013,"code":9047,"language":29},[27],[31,10015,9047],{"__ignoreMap":33},[13,10017,10018],{},"↓",[24,10020,10022],{"className":8930,"code":10021,"filename":8913,"language":8932,"meta":33,"style":33},"{\n    \"auth\":{\n        \"login\":\"ログイン\",\n        \"logout\":\"ログアウト\",\n    },\n    \"words\":{\n        \"save\":\"保存\",\n        \"update\":\"更新\"\n    },\n    \"exceptions\":{\n        \"401\":\"ログインしてください。\",\n        \"model\":{\n            \"403\":\"このデータにアクセスできません。\",\n            \"404\":\"対応するデータが見つかりません。\"\n        }\n    }\n}\n",[31,10023,10024,10028,10038,10056,10074,10078,10088,10108,10126,10130,10141,10161,10172,10194,10212,10216,10220],{"__ignoreMap":33},[46,10025,10026],{"class":48,"line":49},[46,10027,2572],{"class":56},[46,10029,10030,10032,10034,10036],{"class":48,"line":70},[46,10031,8943],{"class":56},[46,10033,3685],{"class":4046},[46,10035,4238],{"class":56},[46,10037,3688],{"class":56},[46,10039,10040,10042,10044,10046,10048,10050,10052,10054],{"class":48,"line":82},[46,10041,8955],{"class":56},[46,10043,8958],{"class":3684},[46,10045,4238],{"class":56},[46,10047,57],{"class":56},[46,10049,4238],{"class":56},[46,10051,8967],{"class":63},[46,10053,4238],{"class":56},[46,10055,3701],{"class":56},[46,10057,10058,10060,10062,10064,10066,10068,10070,10072],{"class":48,"line":91},[46,10059,8955],{"class":56},[46,10061,8978],{"class":3684},[46,10063,4238],{"class":56},[46,10065,57],{"class":56},[46,10067,4238],{"class":56},[46,10069,8987],{"class":63},[46,10071,4238],{"class":56},[46,10073,3701],{"class":56},[46,10075,10076],{"class":48,"line":102},[46,10077,4891],{"class":56},[46,10079,10080,10082,10084,10086],{"class":48,"line":112},[46,10081,8943],{"class":56},[46,10083,8946],{"class":4046},[46,10085,4238],{"class":56},[46,10087,3688],{"class":56},[46,10089,10090,10092,10095,10097,10099,10101,10104,10106],{"class":48,"line":121},[46,10091,8955],{"class":56},[46,10093,10094],{"class":3684},"save",[46,10096,4238],{"class":56},[46,10098,57],{"class":56},[46,10100,4238],{"class":56},[46,10102,10103],{"class":63},"保存",[46,10105,4238],{"class":56},[46,10107,3701],{"class":56},[46,10109,10110,10112,10115,10117,10119,10121,10124],{"class":48,"line":131},[46,10111,8955],{"class":56},[46,10113,10114],{"class":3684},"update",[46,10116,4238],{"class":56},[46,10118,57],{"class":56},[46,10120,4238],{"class":56},[46,10122,10123],{"class":63},"更新",[46,10125,168],{"class":56},[46,10127,10128],{"class":48,"line":139},[46,10129,4891],{"class":56},[46,10131,10132,10134,10137,10139],{"class":48,"line":147},[46,10133,8943],{"class":56},[46,10135,10136],{"class":4046},"exceptions",[46,10138,4238],{"class":56},[46,10140,3688],{"class":56},[46,10142,10143,10145,10148,10150,10152,10154,10157,10159],{"class":48,"line":157},[46,10144,8955],{"class":56},[46,10146,10147],{"class":3684},"401",[46,10149,4238],{"class":56},[46,10151,57],{"class":56},[46,10153,4238],{"class":56},[46,10155,10156],{"class":63},"ログインしてください。",[46,10158,4238],{"class":56},[46,10160,3701],{"class":56},[46,10162,10163,10165,10168,10170],{"class":48,"line":171},[46,10164,8955],{"class":56},[46,10166,10167],{"class":3684},"model",[46,10169,4238],{"class":56},[46,10171,3688],{"class":56},[46,10173,10174,10177,10181,10183,10185,10187,10190,10192],{"class":48,"line":183},[46,10175,10176],{"class":56},"            \"",[46,10178,10180],{"class":10179},"sx098","403",[46,10182,4238],{"class":56},[46,10184,57],{"class":56},[46,10186,4238],{"class":56},[46,10188,10189],{"class":63},"このデータにアクセスできません。",[46,10191,4238],{"class":56},[46,10193,3701],{"class":56},[46,10195,10196,10198,10201,10203,10205,10207,10210],{"class":48,"line":195},[46,10197,10176],{"class":56},[46,10199,10200],{"class":10179},"404",[46,10202,4238],{"class":56},[46,10204,57],{"class":56},[46,10206,4238],{"class":56},[46,10208,10209],{"class":63},"対応するデータが見つかりません。",[46,10211,168],{"class":56},[46,10213,10214],{"class":48,"line":206},[46,10215,3141],{"class":56},[46,10217,10218],{"class":48,"line":4},[46,10219,2723],{"class":56},[46,10221,10222],{"class":48,"line":223},[46,10223,2752],{"class":56},[24,10225,10227],{"className":8930,"code":10226,"filename":8910,"language":8932,"meta":33,"style":33},"{\n    \"auth\":{\n        \"login\":\"Login\",\n        \"logout\":\"Logout\",\n    },\n    \"words\":{\n        \"save\":\"Saving\",\n        \"update\":\"Updating\"\n    },\n    \"exceptions\":{\n        \"401\":\"Please login.\",\n        \"model\":{\n            \"403\":\"You can not access to this data.\",\n            \"404\":\"The data you request is not found.\"\n        }\n    }\n}\n",[31,10228,10229,10233,10243,10262,10281,10285,10295,10314,10331,10335,10345,10364,10374,10393,10410,10414,10418],{"__ignoreMap":33},[46,10230,10231],{"class":48,"line":49},[46,10232,2572],{"class":56},[46,10234,10235,10237,10239,10241],{"class":48,"line":70},[46,10236,8943],{"class":56},[46,10238,3685],{"class":4046},[46,10240,4238],{"class":56},[46,10242,3688],{"class":56},[46,10244,10245,10247,10249,10251,10253,10255,10258,10260],{"class":48,"line":82},[46,10246,8955],{"class":56},[46,10248,8958],{"class":3684},[46,10250,4238],{"class":56},[46,10252,57],{"class":56},[46,10254,4238],{"class":56},[46,10256,10257],{"class":63},"Login",[46,10259,4238],{"class":56},[46,10261,3701],{"class":56},[46,10263,10264,10266,10268,10270,10272,10274,10277,10279],{"class":48,"line":91},[46,10265,8955],{"class":56},[46,10267,8978],{"class":3684},[46,10269,4238],{"class":56},[46,10271,57],{"class":56},[46,10273,4238],{"class":56},[46,10275,10276],{"class":63},"Logout",[46,10278,4238],{"class":56},[46,10280,3701],{"class":56},[46,10282,10283],{"class":48,"line":102},[46,10284,4891],{"class":56},[46,10286,10287,10289,10291,10293],{"class":48,"line":112},[46,10288,8943],{"class":56},[46,10290,8946],{"class":4046},[46,10292,4238],{"class":56},[46,10294,3688],{"class":56},[46,10296,10297,10299,10301,10303,10305,10307,10310,10312],{"class":48,"line":121},[46,10298,8955],{"class":56},[46,10300,10094],{"class":3684},[46,10302,4238],{"class":56},[46,10304,57],{"class":56},[46,10306,4238],{"class":56},[46,10308,10309],{"class":63},"Saving",[46,10311,4238],{"class":56},[46,10313,3701],{"class":56},[46,10315,10316,10318,10320,10322,10324,10326,10329],{"class":48,"line":131},[46,10317,8955],{"class":56},[46,10319,10114],{"class":3684},[46,10321,4238],{"class":56},[46,10323,57],{"class":56},[46,10325,4238],{"class":56},[46,10327,10328],{"class":63},"Updating",[46,10330,168],{"class":56},[46,10332,10333],{"class":48,"line":139},[46,10334,4891],{"class":56},[46,10336,10337,10339,10341,10343],{"class":48,"line":147},[46,10338,8943],{"class":56},[46,10340,10136],{"class":4046},[46,10342,4238],{"class":56},[46,10344,3688],{"class":56},[46,10346,10347,10349,10351,10353,10355,10357,10360,10362],{"class":48,"line":157},[46,10348,8955],{"class":56},[46,10350,10147],{"class":3684},[46,10352,4238],{"class":56},[46,10354,57],{"class":56},[46,10356,4238],{"class":56},[46,10358,10359],{"class":63},"Please login.",[46,10361,4238],{"class":56},[46,10363,3701],{"class":56},[46,10365,10366,10368,10370,10372],{"class":48,"line":171},[46,10367,8955],{"class":56},[46,10369,10167],{"class":3684},[46,10371,4238],{"class":56},[46,10373,3688],{"class":56},[46,10375,10376,10378,10380,10382,10384,10386,10389,10391],{"class":48,"line":183},[46,10377,10176],{"class":56},[46,10379,10180],{"class":10179},[46,10381,4238],{"class":56},[46,10383,57],{"class":56},[46,10385,4238],{"class":56},[46,10387,10388],{"class":63},"You can not access to this data.",[46,10390,4238],{"class":56},[46,10392,3701],{"class":56},[46,10394,10395,10397,10399,10401,10403,10405,10408],{"class":48,"line":195},[46,10396,10176],{"class":56},[46,10398,10200],{"class":10179},[46,10400,4238],{"class":56},[46,10402,57],{"class":56},[46,10404,4238],{"class":56},[46,10406,10407],{"class":63},"The data you request is not found.",[46,10409,168],{"class":56},[46,10411,10412],{"class":48,"line":206},[46,10413,3141],{"class":56},[46,10415,10416],{"class":48,"line":4},[46,10417,2723],{"class":56},[46,10419,10420],{"class":48,"line":223},[46,10421,2752],{"class":56},[13,10423,10424,10425,10427],{},"これでPHPファイルだけでJSONの言語ファイルも作成して、フロントでのVueの言語ファイルなどに提供することができるようになります。動的にこのJSONファイルは生成するので、バージョン管理をするときは",[31,10426,6007],{},"で指定しておくといいです。そしてデプロイや更新時にはこのコマンドを打ち忘れないようにしましょう。",[13,10429,10430],{},"ちなみにですが、実際にやっていることは特定のディレクトリのPHPファイルの内容を取得して、それをJSONにして生成したファイルを置いているだけなので素のPHP、pythonやrubyとか他の言語でも行けると思いますよ。",[1851,10432,10433],{},"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 .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}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 .sx098, html code.shiki .sx098{--shiki-default:#F78C6C}",{"title":33,"searchDepth":82,"depth":82,"links":10435},[10436,10437,10438,10445],{"id":8917,"depth":70,"text":8918},{"id":8629,"depth":70,"text":8629},{"id":9030,"depth":70,"text":9030,"children":10439},[10440,10441,10442,10443,10444],{"id":9033,"depth":82,"text":9033},{"id":9055,"depth":82,"text":9055},{"id":9277,"depth":82,"text":9277},{"id":9597,"depth":82,"text":9598},{"id":9650,"depth":82,"text":9650},{"id":9682,"depth":70,"text":9682},[8675],"2022-05-22",{},"\u002Farticles\u002Flaravel-i18n-json-dump",{"title":8837,"description":8837},"articles\u002Flaravel-i18n-json-dump",[882,1583,10453],"js","tdUMQGHoIH7SI9TXJlID3MGccNOOjq6udWMjq_HTqC0",{"id":10456,"title":10457,"body":10458,"category":10922,"createdAt":10923,"description":10924,"extension":1883,"index":1884,"meta":10925,"navigation":407,"path":10926,"publish":407,"seo":10927,"series":1884,"seriesTitle":1884,"stem":10928,"tag":10929,"thumbnail":8833,"updatedAt":1884,"__hash__":10930},"articles\u002Farticles\u002Flaravel-custom-faker.md","Laravelでカスタムなフェイカーを作成する。",{"type":10,"value":10459,"toc":10913},[10460,10463,10472,10475,10495,10498,10501,10505,10519,10524,10556,10563,10576,10579,10586,10639,10642,10645,10648,10651,10657,10804,10821,10824,10833,10892,10899,10902,10905,10911],[13,10461,10462],{},"こんにちはjunです。Laravelはフルスタックフレームワークと言われるほど開発者に嬉しい機能が揃っています。その中でFakerと呼ばれるダミーデータを挿入する機能はよく使用します。ページングやいろんな文字を入れてみて、ビュー側やロジックなどが問題ないかを確かめることができるので、効率的な開発には必要不可欠です。",[13,10464,10465,10466,10471],{},"Fakerは標準で英語ですが、設定によって日本語にすることができます。FakerはPHPfakerというDevライブラリを使用しており、",[725,10467,10470],{"href":10468,"rel":10469},"https:\u002F\u002Ffakerphp.github.io\u002Fformatters\u002Fnumbers-and-strings\u002F",[729],"PHP Faker Formatters","で使用できるFakerの一覧を見れます。これらのFakerはおもにFactoryで使用します。",[13,10473,10474],{},"例えば氏名、メールアドレス、文章などは以下の通りです。",[24,10476,10478],{"className":2394,"code":10477,"language":882,"meta":33,"style":33},"$this->faker->name();\n$this->faker->email();\n$this->faker->realText();\n",[31,10479,10480,10485,10490],{"__ignoreMap":33},[46,10481,10482],{"class":48,"line":49},[46,10483,10484],{},"$this->faker->name();\n",[46,10486,10487],{"class":48,"line":70},[46,10488,10489],{},"$this->faker->email();\n",[46,10491,10492],{"class":48,"line":82},[46,10493,10494],{},"$this->faker->realText();\n",[13,10496,10497],{},"上記の通りそれっぽいダミーデータを作れるのですが、時たまにアプリで必要なデータ形式のFakerがほしかったり、ランダム・特定条件のマスターのIDを出してほしい、もう少し実装するサービスに即した内容を出してほしいと言った要望がある場合はFakerを自作する必要があります。",[13,10499,10500],{},"今回はそのカスタムFakerの実装を解説したいと思います。",[20,10502,10504],{"id":10503},"カスタムfakerのクラスを作成","カスタムFakerのクラスを作成",[13,10506,10507,10508,10511,10512,10515,10516,10518],{},"最初にカスタムFakerのクラスを作成します。",[31,10509,10510],{},"App\u002FFaker\u002FCustome.php","を作成します。今回は",[31,10513,10514],{},"Custome.php","に実装したいFakerを作成しますが、もしクラスごとにわたい場合は",[31,10517,10514],{},"をFakerごとのクラスに分けてください。",[13,10520,791,10521,10523],{},[31,10522,10514],{},"は以下のように記述します。",[24,10525,10527],{"className":2394,"code":10526,"filename":10510,"language":882,"meta":33,"style":33},"namespace App\\Faker;\nuse Faker\\Provider\\Base;\n\nclass Custome extends Base{\n\n}\n\n",[31,10528,10529,10534,10539,10543,10548,10552],{"__ignoreMap":33},[46,10530,10531],{"class":48,"line":49},[46,10532,10533],{},"namespace App\\Faker;\n",[46,10535,10536],{"class":48,"line":70},[46,10537,10538],{},"use Faker\\Provider\\Base;\n",[46,10540,10541],{"class":48,"line":82},[46,10542,408],{"emptyLinePlaceholder":407},[46,10544,10545],{"class":48,"line":91},[46,10546,10547],{},"class Custome extends Base{\n",[46,10549,10550],{"class":48,"line":102},[46,10551,408],{"emptyLinePlaceholder":407},[46,10553,10554],{"class":48,"line":112},[46,10555,2752],{},[13,10557,10558,10559,10562],{},"ここでは",[31,10560,10561],{},"Faker\\Provider\\Base","を継承させてください。Fakerの元ファイルもFakerメソッドを定義しているクラスで継承しています。",[13,10564,10565,10567,10568,10571,10572,10575],{},[31,10566,10561],{},"からは",[31,10569,10570],{},"randomElements()","といったランダムな配列を取得する、任意範囲の数字を取得する",[31,10573,10574],{},"numberBetween()","といった便利なメソッドを使用できます。",[352,10577,10578],{"id":10578},"フェイカーの書き方",[13,10580,10581,10582,10585],{},"例えば、食品の名前を出してくれる",[31,10583,10584],{},"foodname()","というFakerを作ってみるとします。",[24,10587,10589],{"className":2394,"code":10588,"filename":10510,"language":882,"meta":33,"style":33},"namespace App\\Faker;\nuse Faker\\Provider\\Base;\n\nclass Custome extends Base{\n\n     protected static $foodName = ['ラーメン','パスタ','おにぎり','パン','炒飯']\n\n     public function foodname(){\n        return static::randomElement(static::$foodName);\n     }\n}\n\n",[31,10590,10591,10595,10599,10603,10607,10611,10616,10620,10625,10630,10635],{"__ignoreMap":33},[46,10592,10593],{"class":48,"line":49},[46,10594,10533],{},[46,10596,10597],{"class":48,"line":70},[46,10598,10538],{},[46,10600,10601],{"class":48,"line":82},[46,10602,408],{"emptyLinePlaceholder":407},[46,10604,10605],{"class":48,"line":91},[46,10606,10547],{},[46,10608,10609],{"class":48,"line":102},[46,10610,408],{"emptyLinePlaceholder":407},[46,10612,10613],{"class":48,"line":112},[46,10614,10615],{},"     protected static $foodName = ['ラーメン','パスタ','おにぎり','パン','炒飯']\n",[46,10617,10618],{"class":48,"line":121},[46,10619,408],{"emptyLinePlaceholder":407},[46,10621,10622],{"class":48,"line":131},[46,10623,10624],{},"     public function foodname(){\n",[46,10626,10627],{"class":48,"line":139},[46,10628,10629],{},"        return static::randomElement(static::$foodName);\n",[46,10631,10632],{"class":48,"line":147},[46,10633,10634],{},"     }\n",[46,10636,10637],{"class":48,"line":157},[46,10638,2752],{},[13,10640,10641],{},"このような感じで、配列に静的なプロパティを作成します。そしてメソッドでそのリストからランダムで呼び出す処理を実装すれば大丈夫です。DB上にあるマスターなどを使用したい場合、DBと接続して取得してもいいと思います。",[13,10643,10644],{},"引数を設定できるので、特定の食品だけ取り出すみたいな処理を加えていいでしょう。まずカスタムFakerメソッドを実装できたので、実際に使えるようにします。",[20,10646,10647],{"id":10647},"サービスプロバイダを作成",[13,10649,10650],{},"Laravelの標準のFakerはサービスプロバイダでシングルトンとして登録されています。それをオーバーライドするような処理を行っています。",[13,10652,10653,10656],{},[31,10654,10655],{},"App\u002FProviders\u002FFakerServiceProvider.php","配下にサービスプロバイダを作成します。",[24,10658,10660],{"className":2394,"code":10659,"filename":10655,"language":882,"meta":33,"style":33},"\nnamespace App\\Providers;\nuse Faker\\{Factory, Generator};\nuse Illuminate\\Support\\ServiceProvider;\nuse App\\Faker\\Custome;\n\nclass FakerServiceProvider extends ServiceProvider\n{\n    \u002F**\n     * Register services.\n     *\n     * @return void\n     *\u002F\n    public function register()\n    {\n        $this->app->singleton(Generator::class, function () {\n            $faker = Factory::create(config('app.faker_locale'));\n            $faker->addProvider(new Custome($faker));\n            return $faker;\n        });\n    }\n\n    \u002F**\n     * Bootstrap services.\n     *\n     * @return void\n     *\u002F\n    public function boot()\n    {\n        \u002F\u002F\n    }\n}\n",[31,10661,10662,10666,10671,10676,10681,10686,10690,10695,10699,10703,10708,10712,10716,10720,10725,10729,10734,10739,10744,10749,10753,10757,10761,10765,10770,10774,10778,10782,10787,10791,10796,10800],{"__ignoreMap":33},[46,10663,10664],{"class":48,"line":49},[46,10665,408],{"emptyLinePlaceholder":407},[46,10667,10668],{"class":48,"line":70},[46,10669,10670],{},"namespace App\\Providers;\n",[46,10672,10673],{"class":48,"line":82},[46,10674,10675],{},"use Faker\\{Factory, Generator};\n",[46,10677,10678],{"class":48,"line":91},[46,10679,10680],{},"use Illuminate\\Support\\ServiceProvider;\n",[46,10682,10683],{"class":48,"line":102},[46,10684,10685],{},"use App\\Faker\\Custome;\n",[46,10687,10688],{"class":48,"line":112},[46,10689,408],{"emptyLinePlaceholder":407},[46,10691,10692],{"class":48,"line":121},[46,10693,10694],{},"class FakerServiceProvider extends ServiceProvider\n",[46,10696,10697],{"class":48,"line":131},[46,10698,2572],{},[46,10700,10701],{"class":48,"line":139},[46,10702,2586],{},[46,10704,10705],{"class":48,"line":147},[46,10706,10707],{},"     * Register services.\n",[46,10709,10710],{"class":48,"line":157},[46,10711,2596],{},[46,10713,10714],{"class":48,"line":171},[46,10715,3060],{},[46,10717,10718],{"class":48,"line":183},[46,10719,2606],{},[46,10721,10722],{"class":48,"line":195},[46,10723,10724],{},"    public function register()\n",[46,10726,10727],{"class":48,"line":206},[46,10728,2713],{},[46,10730,10731],{"class":48,"line":4},[46,10732,10733],{},"        $this->app->singleton(Generator::class, function () {\n",[46,10735,10736],{"class":48,"line":223},[46,10737,10738],{},"            $faker = Factory::create(config('app.faker_locale'));\n",[46,10740,10741],{"class":48,"line":231},[46,10742,10743],{},"            $faker->addProvider(new Custome($faker));\n",[46,10745,10746],{"class":48,"line":242},[46,10747,10748],{},"            return $faker;\n",[46,10750,10751],{"class":48,"line":253},[46,10752,7354],{},[46,10754,10755],{"class":48,"line":264},[46,10756,2723],{},[46,10758,10759],{"class":48,"line":275},[46,10760,408],{"emptyLinePlaceholder":407},[46,10762,10763],{"class":48,"line":284},[46,10764,2586],{},[46,10766,10767],{"class":48,"line":296},[46,10768,10769],{},"     * Bootstrap services.\n",[46,10771,10772],{"class":48,"line":305},[46,10773,2596],{},[46,10775,10776],{"class":48,"line":313},[46,10777,3060],{},[46,10779,10780],{"class":48,"line":323},[46,10781,2606],{},[46,10783,10784],{"class":48,"line":529},[46,10785,10786],{},"    public function boot()\n",[46,10788,10789],{"class":48,"line":535},[46,10790,2713],{},[46,10792,10793],{"class":48,"line":540},[46,10794,10795],{},"        \u002F\u002F\n",[46,10797,10798],{"class":48,"line":546},[46,10799,2723],{},[46,10801,10802],{"class":48,"line":551},[46,10803,2752],{},[13,10805,10806,10809,10810,2266,10813,10816,10817,10820],{},[31,10807,10808],{},"Factory::create(config('app.faker_locale'))","としておくと、",[31,10811,10812],{},"config\u002Fapp.php",[31,10814,10815],{},"faker_locale","を用いて日本語化できます。そしてFakerのインスタンスに",[31,10818,10819],{},"addProvider()","を使用して、作成したカスタムフェイカーを追加します。",[352,10822,10823],{"id":10823},"サービスプロバイダの登録",[13,10825,10826,10827,2266,10829,10832],{},"このサービスプロバイダを",[31,10828,10812],{},[31,10830,10831],{},"providers"," に追加します。",[24,10834,10836],{"className":2394,"code":10835,"filename":10812,"language":882,"meta":33,"style":33},"'providers' => [\n\u002F*\n* Laravel Framework Service Providers...\n*\u002F\n\u002F\u002F 省略\n\n\u002F*\n* Application Service Providers...\n　追加\n*\u002F\n    App\\Providers\\FakerServiceProvider::class,\n],\n",[31,10837,10838,10842,10846,10851,10855,10860,10864,10868,10873,10878,10882,10887],{"__ignoreMap":33},[46,10839,10840],{"class":48,"line":49},[46,10841,2402],{},[46,10843,10844],{"class":48,"line":70},[46,10845,2411],{},[46,10847,10848],{"class":48,"line":82},[46,10849,10850],{},"* Laravel Framework Service Providers...\n",[46,10852,10853],{"class":48,"line":91},[46,10854,2444],{},[46,10856,10857],{"class":48,"line":102},[46,10858,10859],{},"\u002F\u002F 省略\n",[46,10861,10862],{"class":48,"line":112},[46,10863,408],{"emptyLinePlaceholder":407},[46,10865,10866],{"class":48,"line":121},[46,10867,2411],{},[46,10869,10870],{"class":48,"line":131},[46,10871,10872],{},"* Application Service Providers...\n",[46,10874,10875],{"class":48,"line":139},[46,10876,10877],{},"　追加\n",[46,10879,10880],{"class":48,"line":147},[46,10881,2444],{},[46,10883,10884],{"class":48,"line":157},[46,10885,10886],{},"    App\\Providers\\FakerServiceProvider::class,\n",[46,10888,10889],{"class":48,"line":171},[46,10890,10891],{},"],\n",[13,10893,10894,10895,10898],{},"そうするとfakerにて",[31,10896,10897],{},"$this->faker->foodname()","でランダムな食品名が出てきます。",[20,10900,10901],{"id":10901},"すぐに検証したい場合の方法",[13,10903,10904],{},"上記のFakerはFactoryなどで使用できますが、Tinkerなどですぐに確かめたいということがあると思います。そんな時はTinkerで以下のようにFakerのインスタンスを生成して、チェックできます。なお上記のサービスプロバイダーを登録している必要があります。",[24,10906,10909],{"className":10907,"code":10908,"language":29},[27],"php artian tinker\n>> $faker = app()->make(Faker\\Generator::class)\n>> $faker->foodname()\n=>'パスタ'\n",[31,10910,10908],{"__ignoreMap":33},[1851,10912,8214],{},{"title":33,"searchDepth":82,"depth":82,"links":10914},[10915,10918,10921],{"id":10503,"depth":70,"text":10504,"children":10916},[10917],{"id":10578,"depth":82,"text":10578},{"id":10647,"depth":70,"text":10647,"children":10919},[10920],{"id":10823,"depth":82,"text":10823},{"id":10901,"depth":70,"text":10901},[8675],"2022-04-02","Laravelでのカスタムフェイカーの作り方",{},"\u002Farticles\u002Flaravel-custom-faker",{"title":10457,"description":10924},"articles\u002Flaravel-custom-faker",[882,1583],"J5_Trsafdn5yWViWjr3IsUw74QmcNr3WsF_8_DUEepU",{"id":10932,"title":10933,"body":10934,"category":11394,"createdAt":11395,"description":11396,"extension":1883,"index":1884,"meta":11397,"navigation":407,"path":11398,"publish":407,"seo":11399,"series":1884,"seriesTitle":1884,"stem":11400,"tag":11401,"thumbnail":8833,"updatedAt":1884,"__hash__":11402},"articles\u002Farticles\u002Flaravel-protect-resource.md","Laravelでログインしたユーザーのみ読み取れる画像・アセットを設定する方法",{"type":10,"value":10935,"toc":11387},[10936,10939,10950,10952,10960,10966,10969,10972,10980,10983,11003,11030,11033,11040,11043,11046,11075,11078,11092,11099,11102,11109,11112,11119,11148,11155,11181,11184,11187,11253,11265,11292,11307,11324,11327,11330,11333,11339,11373,11382,11385],[13,10937,10938],{},"こんにちはjunです。Laravel製のシステムにてWebマニュアルを作成していた時、「あれ？マニュルはログインユーザーのみ見れる様にするけど、画像などはどうすればいいんだ？」という事態がありました。",[13,10940,10941,10942,10945,10946,10949],{},"Laravelはルートを定義し、その際に認証を設けることができます。ただし静的な画像（今回の様なあらかじめセットしておくマニュアル画像など）を配置する場合は",[31,10943,10944],{},"public","配下または、",[31,10947,10948],{},"storage\u002Fpublic","配下に置くことが多いと思います。しかしそれらのディレクトリは名の通りいかなるアクセスに対してリクエストを許可しています。",[13,10951,9007],{},[336,10953,10954,10957],{},[339,10955,10956],{},"ログインをしないと見れない画像やアセット",[339,10958,10959],{},"アクセスを制限したい画像やアセット",[13,10961,10962,10963,10965],{},"を実装したい時は単純に",[31,10964,10944],{},"配下に置くことはできません。この場合Laravelではコントローラーを使用して、アセットのリクエストに対して一度認証のロジックをかける必要があります。普段Laravelを使用していると、特定のURLとそのビューに対する認証はルートを定義するだけで簡単に設定できます。しかしビュー以外のアセットファイルの場合はWebサーバーとLaravelの仕組みを少し理解している必要ががります。今回はその様な保護したアセットルートの設定方法を解説しようと思います。",[20,10967,10968],{"id":10968},"概要",[13,10970,10971],{},"Laravelで構築されたURLで指定のビューやファイルをレスポンスとして返す時２通りの処理方法があります。",[5704,10973,10974,10977],{},[339,10975,10976],{},"ドキュメントルート配下にリクエストで示されたファイルがある場合、それを返す。（webサーバー）",[339,10978,10979],{},"ドキュメントルートにない場合、Route.phpで定義したルートを照らし合わせ、設定したビューやファイルを返す。（webサーバー+PHP）",[13,10981,10982],{},"「２」の方はよくわかると思います。例えば以下の様なルートを定義した時",[24,10984,10987],{"className":2394,"code":10985,"filename":10986,"language":882,"meta":33,"style":33},"Route::get('\u002Ftest', function () {\n    return view('welcome');\n});\n","route.php",[31,10988,10989,10994,10999],{"__ignoreMap":33},[46,10990,10991],{"class":48,"line":49},[46,10992,10993],{},"Route::get('\u002Ftest', function () {\n",[46,10995,10996],{"class":48,"line":70},[46,10997,10998],{},"    return view('welcome');\n",[46,11000,11001],{"class":48,"line":82},[46,11002,2966],{},[13,11004,11005,11008,11009,11012,11013,11015,11016,4777,11019,11021,11022,11025,11026,11029],{},[31,11006,11007],{},"https:\u002F\u002Fexample.com\u002Ftest","というURLにアクセスすると",[31,11010,11011],{},"view('welcome')","で定義したHTML（画面）がレスポンスとして表示されます。一方「１」の方はというと例えば",[31,11014,10944],{},"配下に置いた",[31,11017,11018],{},"css",[31,11020,10453],{},"など静的なファイルがあげられます。例えば",[31,11023,11024],{},"https:\u002F\u002Fexample.com\u002Fcss\u002Fstyle.css","の場合、webサーバーは",[31,11027,11028],{},"public\u002Fcss\u002Fstyle.css","があればそれをレスポンスとして返します。",[13,11031,11032],{},"両者の違いはwebサーバーだけで完結しているか、PHP（Laravel）も動かしているかです。これはpublic配下の.htaccessを見ると理解できます。",[24,11034,11038],{"className":11035,"code":11036,"filename":11037,"language":29,"meta":33},[27],"\u003CIfModule mod_rewrite.c>\n    # Send Requests To Front Controller...\n    RewriteCond %{REQUEST_FILENAME} !-d\n    RewriteCond %{REQUEST_FILENAME} !-f\n    RewriteRule ^ index.php [L]\n\u003C\u002FIfModule>\n",".htaccess",[31,11039,11036],{"__ignoreMap":33},[13,11041,11042],{},"一部省略していますが、重要なのはこの箇所です。これは「もし、リクエストしたディレクトリおよびファイルがない場合、index.phpを実行する」という意味です。つまりLaravelが置かれたwebサーバーでは、まず「リクエストされたファイルが静的に置かれているかをチェック」そしてもしない場合は「index.phpを実行してLaravelが動的にルートに対するレスポンスを作成する」ということが行われています。",[13,11044,11045],{},"Apache側で静的ファイルに対するアクセスを設定していない場合、基本的にリクエストに合致するファイルがある場合はレスポンスしてしまいます。今回の様な保護したファイル、つまりLaravelの認証などを通してファイルを返すためには、独自のルートを定義してレスポンスする必要があります。",[808,11047,11049,11050,11053,11054,11057,11058,11060,11061,11064,11065],{"className":11048},[811,812],"\nLaravelではアップロードされたファイルは",[31,11051,11052],{},"storage\u002Fapp\u002Fpublic"," 配下に配置し、そのstorageファイルへのリクエストは上記のwebサーバーのみで処理できます。storageディレクトリがpublicとは別なのになぜできるのか？それはシンボリックリンクを張っているからです。構築時に",[31,11055,11056],{},"php artisan storage:link","というおまじないを唱えたと思います。これはpublic配下に",[31,11059,11052],{},"に連絡する",[31,11062,11063],{},"storage","というシンボリックリンクを配置する処理を行っています。\n",[13,11066,11067,11068,11071,11072,11074],{},"実際にpublic配下にstorageというものがあり、Vscodeでは矢印マークが加わっているのが分かります。ls -lを実行してみると",[31,11069,11070],{},"storage -> \u002Fvar\u002Fwww\u002Fhtml\u002Fstorage\u002Fapp\u002Fpublic","という風に表示されます（私の環境の場合）。ディレクトリとして離れていても、シンボリックリンクを貼ることで",[31,11073,11052],{},"配下をwebサーバーが走査することができる様にしています。",[13,11076,11077],{},"今回のような保護したアセットファイルルートを設定するために",[5704,11079,11080,11083,11086,11089],{},[339,11081,11082],{},"専用のディレクトリを作成",[339,11084,11085],{},"読み取りルートの定義",[339,11087,11088],{},"ファイルの取得処理",[339,11090,11091],{},"レスポンス処理",[13,11093,11094,11095,11098],{},"上記のプログラムを作成します。今回は「ログインしたユーザーが見れるwebマニュアルの画像」ということなので、",[31,11096,11097],{},"resources","配下にファイルを置いておくことにします。一応後でstorageディレクトリに保護ファイルを配置・取得する方法も記述します。",[20,11100,11101],{"id":11101},"ディレクトリの作成",[13,11103,11104,11105,11108],{},"まずは専用のディレクトリを作成します。今回は静的に置いておくので",[31,11106,11107],{},"resources\u002Fprotected","という保護アセットファイルディレクトリを作っておきます。リクエストがあった場合はこのディレクトリからファイルを取得します。",[20,11110,11111],{"id":11111},"ルートを定義する",[13,11113,11114,11115,11118],{},"それではルートを定義します。",[31,11116,11117],{},"routes\u002Fweb.php","にて以下の様なルートを設定。",[24,11120,11122],{"className":2394,"code":11121,"filename":11117,"language":882,"meta":33,"style":33},"Route::group(['middleware' => 'auth'], function () {\n    Route::get('\u002Fprotected\u002F{path?}', function (Request $request,$path='') {\n        \u002F\u002F 後で書きます..\n    })->where('path', '.*');\n});\n",[31,11123,11124,11129,11134,11139,11144],{"__ignoreMap":33},[46,11125,11126],{"class":48,"line":49},[46,11127,11128],{},"Route::group(['middleware' => 'auth'], function () {\n",[46,11130,11131],{"class":48,"line":70},[46,11132,11133],{},"    Route::get('\u002Fprotected\u002F{path?}', function (Request $request,$path='') {\n",[46,11135,11136],{"class":48,"line":82},[46,11137,11138],{},"        \u002F\u002F 後で書きます..\n",[46,11140,11141],{"class":48,"line":91},[46,11142,11143],{},"    })->where('path', '.*');\n",[46,11145,11146],{"class":48,"line":102},[46,11147,2966],{},[13,11149,11150,11151,11154],{},"authミドルウェアでグルーピングをして",[31,11152,11153],{},"protected","配下のルートを保護します。",[13,11156,11157,11160,11161,4777,11164,11167,11168,11170,11171,1612,11174,4777,11177,11180],{},[31,11158,11159],{},"{path?}","は任意の記述を意味します。つまり",[31,11162,11163],{},"\u002Fprotected\u002Fsecret.jpg",[31,11165,11166],{},"\u002Fprotected\u002Fmanual\u002Fprivate.png","などのルートにをキャッチすることができます。そして",[31,11169,11159],{},"パラメータはコールバック（コントローラー）に第二引数として使用できます。先程の例のパスの場合、",[31,11172,11173],{},"$path",[31,11175,11176],{},"secret.jpg",[31,11178,11179],{},"manual\u002Fprivate.png","となります。この値は後でファイルの取得に使用します。ちなみに今回はルートに処理内容を記述しますが、プロジェクトによっては複雑な認証処理を実装する場合はコントローラーに記述しても大丈夫です。",[20,11182,11183],{"id":11183},"ファイルの取得とレスポンスを行う",[13,11185,11186],{},"ではファイルの取得の処理を記述します。",[24,11188,11190],{"className":2394,"code":11189,"filename":11117,"language":882,"meta":33,"style":33},"use Illuminate\\Support\\Facades\\File;\n\nRoute::group(['middleware' => 'auth'], function () {\n    Route::get('\u002Fprotected\u002F{path?}', function (Request $request,$path='') {\n        if($path==='') abort(404);\n\n        $rp = resource_path('protected\u002F'.$path);\n        if(File::exists($rp)){\n            return response()->file($rp);\n        }else{\n            abort(404);\n        }\n    })->where('path', '.*');\n});\n",[31,11191,11192,11196,11200,11204,11208,11213,11217,11222,11227,11232,11236,11241,11245,11249],{"__ignoreMap":33},[46,11193,11194],{"class":48,"line":49},[46,11195,9294],{},[46,11197,11198],{"class":48,"line":70},[46,11199,408],{"emptyLinePlaceholder":407},[46,11201,11202],{"class":48,"line":82},[46,11203,11128],{},[46,11205,11206],{"class":48,"line":91},[46,11207,11133],{},[46,11209,11210],{"class":48,"line":102},[46,11211,11212],{},"        if($path==='') abort(404);\n",[46,11214,11215],{"class":48,"line":112},[46,11216,408],{"emptyLinePlaceholder":407},[46,11218,11219],{"class":48,"line":121},[46,11220,11221],{},"        $rp = resource_path('protected\u002F'.$path);\n",[46,11223,11224],{"class":48,"line":131},[46,11225,11226],{},"        if(File::exists($rp)){\n",[46,11228,11229],{"class":48,"line":139},[46,11230,11231],{},"            return response()->file($rp);\n",[46,11233,11234],{"class":48,"line":147},[46,11235,9422],{},[46,11237,11238],{"class":48,"line":157},[46,11239,11240],{},"            abort(404);\n",[46,11242,11243],{"class":48,"line":171},[46,11244,3141],{},[46,11246,11247],{"class":48,"line":183},[46,11248,11143],{},[46,11250,11251],{"class":48,"line":195},[46,11252,2966],{},[13,11254,11255,11256,11258,11259,2762,11262,11264],{},"最初に",[31,11257,11173],{},"がない場合は404にアボートします。そして何かしらファイルが指定された場合は",[31,11260,11261],{},"resource_path('protected\u002F'.$path)",[31,11263,11107],{},"配下のファイルパスを取得します。",[808,11266,11269,11270,11273,11274,11277,11278,11281,11282,11285,11286,1612,11288,11291],{"className":11267},[811,11268],"alert-danger","\nwebサーバーでなくPHP（Laravel）にてユーザーからの入力値（リクエストパス）を用いてファイルの取得をする場合、PHPの",[31,11271,11272],{},"file_get_contents()","は使用せずLaravelの",[31,11275,11276],{},"resource_path()","や",[31,11279,11280],{},"storage_path","を使用し、さらにFileファサード、",[31,11283,11284],{},"file()","メソッドを使用しましょう。",[31,11287,11272],{},[31,11289,11290],{},"..\u002F","といった記述は文字列でなく、パスとして認識してしまい想定しないディレクトリのファイルにがブラウザを通じて取得される可能性があります。このような脆弱性をディレクトリトラバーサルといいます。Laravelのファイル取得系のメソッドはその辺は対策済みなので、基本的にはLaravelのメソッドを使用しましょう。\n",[13,11293,11294,11295,11298,11299,11302,11303,11306],{},"ファイルパスを作成したら",[31,11296,11297],{},"File::exists()"," を使用してファイルが存在するかをチェックします。存在しないファイルをfile()パスで使用すると",[31,11300,11301],{},"FileNotFoundException","が発生してしまいます。例外処理でやってもいいのですが、",[31,11304,11305],{},"response","メソッドを呼んでいるので念のためあらかじめチェックしておきます。",[13,11308,11309,11310,11313,11314,11316,11317,11319,11320,11323],{},"ファイルがある場合は ",[31,11311,11312],{},"response()->file();","を使用して対象の",[31,11315,11107],{},"配下のファイルをレスポンスとして返します。ない場合は404へアボートします。",[31,11318,11284],{},"メソッドを使用することで拡張子から",[31,11321,11322],{},"content-type","を設定してくれるそうでCSVだろうがHTMLでもMP4でも問題なくレスポンスしてくれます。",[13,11325,11326],{},"ルート自体はLaravelのミドルウェアを使用することでファイルを保護し、任意のファイルパスを使用して認証が通ればファイルを取得することができる様になります。",[20,11328,11329],{"id":11329},"storageでやる方法",[13,11331,11332],{},"resourcesは基本的に開発者が静的にファイルを置く場合に使用します。ユーザーが自由にアップロードして、保護しながら呼び出したい時はstorageディレクトリを使用します。ファイルの取得と保護は上記とほぼ同じですが、storageの場合は少しcconfigの設定を行います。",[13,11334,11335,11338],{},[31,11336,11337],{},"config\u002Ffilesystem.php","にて以下の様に保護storageディレクトリを定義します。",[24,11340,11342],{"className":2394,"code":11341,"filename":11337,"language":882,"meta":33,"style":33},"'protected' => [\n    'driver' => 'local',\n    'root' => storage_path('app\u002Fprotected'),\n    'url' => env('APP_URL') . '\u002Fstorage',\n    'visibility' => 'private',\n],\n\n",[31,11343,11344,11349,11354,11359,11364,11369],{"__ignoreMap":33},[46,11345,11346],{"class":48,"line":49},[46,11347,11348],{},"'protected' => [\n",[46,11350,11351],{"class":48,"line":70},[46,11352,11353],{},"    'driver' => 'local',\n",[46,11355,11356],{"class":48,"line":82},[46,11357,11358],{},"    'root' => storage_path('app\u002Fprotected'),\n",[46,11360,11361],{"class":48,"line":91},[46,11362,11363],{},"    'url' => env('APP_URL') . '\u002Fstorage',\n",[46,11365,11366],{"class":48,"line":102},[46,11367,11368],{},"    'visibility' => 'private',\n",[46,11370,11371],{"class":48,"line":112},[46,11372,10891],{},[13,11374,1777,11375,11378,11379,11381],{},[31,11376,11377],{},"Storage::disk('protected')->path()","を用いて対象ファイルパスを取得することができる様になります。ファイルストレージはローカルでなくS3など外部のものを使用することもあるので、この様に設定ファイルで定義しておくといいです。storageにprotectedディレクトリを作成した後、あとはルートを定義して",[31,11380,11377],{},"を用いてリクエストされたファイルパスを取得し、存在チェックをしてレスポンスで返せばOKです。",[13,11383,11384],{},"今回は簡単なauthミドルウェアですが、権限のロジックを組み込むことで所有者のリクエストのみに見せたり、特定の人のみに見せるといった芸当ができそうです。ただしwebサーバーの静的な配信でなく、ファイルの取得にPHPを動かすことになるので大量配信の場合はパフォーマンスはちょっと心配かもしません。",[1851,11386,8214],{},{"title":33,"searchDepth":82,"depth":82,"links":11388},[11389,11390,11391,11392,11393],{"id":10968,"depth":70,"text":10968},{"id":11101,"depth":70,"text":11101},{"id":11111,"depth":70,"text":11111},{"id":11183,"depth":70,"text":11183},{"id":11329,"depth":70,"text":11329},[1881],"2022-03-26","画像、アセットにログインしたユーザーのみリクエストを制限する",{},"\u002Farticles\u002Flaravel-protect-resource",{"title":10933,"description":11396},"articles\u002Flaravel-protect-resource",[1583,882],"f9NQ8_uhPD5HoW5tFBCWKT-Mw4ExKqCgzTqezzlXg_w",{"id":11404,"title":11405,"body":11406,"category":11890,"createdAt":11891,"description":11405,"extension":1883,"index":1884,"meta":11892,"navigation":407,"path":11893,"publish":407,"seo":11894,"series":1884,"seriesTitle":1884,"stem":11895,"tag":11896,"thumbnail":8833,"updatedAt":1884,"__hash__":11897},"articles\u002Farticles\u002Flaravel-validation-unit-test.md","Laravelでカスタムバリデーションのユニットテストをする方法",{"type":10,"value":11407,"toc":11883},[11408,11415,11418,11421,11424,11492,11495,11502,11671,11674,11677,11680,11683,11703,11712,11715,11718,11772,11775,11828,11849,11855,11858,11878,11881],[13,11409,11410,11411,11414],{},"Laravelでは",[31,11412,11413],{},"Illuminate\\Contracts\\Validation\\Rule","を継承したクラスを用いてカスタムなバリデーションを作成することができます。そしてサービスプロバイダに登録することで、FormRequestで文字列で指定することでリクエストのバリデーションを拡張できます。",[13,11416,11417],{},"ただしこの様な独自コードはきちんとユニットテストをすることが大切です。Laravelではこのカスタムバリデーションを簡単にユニットテストをすることができます。",[20,11419,11420],{"id":11420},"サンプルのバリデーション",[13,11422,11423],{},"とりあえず以下の様な郵便番号のバリデーションを作成したとします。７桁のハイフンなしの数字が郵便番号の形式とします。",[24,11425,11427],{"className":2394,"code":11426,"language":882,"meta":33,"style":33},"namespace App\\Rules;\nuse Illuminate\\Contracts\\Validation\\Rule;\n\nclass Zipcode implements Rule{\n\n    public function passes($attribute, $value)\n    {\n        return preg_match('\u002F^[0-9]{3}-?[0-9]{4}$\u002F', $value);\n    }\n\n    public function message(){\n        return '郵便番号は7桁の半角数字で入力してください。';\n    }\n}\n",[31,11428,11429,11434,11439,11443,11448,11452,11457,11461,11466,11470,11474,11479,11484,11488],{"__ignoreMap":33},[46,11430,11431],{"class":48,"line":49},[46,11432,11433],{},"namespace App\\Rules;\n",[46,11435,11436],{"class":48,"line":70},[46,11437,11438],{},"use Illuminate\\Contracts\\Validation\\Rule;\n",[46,11440,11441],{"class":48,"line":82},[46,11442,408],{"emptyLinePlaceholder":407},[46,11444,11445],{"class":48,"line":91},[46,11446,11447],{},"class Zipcode implements Rule{\n",[46,11449,11450],{"class":48,"line":102},[46,11451,408],{"emptyLinePlaceholder":407},[46,11453,11454],{"class":48,"line":112},[46,11455,11456],{},"    public function passes($attribute, $value)\n",[46,11458,11459],{"class":48,"line":121},[46,11460,2713],{},[46,11462,11463],{"class":48,"line":131},[46,11464,11465],{},"        return preg_match('\u002F^[0-9]{3}-?[0-9]{4}$\u002F', $value);\n",[46,11467,11468],{"class":48,"line":139},[46,11469,2723],{},[46,11471,11472],{"class":48,"line":147},[46,11473,408],{"emptyLinePlaceholder":407},[46,11475,11476],{"class":48,"line":157},[46,11477,11478],{},"    public function message(){\n",[46,11480,11481],{"class":48,"line":171},[46,11482,11483],{},"        return '郵便番号は7桁の半角数字で入力してください。';\n",[46,11485,11486],{"class":48,"line":183},[46,11487,2723],{},[46,11489,11490],{"class":48,"line":195},[46,11491,2752],{},[20,11493,11494],{"id":11494},"ユニットテストファイルを作成",[13,11496,11497,11498,11501],{},"ひとまず ",[31,11499,11500],{},"tests\u002FUnit\u002FRules.php"," というものを作成します。",[24,11503,11506],{"className":2394,"code":11504,"filename":11505,"language":882,"meta":33,"style":33},"namespace Tests\\Unit;\n\nuse App\\Rules\\Zipcode;\nuse Illuminate\\Foundation\\Testing\\TestCase;\nuse Tests\\CreatesApplication;\nuse Illuminate\\Support\\Facades\\Validator;\nuse \\Illuminate\\Validation\\ValidationException;\n\nclass Rules extends TestCase{\n    use CreatesApplication;\n\n    public function test_zipcode_validation(){\n        $tests = [\n            '1234567'=>true,\n            '0012344'=>true,\n            '0012340'=>true,\n            '12345678'=>false,\n            '123456'=>false,\n            '1234 56'=>false,\n            '1234a56'=>false,\n            '1234_56'=>false,\n        ];\n        foreach($tests as $key => $condition){\n            try{\n                Validator::make(['test'=>$key],[\n                    'test'=> new Zipcode()\n                ])->validate();\n                $this->assertTrue($condition===true);\n            }catch(ValidationException $e){\n                $this->assertFalse($condition);\n            }\n        }\n    }\n}\n","Rules.php",[31,11507,11508,11513,11517,11522,11527,11532,11537,11542,11546,11551,11556,11560,11565,11570,11575,11580,11585,11590,11595,11600,11605,11610,11615,11620,11625,11630,11635,11640,11645,11650,11655,11659,11663,11667],{"__ignoreMap":33},[46,11509,11510],{"class":48,"line":49},[46,11511,11512],{},"namespace Tests\\Unit;\n",[46,11514,11515],{"class":48,"line":70},[46,11516,408],{"emptyLinePlaceholder":407},[46,11518,11519],{"class":48,"line":82},[46,11520,11521],{},"use App\\Rules\\Zipcode;\n",[46,11523,11524],{"class":48,"line":91},[46,11525,11526],{},"use Illuminate\\Foundation\\Testing\\TestCase;\n",[46,11528,11529],{"class":48,"line":102},[46,11530,11531],{},"use Tests\\CreatesApplication;\n",[46,11533,11534],{"class":48,"line":112},[46,11535,11536],{},"use Illuminate\\Support\\Facades\\Validator;\n",[46,11538,11539],{"class":48,"line":121},[46,11540,11541],{},"use \\Illuminate\\Validation\\ValidationException;\n",[46,11543,11544],{"class":48,"line":131},[46,11545,408],{"emptyLinePlaceholder":407},[46,11547,11548],{"class":48,"line":139},[46,11549,11550],{},"class Rules extends TestCase{\n",[46,11552,11553],{"class":48,"line":147},[46,11554,11555],{},"    use CreatesApplication;\n",[46,11557,11558],{"class":48,"line":157},[46,11559,408],{"emptyLinePlaceholder":407},[46,11561,11562],{"class":48,"line":171},[46,11563,11564],{},"    public function test_zipcode_validation(){\n",[46,11566,11567],{"class":48,"line":183},[46,11568,11569],{},"        $tests = [\n",[46,11571,11572],{"class":48,"line":195},[46,11573,11574],{},"            '1234567'=>true,\n",[46,11576,11577],{"class":48,"line":206},[46,11578,11579],{},"            '0012344'=>true,\n",[46,11581,11582],{"class":48,"line":4},[46,11583,11584],{},"            '0012340'=>true,\n",[46,11586,11587],{"class":48,"line":223},[46,11588,11589],{},"            '12345678'=>false,\n",[46,11591,11592],{"class":48,"line":231},[46,11593,11594],{},"            '123456'=>false,\n",[46,11596,11597],{"class":48,"line":242},[46,11598,11599],{},"            '1234 56'=>false,\n",[46,11601,11602],{"class":48,"line":253},[46,11603,11604],{},"            '1234a56'=>false,\n",[46,11606,11607],{"class":48,"line":264},[46,11608,11609],{},"            '1234_56'=>false,\n",[46,11611,11612],{"class":48,"line":275},[46,11613,11614],{},"        ];\n",[46,11616,11617],{"class":48,"line":284},[46,11618,11619],{},"        foreach($tests as $key => $condition){\n",[46,11621,11622],{"class":48,"line":296},[46,11623,11624],{},"            try{\n",[46,11626,11627],{"class":48,"line":305},[46,11628,11629],{},"                Validator::make(['test'=>$key],[\n",[46,11631,11632],{"class":48,"line":313},[46,11633,11634],{},"                    'test'=> new Zipcode()\n",[46,11636,11637],{"class":48,"line":323},[46,11638,11639],{},"                ])->validate();\n",[46,11641,11642],{"class":48,"line":529},[46,11643,11644],{},"                $this->assertTrue($condition===true);\n",[46,11646,11647],{"class":48,"line":535},[46,11648,11649],{},"            }catch(ValidationException $e){\n",[46,11651,11652],{"class":48,"line":540},[46,11653,11654],{},"                $this->assertFalse($condition);\n",[46,11656,11657],{"class":48,"line":546},[46,11658,5380],{},[46,11660,11661],{"class":48,"line":551},[46,11662,3141],{},[46,11664,11665],{"class":48,"line":557},[46,11666,2723],{},[46,11668,11669],{"class":48,"line":562},[46,11670,2752],{},[13,11672,11673],{},"バリデーションテストでは正しい形式は正しいと判断（ポジティブテスト）し、間違っているものは間違っていると判断（ネガティブテスト）できているかをテストします。\nもしも正しいのに間違っていると判断したり、間違っているのに正しいとなったらテストが失敗する様になっています。",[13,11675,11676],{},"ここでバリデーションテストの詳細を解説します。",[352,11678,11679],{"id":11679},"バリデーションのインスタンスを作成",[13,11681,11682],{},"最初にテストしたいバリデーションのインスタンス、そしてバリデーターインスタンスを作成します。",[24,11684,11686],{"className":2394,"code":11685,"language":882,"meta":33,"style":33},"Validator::make(['test'=>$key],[\n    'test'=> new Zipcode()\n])->validate();\n",[31,11687,11688,11693,11698],{"__ignoreMap":33},[46,11689,11690],{"class":48,"line":49},[46,11691,11692],{},"Validator::make(['test'=>$key],[\n",[46,11694,11695],{"class":48,"line":70},[46,11696,11697],{},"    'test'=> new Zipcode()\n",[46,11699,11700],{"class":48,"line":82},[46,11701,11702],{},"])->validate();\n",[13,11704,11705,8605,11708,11711],{},[31,11706,11707],{},"Validator",[31,11709,11710],{},"make()","を使用して第一引数に、バリデーションをする値とキーを設定します。第二引数にはバリデーションのキーとバリデーションインスタンスを指定します。",[352,11713,11714],{"id":11714},"いろんなパターンをテストする",[13,11716,11717],{},"いろいろなパターンをテストするため以下の様に配列でテストパターンと予期する正誤を設定します。Trueはバリデーション通過で、Falseはバリデーション違反（間違った形式）であることを示します。",[24,11719,11721],{"className":2394,"code":11720,"language":882,"meta":33,"style":33},"$tests = [\n    '1234567'=>true,\n    '0012344'=>true,\n    '0012340'=>true,\n    '12345678'=>false,\n    '123456'=>false,\n    '1234 56'=>false,\n    '1234a56'=>false,\n    '1234_56'=>false,\n];\n",[31,11722,11723,11728,11733,11738,11743,11748,11753,11758,11763,11768],{"__ignoreMap":33},[46,11724,11725],{"class":48,"line":49},[46,11726,11727],{},"$tests = [\n",[46,11729,11730],{"class":48,"line":70},[46,11731,11732],{},"    '1234567'=>true,\n",[46,11734,11735],{"class":48,"line":82},[46,11736,11737],{},"    '0012344'=>true,\n",[46,11739,11740],{"class":48,"line":91},[46,11741,11742],{},"    '0012340'=>true,\n",[46,11744,11745],{"class":48,"line":102},[46,11746,11747],{},"    '12345678'=>false,\n",[46,11749,11750],{"class":48,"line":112},[46,11751,11752],{},"    '123456'=>false,\n",[46,11754,11755],{"class":48,"line":121},[46,11756,11757],{},"    '1234 56'=>false,\n",[46,11759,11760],{"class":48,"line":131},[46,11761,11762],{},"    '1234a56'=>false,\n",[46,11764,11765],{"class":48,"line":139},[46,11766,11767],{},"    '1234_56'=>false,\n",[46,11769,11770],{"class":48,"line":147},[46,11771,3493],{},[13,11773,11774],{},"そしてforeachでそれぞれチェックします。",[24,11776,11778],{"className":2394,"code":11777,"language":882,"meta":33,"style":33},"foreach($tests as $key => $condition){\n    try{\n        Validator::make(['test'=>$key],[\n            'test'=> new Zipcode()\n        ])->validate();\n        $this->assertTrue($condition===true);\n    }catch(ValidationException $e){\n        $this->assertFalse($condition);\n    }\n}\n",[31,11779,11780,11785,11790,11795,11800,11805,11810,11815,11820,11824],{"__ignoreMap":33},[46,11781,11782],{"class":48,"line":49},[46,11783,11784],{},"foreach($tests as $key => $condition){\n",[46,11786,11787],{"class":48,"line":70},[46,11788,11789],{},"    try{\n",[46,11791,11792],{"class":48,"line":82},[46,11793,11794],{},"        Validator::make(['test'=>$key],[\n",[46,11796,11797],{"class":48,"line":91},[46,11798,11799],{},"            'test'=> new Zipcode()\n",[46,11801,11802],{"class":48,"line":102},[46,11803,11804],{},"        ])->validate();\n",[46,11806,11807],{"class":48,"line":112},[46,11808,11809],{},"        $this->assertTrue($condition===true);\n",[46,11811,11812],{"class":48,"line":121},[46,11813,11814],{},"    }catch(ValidationException $e){\n",[46,11816,11817],{"class":48,"line":131},[46,11818,11819],{},"        $this->assertFalse($condition);\n",[46,11821,11822],{"class":48,"line":139},[46,11823,2723],{},[46,11825,11826],{"class":48,"line":147},[46,11827,2752],{},[13,11829,11830,11833,11834,11837,11838,11840,11841,11844,11845,11848],{},[31,11831,11832],{},"validate()","メソッドは失敗すると",[31,11835,11836],{},"ValidationException"," を投げます。そのため例外処理で",[31,11839,11836],{}," をキャッチします。正しい形式を正しいと判断できれば",[31,11842,11843],{},"$this->assertTrue($condition===true);","となり、まちがった形を間違っていると判断できればキャッチして",[31,11846,11847],{},"$this->assertFalse($condition);","でアサートされます。",[24,11850,11853],{"className":11851,"code":11852,"language":29},[27],"php artisan test \n",[31,11854,11852],{"__ignoreMap":33},[13,11856,11857],{},"にてテストを行い問題なければそのまま通過します。テストは以上の方法でいろんなパターンをテストできます。パターンは思いついたものを配列に書いてもいいですし、プログラム的に大量に配列を生成してもいいかもしれません。まとめると",[336,11859,11860,11863,11868],{},[339,11861,11862],{},"バリデーションインスタンスを作成",[339,11864,11865,11867],{},[31,11866,11836],{},"を用いて間違っている形を識別",[339,11869,11870,11871,1440,11874,11877],{},"バリデーション対象の値が正誤かどうかは",[31,11872,11873],{},"assertTrue",[31,11875,11876],{},"assertFalse","で正しく判断できているかをアサート",[13,11879,11880],{},"こんな感じです。私はこのテストで結構救われたので、バリデーションを作った時は必ずユニットテストをしましょう。",[1851,11882,8214],{},{"title":33,"searchDepth":82,"depth":82,"links":11884},[11885,11886],{"id":11420,"depth":70,"text":11420},{"id":11494,"depth":70,"text":11494,"children":11887},[11888,11889],{"id":11679,"depth":82,"text":11679},{"id":11714,"depth":82,"text":11714},[8675],"2022-03-25",{},"\u002Farticles\u002Flaravel-validation-unit-test",{"title":11405,"description":11405},"articles\u002Flaravel-validation-unit-test",[882,1583],"_6kCaD6uDnBdQkj-XY_8G39zixCeVekVE-xS127shV4",{"id":11899,"title":11900,"body":11901,"category":12198,"createdAt":12199,"description":12200,"extension":1883,"index":1884,"meta":12201,"navigation":407,"path":12202,"publish":407,"seo":12203,"series":1884,"seriesTitle":1884,"stem":12204,"tag":12205,"thumbnail":1884,"updatedAt":1884,"__hash__":12207},"articles\u002Farticles\u002Flaravel-plain-text-with-html.md","LaravelのMailableでHTMLメールとプレーンテキストメール両方を送信する方法",{"type":10,"value":11902,"toc":12188},[11903,11906,11910,11913,11920,11924,11927,11936,11940,11947,11957,11963,11966,11970,11973,12044,12054,12062,12065,12068,12074,12089,12109,12128,12131,12138,12186],[13,11904,11905],{},"こんにちはjunです。Laravelでメール機能が伴う内容を実装していたときに、HTMLメールだけでなくプレーンテキストメールでも送付してほしいとの用件がありました。Laravelではメールを送信する時は大抵、Mailableを使用しますがその時に両方送る方法が意外と日本語でなかったので忘備録として記事を作りました。プレーンテキストとはなんぞや？というとこから解説するので、対策法をさっさと知りたい方は「Mailabeでのプレーンテキストの設定」を見てください。",[20,11907,11909],{"id":11908},"プレーンテキストメールとhtmlメールの違い","プレーンテキストメールとHTMLメールの違い",[13,11911,11912],{},"HTMLメールは名の通り、HTMLの記法で作成されたメールです。生のデータにはHTMLが書かれており、メールクライアント側でHTMLをレンダリングしてメール内容を表示します。リッチなメールを送付できるというメリットがあります。デメリットとして環境やデバイスによってはメールが全く見れなくなることです。",[13,11914,11915,11916,11919],{},"HTMLメールが見れない環境や昔はプレーンテキストメールといった、メモ帳で書いた様な本当に純粋な文字だけのメールを利用します。メリットはどの端末でも必ず表示はできるので、確実に届けたいメールなどにはプレーンテキストがおすすめです。例えばGithubの二段階認証メールは",[31,11917,11918],{},"Content-Type: text\u002Fplain; charset=UTF-8","とプレーンテキストで必ず送られ、毎週のお知らせメールはその両方が送られています。",[352,11921,11923],{"id":11922},"どうやって確認できるの","どうやって確認できるの？",[13,11925,11926],{},"気になる方は届いたメールのソースを見てみましょう。Gmailであれば「画面右側の点々」をクリックして「メッセージのソースを表示」をクリックしますと、メールのヘッダやボディを確認できます。そのとき",[13,11928,11929,11931,11932,11935],{},[31,11930,11918],{},"があれば、プレーンテキストメール形式で送付され、",[31,11933,11934],{},"Content-Type: text\u002Fhtml; charset=UTF-8","があればHTMLメールです。きちんとHTMLの記述があるのを確認してみてください。",[352,11937,11939],{"id":11938},"なぜ両方ともつけることができるの","なぜ両方ともつけることができるの？",[13,11941,11942,11943,11946],{},"ちなみにメールにはHTMLとプレーン両方ともつけることは可能です。その場合環境に合わせてHTML・プレーンのものが表示されます。その仕組みはソースの中に ",[31,11944,11945],{},"Content-Type: multipart\u002Falternative; boundary=","のような記述をしようすることです。これはメールの内容を複数の形式で送りますよというヘッダ要素です。HTMLの内容とプレーンテキストの内容がソースでどこで分けているかを示しています。",[13,11948,11949,11952,11953,11956],{},[31,11950,11951],{}," boundary=\"...\""," のboundaryの中身にある文字を境界として使用します。Laravelの場合はSwiftを使用しているので ",[31,11954,11955],{},"_=_swift_1646631221_d7511bd8d439c84878b3339aaec563e1_=_","という文字（一部分はランダムです）が境界として使用され、",[24,11958,11961],{"className":11959,"code":11960,"language":29},[27],"Content-Type: multipart\u002Falternative; boundary=\"_=_swift_1646631221_d7511bd8d439c84878b3339aaec563e1_=_\"\n\n--_=_swift_1646631221_d7511bd8d439c84878b3339aaec563e1_=_\nContent-Type: text\u002Fplain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\n（１）HTMLの記述’\n\n--_=_swift_1646631221_d7511bd8d439c84878b3339aaec563e1_=_\nContent-Type: text\u002Fhtml; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\n（２）プレーンテキストの記述\n\n--_=_swift_1646631221_d7511bd8d439c84878b3339aaec563e1_=_\n",[31,11962,11960],{"__ignoreMap":33},[13,11964,11965],{},"上記の様な記述があると思います。ここで境界の文字を用いて２つの形式の内容を記述し、メールを送付してクライアントは適宜そのソースを汲み取って表示しています。",[20,11967,11969],{"id":11968},"mailabeでのプレーンテキストの設定","Mailabeでのプレーンテキストの設定",[13,11971,11972],{},"前置きがながくなりましたが、LaravelのMailableでは以下の様に指定します。",[24,11974,11976],{"className":2394,"code":11975,"language":882,"meta":33,"style":33},"\u002F**\n    * Build the message.\n    *\n    * @return $this\n*\u002F\npublic function build()\n{\n    return $this->view('mail.registration',[\n        'applay_user_name'=>$name,\n    ])->text('mail.registration_text',[ \u002F\u002F これ！\n        'applay_user_name'=>$name,\n    ])\n    ->subject('登録を受け付けました。');\n}\n",[31,11977,11978,11983,11988,11993,11998,12002,12007,12011,12016,12021,12026,12030,12035,12040],{"__ignoreMap":33},[46,11979,11980],{"class":48,"line":49},[46,11981,11982],{},"\u002F**\n",[46,11984,11985],{"class":48,"line":70},[46,11986,11987],{},"    * Build the message.\n",[46,11989,11990],{"class":48,"line":82},[46,11991,11992],{},"    *\n",[46,11994,11995],{"class":48,"line":91},[46,11996,11997],{},"    * @return $this\n",[46,11999,12000],{"class":48,"line":102},[46,12001,2444],{},[46,12003,12004],{"class":48,"line":112},[46,12005,12006],{},"public function build()\n",[46,12008,12009],{"class":48,"line":121},[46,12010,2572],{},[46,12012,12013],{"class":48,"line":131},[46,12014,12015],{},"    return $this->view('mail.registration',[\n",[46,12017,12018],{"class":48,"line":139},[46,12019,12020],{},"        'applay_user_name'=>$name,\n",[46,12022,12023],{"class":48,"line":147},[46,12024,12025],{},"    ])->text('mail.registration_text',[ \u002F\u002F これ！\n",[46,12027,12028],{"class":48,"line":157},[46,12029,12020],{},[46,12031,12032],{"class":48,"line":171},[46,12033,12034],{},"    ])\n",[46,12036,12037],{"class":48,"line":183},[46,12038,12039],{},"    ->subject('登録を受け付けました。');\n",[46,12041,12042],{"class":48,"line":195},[46,12043,2752],{},[13,12045,12046,12049,12050,12053],{},[31,12047,12048],{},"text()","メソッドを使用することで前述の解説の様にプレーンテキスト用のmultipartを入れ込んでくれます。なお、引数は",[31,12051,12052],{},"view()","メソッドと同じでテンプレートファイルとデータを渡すことができます。この記法はLaravel5.3から利用できます。",[13,12055,12056,12057],{},"参考\n",[725,12058,12061],{"href":12059,"rel":12060},"https:\u002F\u002Flaravel.com\u002Fdocs\u002F9.x\u002Fmail#plain-text-emails",[729],"Laravel9 Mail",[352,12063,12064],{"id":12064},"テンプレートの記述とファイル構成のすすめ",[13,12066,12067],{},"私の場合は以下の様な構成とファイル名でメールテンプレートを管理しています。",[24,12069,12072],{"className":12070,"code":12071,"language":29},[27],"views\n│\n├── mail\n    ├── master.blade.php\n    ├── master_text.blade.php\n    ├── register.blade.php\n    └── register_text.blade.php\n",[31,12073,12071],{"__ignoreMap":33},[13,12075,12076,12077,12080,12081,12084,12085,12088],{},"まずviewsでmail用のテンプレートを格納するディレクトリ を作成し、そしてHプレーンテキスト用のテンプレートはTML用のものに",[31,12078,12079],{},"_text","をつけておきます。そして",[31,12082,12083],{},"master.blade.php","はレイアウトやHTMLのスタイルを定義しています。同じようにプレーン用のレイアウトファイルの",[31,12086,12087],{},"master_text.blade.php","を用意しておくといいです。",[24,12090,12093],{"className":2394,"code":12091,"filename":12092,"language":882,"meta":33,"style":33},"{{$name}}様\n\u003Cp>いつもご利用いただきありがとうございます。\u003C\u002Fp>\n...\n","register.blade.php",[31,12094,12095,12100,12105],{"__ignoreMap":33},[46,12096,12097],{"class":48,"line":49},[46,12098,12099],{},"{{$name}}様\n",[46,12101,12102],{"class":48,"line":70},[46,12103,12104],{},"\u003Cp>いつもご利用いただきありがとうございます。\u003C\u002Fp>\n",[46,12106,12107],{"class":48,"line":82},[46,12108,2851],{},[24,12110,12113],{"className":2394,"code":12111,"filename":12112,"language":882,"meta":33,"style":33},"{{$name}}様\nいつもご利用いただきありがとうございます。\n...\n","register_text.blade.php",[31,12114,12115,12119,12124],{"__ignoreMap":33},[46,12116,12117],{"class":48,"line":49},[46,12118,12099],{},[46,12120,12121],{"class":48,"line":70},[46,12122,12123],{},"いつもご利用いただきありがとうございます。\n",[46,12125,12126],{"class":48,"line":82},[46,12127,2851],{},[20,12129,12130],{"id":12130},"notifiableの場合",[13,12132,12133,12134,12137],{},"これはLaravel8しか確認していませんが、Notifiableで使用されるMailMessageの場合はプレーンテキストが自動的に作成されていました。以下の様に",[31,12135,12136],{},"toMail()","を作成しておけばHTMLもプレーンも送付されていました。",[24,12139,12141],{"className":2394,"code":12140,"language":882,"meta":33,"style":33},"public function toMail($notifiable)\n{\n    return (new MailMessage)\n                ->subject('メールアドレスが変更されました')\n                ->line(\"いつもご利用いただきありがとうございます。\")\n                ->line('連絡用メールアドレスの変更を受け付けました。')\n                ->line('※本メールは送信専用です。ご返信いただいても対応できませんのでご了承ください。')\n                ->line('※本メールにお心当たりがない場合は、恐れ入りますが破棄していただきますようお願いいたします。');\n}\n",[31,12142,12143,12148,12152,12157,12162,12167,12172,12177,12182],{"__ignoreMap":33},[46,12144,12145],{"class":48,"line":49},[46,12146,12147],{},"public function toMail($notifiable)\n",[46,12149,12150],{"class":48,"line":70},[46,12151,2572],{},[46,12153,12154],{"class":48,"line":82},[46,12155,12156],{},"    return (new MailMessage)\n",[46,12158,12159],{"class":48,"line":91},[46,12160,12161],{},"                ->subject('メールアドレスが変更されました')\n",[46,12163,12164],{"class":48,"line":102},[46,12165,12166],{},"                ->line(\"いつもご利用いただきありがとうございます。\")\n",[46,12168,12169],{"class":48,"line":112},[46,12170,12171],{},"                ->line('連絡用メールアドレスの変更を受け付けました。')\n",[46,12173,12174],{"class":48,"line":121},[46,12175,12176],{},"                ->line('※本メールは送信専用です。ご返信いただいても対応できませんのでご了承ください。')\n",[46,12178,12179],{"class":48,"line":131},[46,12180,12181],{},"                ->line('※本メールにお心当たりがない場合は、恐れ入りますが破棄していただきますようお願いいたします。');\n",[46,12183,12184],{"class":48,"line":139},[46,12185,2752],{},[1851,12187,8214],{},{"title":33,"searchDepth":82,"depth":82,"links":12189},[12190,12194,12197],{"id":11908,"depth":70,"text":11909,"children":12191},[12192,12193],{"id":11922,"depth":82,"text":11923},{"id":11938,"depth":82,"text":11939},{"id":11968,"depth":70,"text":11969,"children":12195},[12196],{"id":12064,"depth":82,"text":12064},{"id":12130,"depth":70,"text":12130},[8675],"2022-03-07","LaravelのMilableでHTMLメールとプレーンテキストメール両方を送信する方法",{},"\u002Farticles\u002Flaravel-plain-text-with-html",{"title":11900,"description":12200},"articles\u002Flaravel-plain-text-with-html",[1583,882,12206],"network","Ru_0w8TnRhwaXPjcr0gSRvxAArbPnEm7l-VCgPS-v6w",1780987134294]