[{"data":1,"prerenderedAt":4592},["ShallowReactive",2],{"category-ministack":3},{"count":4,"content":5},29,[6,836,1255,1449,1563,1697,1848,3529,4010,4506],{"id":7,"title":8,"body":9,"category":820,"createdAt":822,"description":823,"extension":824,"index":825,"meta":826,"navigation":94,"path":827,"publish":94,"seo":828,"series":825,"seriesTitle":825,"stem":829,"tag":830,"thumbnail":834,"updatedAt":822,"__hash__":835},"articles\u002Farticles\u002Faws-sam-disconnect-dymamodb-local.md","AWS SAMで作ったLamdaコードがローカルのDynamoDBに接続できないときの対処",{"type":10,"value":11,"toc":813},"minimark",[12,16,19,30,33,44,47,50,55,58,223,234,573,576,582,585,592,595,598,603,611,626,629,633,636,754,757,763,769,803,806,809],[13,14,15],"p",{},"こんにちはjunです。最近はこの静的ブログに動的な機能を追加するために、サーバーレス環境でのサービス開発を行っています。API gateway をトリガーとしてAWS Lambdaでコードを実行してDynamoDBで内容を保存するといったよくある構成で開発をしていました。",[13,17,18],{},"AWS Lambdaはweb上でもコードを書いて実行できますが、できたらローカル上でコードを書いてテストしてからデプロイしたいです。特に更新や改修の時はローカルで検証できることが好ましいです。",[13,20,21,22,29],{},"AWSはその辺も考えており、",[23,24,28],"a",{"href":25,"rel":26},"https:\u002F\u002Fdocs.aws.amazon.com\u002Fja_jp\u002Fserverless-application-model\u002Flatest\u002Fdeveloperguide\u002Fwhat-is-sam.html",[27],"nofollow","AWS SAM(serverless application model)"," を用意してくれています。私も開発ではこのSAMを使用してローカルで構築後、テストして本番に適用しています。",[13,31,32],{},"ひとまずチュートリアルのデプロイができたので、DynamoDBを用いた環境をローカルに作ってコードをテストすることにしました。しかし、DynamoDBをscanする処理でいつもストップし、",[34,35,40],"pre",{"className":36,"code":38,"language":39},[37],"language-text","Inaccessible host: `127.0.0.1' at port `8000'. This service may not be available in the `ap-north-east1' region.\n","text",[41,42,38],"code",{"__ignoreMap":43},"",[13,45,46],{},"というメッセージでDynamoDBに接続できませんでした。ちなみにDynamoDBの環境はDockerを用いてローカルで構築しており、localost:8000に待機していました。DynamoDBのコンテナは起動しているのに、どうして接続できないのか悩んでいました。",[13,48,49],{},"結論を言うとDockerのネットワークの設定が間違っていたからです。詳細を解説します。",[51,52,54],"h2",{"id":53},"lambdaのコードとdynamodbのコンテナ設定","LambdaのコードとDynamoDBのコンテナ設定",[13,56,57],{},"DynamoDBはこちらのDocker imageを使用して、以下の設定を用いていました。（参考サイトからのコピペです...)",[34,59,64],{"className":60,"code":61,"filename":62,"language":63,"meta":43,"style":43},"language-yaml shiki shiki-themes material-theme-ocean","version: \"3\"\n\nservices:\n  dynamodb-local:\n    container_name: dynamodb-local\n    image: amazon\u002Fdynamodb-local\n    ports:\n      - 8000:8000\n    command: -jar DynamoDBLocal.jar -dbPath \u002Fdata -sharedDb\n    volumes:\n      - .\u002Fdata:\u002Fdata\n    networks:\n      - lambda-local\nnetworks:\n  lambda-local:\n    external: true\n","docker-compose.yml","yaml",[41,65,66,89,96,105,113,124,135,143,152,163,171,179,187,195,203,211],{"__ignoreMap":43},[67,68,71,75,79,82,86],"span",{"class":69,"line":70},"line",1,[67,72,74],{"class":73},"s-wAU","version",[67,76,78],{"class":77},"sAklC",":",[67,80,81],{"class":77}," \"",[67,83,85],{"class":84},"sfyAc","3",[67,87,88],{"class":77},"\"\n",[67,90,92],{"class":69,"line":91},2,[67,93,95],{"emptyLinePlaceholder":94},true,"\n",[67,97,99,102],{"class":69,"line":98},3,[67,100,101],{"class":73},"services",[67,103,104],{"class":77},":\n",[67,106,108,111],{"class":69,"line":107},4,[67,109,110],{"class":73},"  dynamodb-local",[67,112,104],{"class":77},[67,114,116,119,121],{"class":69,"line":115},5,[67,117,118],{"class":73},"    container_name",[67,120,78],{"class":77},[67,122,123],{"class":84}," dynamodb-local\n",[67,125,127,130,132],{"class":69,"line":126},6,[67,128,129],{"class":73},"    image",[67,131,78],{"class":77},[67,133,134],{"class":84}," amazon\u002Fdynamodb-local\n",[67,136,138,141],{"class":69,"line":137},7,[67,139,140],{"class":73},"    ports",[67,142,104],{"class":77},[67,144,146,149],{"class":69,"line":145},8,[67,147,148],{"class":77},"      -",[67,150,151],{"class":84}," 8000:8000\n",[67,153,155,158,160],{"class":69,"line":154},9,[67,156,157],{"class":73},"    command",[67,159,78],{"class":77},[67,161,162],{"class":84}," -jar DynamoDBLocal.jar -dbPath \u002Fdata -sharedDb\n",[67,164,166,169],{"class":69,"line":165},10,[67,167,168],{"class":73},"    volumes",[67,170,104],{"class":77},[67,172,174,176],{"class":69,"line":173},11,[67,175,148],{"class":77},[67,177,178],{"class":84}," .\u002Fdata:\u002Fdata\n",[67,180,182,185],{"class":69,"line":181},12,[67,183,184],{"class":73},"    networks",[67,186,104],{"class":77},[67,188,190,192],{"class":69,"line":189},13,[67,191,148],{"class":77},[67,193,194],{"class":84}," lambda-local\n",[67,196,198,201],{"class":69,"line":197},14,[67,199,200],{"class":73},"networks",[67,202,104],{"class":77},[67,204,206,209],{"class":69,"line":205},15,[67,207,208],{"class":73},"  lambda-local",[67,210,104],{"class":77},[67,212,214,217,219],{"class":69,"line":213},16,[67,215,216],{"class":73},"    external",[67,218,78],{"class":77},[67,220,222],{"class":221},"sbqyR"," true\n",[13,224,225,226,229,230,233],{},"上記の",[41,227,228],{},"- 8000:8000"," の通りlocalhostの8000にDynamoDBのエンドポイントが待機しています。\n",[41,231,232],{},"docker-compose up","してこの記述の通り、Lambdaのコードで以下のように記述しました。",[34,235,240],{"className":236,"code":237,"filename":238,"language":239,"meta":43,"style":43},"language-js shiki shiki-themes material-theme-ocean","const AWS = require('aws-sdk')\nlet response;\n\nvar dynamoOpt = {\n    endpoint: \"http:\u002F\u002Flocalhost:8000\",\n};\n\nexports.lambdaHandler = async (event, context) => {\n    let params = {\n            TableName:'table',\n        };\n\n        try{\n            const result = await dynamodb.scan(params).promise()\n            return {\n                statusCode: 200,\n                body: JSON.stringify(result.Items),\n            };\n        }catch (e){\n            return {\n                statusCode: 500,\n                body: e.message,\n            };\n        }\n}\n","app.js","js",[41,241,242,273,284,288,301,319,324,328,363,375,391,396,400,409,446,453,466,496,502,520,527,539,556,561,567],{"__ignoreMap":43},[67,243,244,248,252,255,259,262,265,268,270],{"class":69,"line":70},[67,245,247],{"class":246},"sJ14y","const",[67,249,251],{"class":250},"s0W1g"," AWS ",[67,253,254],{"class":77},"=",[67,256,258],{"class":257},"sdLwU"," require",[67,260,261],{"class":250},"(",[67,263,264],{"class":77},"'",[67,266,267],{"class":84},"aws-sdk",[67,269,264],{"class":77},[67,271,272],{"class":250},")\n",[67,274,275,278,281],{"class":69,"line":91},[67,276,277],{"class":246},"let",[67,279,280],{"class":250}," response",[67,282,283],{"class":77},";\n",[67,285,286],{"class":69,"line":98},[67,287,95],{"emptyLinePlaceholder":94},[67,289,290,293,296,298],{"class":69,"line":107},[67,291,292],{"class":246},"var",[67,294,295],{"class":250}," dynamoOpt ",[67,297,254],{"class":77},[67,299,300],{"class":77}," {\n",[67,302,303,306,308,310,313,316],{"class":69,"line":115},[67,304,305],{"class":73},"    endpoint",[67,307,78],{"class":77},[67,309,81],{"class":77},[67,311,312],{"class":84},"http:\u002F\u002Flocalhost:8000",[67,314,315],{"class":77},"\"",[67,317,318],{"class":77},",\n",[67,320,321],{"class":69,"line":126},[67,322,323],{"class":77},"};\n",[67,325,326],{"class":69,"line":137},[67,327,95],{"emptyLinePlaceholder":94},[67,329,330,333,336,339,342,345,349,352,355,358,361],{"class":69,"line":145},[67,331,332],{"class":77},"exports.",[67,334,335],{"class":257},"lambdaHandler",[67,337,338],{"class":77}," =",[67,340,341],{"class":246}," async",[67,343,344],{"class":77}," (",[67,346,348],{"class":347},"s7ZW3","event",[67,350,351],{"class":77},",",[67,353,354],{"class":347}," context",[67,356,357],{"class":77},")",[67,359,360],{"class":246}," =>",[67,362,300],{"class":77},[67,364,365,368,371,373],{"class":69,"line":154},[67,366,367],{"class":246},"    let",[67,369,370],{"class":250}," params",[67,372,338],{"class":77},[67,374,300],{"class":77},[67,376,377,380,382,384,387,389],{"class":69,"line":165},[67,378,379],{"class":73},"            TableName",[67,381,78],{"class":77},[67,383,264],{"class":77},[67,385,386],{"class":84},"table",[67,388,264],{"class":77},[67,390,318],{"class":77},[67,392,393],{"class":69,"line":173},[67,394,395],{"class":77},"        };\n",[67,397,398],{"class":69,"line":181},[67,399,95],{"emptyLinePlaceholder":94},[67,401,402,406],{"class":69,"line":189},[67,403,405],{"class":404},"s6cf3","        try",[67,407,408],{"class":77},"{\n",[67,410,411,414,417,419,422,425,428,431,433,436,438,440,443],{"class":69,"line":197},[67,412,413],{"class":246},"            const",[67,415,416],{"class":250}," result",[67,418,338],{"class":77},[67,420,421],{"class":404}," await",[67,423,424],{"class":250}," dynamodb",[67,426,427],{"class":77},".",[67,429,430],{"class":257},"scan",[67,432,261],{"class":73},[67,434,435],{"class":250},"params",[67,437,357],{"class":73},[67,439,427],{"class":77},[67,441,442],{"class":257},"promise",[67,444,445],{"class":73},"()\n",[67,447,448,451],{"class":69,"line":205},[67,449,450],{"class":404},"            return",[67,452,300],{"class":77},[67,454,455,458,460,464],{"class":69,"line":213},[67,456,457],{"class":73},"                statusCode",[67,459,78],{"class":77},[67,461,463],{"class":462},"sx098"," 200",[67,465,318],{"class":77},[67,467,469,472,474,477,479,482,484,487,489,492,494],{"class":69,"line":468},17,[67,470,471],{"class":73},"                body",[67,473,78],{"class":77},[67,475,476],{"class":250}," JSON",[67,478,427],{"class":77},[67,480,481],{"class":257},"stringify",[67,483,261],{"class":73},[67,485,486],{"class":250},"result",[67,488,427],{"class":77},[67,490,491],{"class":250},"Items",[67,493,357],{"class":73},[67,495,318],{"class":77},[67,497,499],{"class":69,"line":498},18,[67,500,501],{"class":77},"            };\n",[67,503,505,508,511,513,516,518],{"class":69,"line":504},19,[67,506,507],{"class":77},"        }",[67,509,510],{"class":404},"catch",[67,512,344],{"class":73},[67,514,515],{"class":250},"e",[67,517,357],{"class":73},[67,519,408],{"class":77},[67,521,523,525],{"class":69,"line":522},20,[67,524,450],{"class":404},[67,526,300],{"class":77},[67,528,530,532,534,537],{"class":69,"line":529},21,[67,531,457],{"class":73},[67,533,78],{"class":77},[67,535,536],{"class":462}," 500",[67,538,318],{"class":77},[67,540,542,544,546,549,551,554],{"class":69,"line":541},22,[67,543,471],{"class":73},[67,545,78],{"class":77},[67,547,548],{"class":250}," e",[67,550,427],{"class":77},[67,552,553],{"class":250},"message",[67,555,318],{"class":77},[67,557,559],{"class":69,"line":558},23,[67,560,501],{"class":77},[67,562,564],{"class":69,"line":563},24,[67,565,566],{"class":77},"        }\n",[67,568,570],{"class":69,"line":569},25,[67,571,572],{"class":77},"}\n",[13,574,575],{},"そしてビルドしてテストをしても",[34,577,580],{"className":578,"code":579,"language":39},[37],"sam build\nsam local invoke -e events\u002Fevent.json\n\nInaccessible host: `127.0.0.1' at port `8000'. This service may not be available in the `ap-north-east1' region.\n",[41,581,579],{"__ignoreMap":43},[13,583,584],{},"とDynamoDBのホストに接続できていないというエラーが発生しました。色々検索した結果、以下の記事がキーとなり解決しました。",[13,586,587],{},[23,588,591],{"href":589,"rel":590},"https:\u002F\u002Ffuture-architect.github.io\u002Farticles\u002F20200323\u002F",[27],"Serverless連載1: SAMを使ったローカルテスト（Go編）",[51,593,594],{"id":594},"解決法",[13,596,597],{},"上記記事の図をみて一気に理解できました。原因はSAMのコンテナがDynamoDBのコンテナに接続できず、またホスト（localhost)の指定が間違っていたからです。",[599,600,602],"h3",{"id":601},"samもdockerで実行されている","SAMもdockerで実行されている",[13,604,605,606],{},"SAMは自動デプロイだけでなく、ローカルでのテストもサポートしています。",[23,607,610],{"href":608,"rel":609},"https:\u002F\u002Fdocs.aws.amazon.com\u002Fja_jp\u002Fserverless-application-model\u002Flatest\u002Fdeveloperguide\u002Fserverless-sam-cli-install-linux.html#serverless-sam-cli-install-linux-docker",[27],"テストではDockerを用いることがドキュメントにも書かれています。",[13,612,613,614,617,618,621,622,625],{},"LambdaのコードはDockerのあるコンテナにて実行されることになります。そのため、",[41,615,616],{},"localhost:8000","というのは私のホストOSの8000ポートでなく、SAMが実行されている環境の8000ポートに向いていたのです。SAMコンテナ内の",[41,619,620],{},"locahost:8000","にはDynamoDBなんてありませんから、",[41,623,624],{},"Inaccessible host"," になるのは当たり前です。",[13,627,628],{},"つまりやることはSAMコンテナネットワークと、DynamoDBコンテナネットワークを接続してあげる必要があります。",[599,630,632],{"id":631},"dynamodbの設定とテスト実行時の引数を与える","DynamoDBの設定とテスト実行時の引数を与える",[13,634,635],{},"先ほどのDynamoDBのdocker-compose.yamlを見てみると、ネットワークの設定があります。",[34,637,639],{"className":60,"code":638,"filename":62,"language":63,"meta":43,"style":43},"version: \"3\"\n\nservices:\n  dynamodb-local:\n    container_name: dynamodb-local\n    image: amazon\u002Fdynamodb-local\n    ports:\n      - 8000:8000\n    command: -jar DynamoDBLocal.jar -dbPath \u002Fdata -sharedDb\n    volumes:\n      - .\u002Fdata:\u002Fdata\n    networks:\n      - lambda-local\nnetworks:\n  lambda-local:\n    external: true #これ！\n",[41,640,641,653,657,663,669,677,685,691,697,705,711,717,723,729,735,741],{"__ignoreMap":43},[67,642,643,645,647,649,651],{"class":69,"line":70},[67,644,74],{"class":73},[67,646,78],{"class":77},[67,648,81],{"class":77},[67,650,85],{"class":84},[67,652,88],{"class":77},[67,654,655],{"class":69,"line":91},[67,656,95],{"emptyLinePlaceholder":94},[67,658,659,661],{"class":69,"line":98},[67,660,101],{"class":73},[67,662,104],{"class":77},[67,664,665,667],{"class":69,"line":107},[67,666,110],{"class":73},[67,668,104],{"class":77},[67,670,671,673,675],{"class":69,"line":115},[67,672,118],{"class":73},[67,674,78],{"class":77},[67,676,123],{"class":84},[67,678,679,681,683],{"class":69,"line":126},[67,680,129],{"class":73},[67,682,78],{"class":77},[67,684,134],{"class":84},[67,686,687,689],{"class":69,"line":137},[67,688,140],{"class":73},[67,690,104],{"class":77},[67,692,693,695],{"class":69,"line":145},[67,694,148],{"class":77},[67,696,151],{"class":84},[67,698,699,701,703],{"class":69,"line":154},[67,700,157],{"class":73},[67,702,78],{"class":77},[67,704,162],{"class":84},[67,706,707,709],{"class":69,"line":165},[67,708,168],{"class":73},[67,710,104],{"class":77},[67,712,713,715],{"class":69,"line":173},[67,714,148],{"class":77},[67,716,178],{"class":84},[67,718,719,721],{"class":69,"line":181},[67,720,184],{"class":73},[67,722,104],{"class":77},[67,724,725,727],{"class":69,"line":189},[67,726,148],{"class":77},[67,728,194],{"class":84},[67,730,731,733],{"class":69,"line":197},[67,732,200],{"class":73},[67,734,104],{"class":77},[67,736,737,739],{"class":69,"line":205},[67,738,208],{"class":73},[67,740,104],{"class":77},[67,742,743,745,747,750],{"class":69,"line":213},[67,744,216],{"class":73},[67,746,78],{"class":77},[67,748,749],{"class":221}," true",[67,751,753],{"class":752},"sC9rS"," #これ！\n",[13,755,756],{},"そしてSAMコンテナの立ち上げの際、このネットワークに接続できる引数があります。",[34,758,761],{"className":759,"code":760,"language":39},[37],"sam local invoke -e events\u002Fevent.json --docker-network lambda-local\n",[41,762,760],{"__ignoreMap":43},[13,764,765,768],{},[41,766,767],{},"--docker-network lambda-local"," を指定することで、DynamoDBコンテナのネットワークにアクセスできるようになります。そしてホストも以下のように書き換えます。",[34,770,772],{"className":236,"code":771,"filename":238,"language":239,"meta":43,"style":43},"var dynamoOpt = {\n    endpoint: \"http:\u002F\u002Fdynamodb-local:8000\",\n};\n",[41,773,774,784,799],{"__ignoreMap":43},[67,775,776,778,780,782],{"class":69,"line":70},[67,777,292],{"class":246},[67,779,295],{"class":250},[67,781,254],{"class":77},[67,783,300],{"class":77},[67,785,786,788,790,792,795,797],{"class":69,"line":91},[67,787,305],{"class":73},[67,789,78],{"class":77},[67,791,81],{"class":77},[67,793,794],{"class":84},"http:\u002F\u002Fdynamodb-local:8000",[67,796,315],{"class":77},[67,798,318],{"class":77},[67,800,801],{"class":69,"line":98},[67,802,323],{"class":77},[13,804,805],{},"実際のコードではホスト部分は環境変数に置き換えます。とりあえず上記ホストに変更して、もう一度先ほどのコマンドを実行しました。結果、なんとかDynamoDBに接続してCRUD操作ができるようになりました。",[13,807,808],{},"複数のコンテナを立てていたりすると、どこがどう繋がっているか分からなくなるので結構はまりました。汗）",[810,811,812],"style",{},"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 .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 .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html pre.shiki code .s7ZW3, html code.shiki .s7ZW3{--shiki-default:#BABED8;--shiki-default-font-style:italic}html pre.shiki code .s6cf3, html code.shiki .s6cf3{--shiki-default:#89DDFF;--shiki-default-font-style:italic}html pre.shiki code .sx098, html code.shiki .sx098{--shiki-default:#F78C6C}html pre.shiki code .sC9rS, html code.shiki .sC9rS{--shiki-default:#464B5D;--shiki-default-font-style:italic}",{"title":43,"searchDepth":98,"depth":98,"links":814},[815,816],{"id":53,"depth":91,"text":54},{"id":594,"depth":91,"text":594,"children":817},[818,819],{"id":601,"depth":98,"text":602},{"id":631,"depth":98,"text":632},[821],"ministack","2026-05-07","AWS SAMとDynamoDBの接続","md",null,{},"\u002Farticles\u002Faws-sam-disconnect-dymamodb-local",{"title":8,"description":823},"articles\u002Faws-sam-disconnect-dymamodb-local",[831,832,833],"aws","docker","serverless","util\u002Fsam_squirrel.png","wCqQZ02-pnfwd_-TA9YeBhfjRe0nMADGHPc1dk6e2z0",{"id":837,"title":838,"body":839,"category":1245,"createdAt":1246,"description":1247,"extension":824,"index":825,"meta":1248,"navigation":94,"path":1249,"publish":94,"seo":1250,"series":825,"seriesTitle":825,"stem":1251,"tag":1252,"thumbnail":825,"updatedAt":825,"__hash__":1254},"articles\u002Farticles\u002F2024_06_28_laravel-guard-igonore-on-seeder.md","LaravelのSeederではModelのguardが無効になることで場合によって存在しないカラムへの挿入でエラーとなる",{"type":10,"value":840,"toc":1240},[841,852,859,862,865,868,953,956,959,1068,1071,1101,1104,1166,1180,1197,1200,1206,1225,1228,1231,1237],[13,842,843,844,847,848,851],{},"こんにちはjunです。Laravelのseederでfactoryの",[41,845,846],{},"create()","を使用せず、",[41,849,850],{},"new Model()","からなるモデルメソッドからデータ挿入を行った時に詰まったことがあり、その忘備録です。",[13,853,854,855,858],{},"結論としてSeederではモデルのguardが無効になり、",[41,856,857],{},"fill()","を使用した挿入で存在しないカラムへのinsertが走ってエラーになることが原因でした。背景から話すので、もしさっさと解決策を知りたい方は「解決方法」のとこまで移動してください。",[51,860,861],{"id":861},"モデルベースで挿入処理をしたい",[13,863,864],{},"Laravelにはダミーデータを挿入するためにseederと呼ばれる挿入スクリプトファイル、factoryというfakerを利用して指定モデルへのダミーデータの定義を作成できます。とりあえずデータを作成してページングや表示の様子を確かめる時に非常に便利です。",[13,866,867],{},"ただしこのfacotryで挿入するときは「DBのinsert処理を走らせる」方法です。factoryと連結したモデルのキャストなどは考慮してくれますが、基本的にはDBのinsert文を作成するような感じです。",[34,869,873],{"className":870,"code":871,"language":872,"meta":43,"style":43},"language-php shiki shiki-themes material-theme-ocean","\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","php",[41,874,875,880,884,889,893,898,903,907,912,917,922,927,935,943,948],{"__ignoreMap":43},[67,876,877],{"class":69,"line":70},[67,878,879],{},"\u002F\u002F ※ Laravel6での記述方法です！\n",[67,881,882],{"class":69,"line":91},[67,883,95],{"emptyLinePlaceholder":94},[67,885,886],{"class":69,"line":98},[67,887,888],{},"\u002F** @var \\Illuminate\\Database\\Eloquent\\Factory $factory *\u002F\n",[67,890,891],{"class":69,"line":107},[67,892,95],{"emptyLinePlaceholder":94},[67,894,895],{"class":69,"line":115},[67,896,897],{},"use App\\Models\\Article;\n",[67,899,900],{"class":69,"line":126},[67,901,902],{},"use Faker\\Generator as Faker;\n",[67,904,905],{"class":69,"line":137},[67,906,95],{"emptyLinePlaceholder":94},[67,908,909],{"class":69,"line":145},[67,910,911],{},"$factory->define(Article::class, function (Faker $faker) {\n",[67,913,914],{"class":69,"line":154},[67,915,916],{},"    return [\n",[67,918,919],{"class":69,"line":165},[67,920,921],{},"        'title'=>$faker->text(),\n",[67,923,924],{"class":69,"line":173},[67,925,926],{},"        'content'=>$faker->realText(),\n",[67,928,929,932],{"class":69,"line":181},[67,930,931],{},"        'is_active'=>true,",[67,933,934],{}," \u002F\u002F キャストしてくれる。true => 1\n",[67,936,937,940],{"class":69,"line":189},[67,938,939],{},"        'author'=> User::inRandomOrder()->first()->id",[67,941,942],{}," \u002F\u002F ID番号を指定しないとダメ。\n",[67,944,945],{"class":69,"line":197},[67,946,947],{},"    ];\n",[67,949,950],{"class":69,"line":205},[67,951,952],{},"});\n",[13,954,955],{},"複雑なリレーションがあったり、モデル内でなんやかんやして他のモデルやデータに変更を与える場合は上記の方法では面倒なことがあります。そのため、facotryで仮のhttpリクエストに相当する連想配列を作成しておき、リクエストボディから挿入処理を行うときのメソッドを経由してデータを作成したい時があります。",[13,957,958],{},"例ではこんな感じ..",[34,960,962],{"className":870,"code":961,"language":872,"meta":43,"style":43},"\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",[41,963,964,969,974,979,984,989,993,997,1001,1006,1011,1016,1021,1026,1031,1035,1040,1044,1048,1053,1058,1063],{"__ignoreMap":43},[67,965,966],{"class":69,"line":70},[67,967,968],{},"\u002F\u002F Controller.php\n",[67,970,971],{"class":69,"line":91},[67,972,973],{},"public function createData(HogeRequest $request){\n",[67,975,976],{"class":69,"line":98},[67,977,978],{},"    $m = new TargetModel();\n",[67,980,981],{"class":69,"line":107},[67,982,983],{},"    $m->add($request->validated());\n",[67,985,986],{"class":69,"line":115},[67,987,988],{},"    return $m\n",[67,990,991],{"class":69,"line":126},[67,992,572],{},[67,994,995],{"class":69,"line":137},[67,996,95],{"emptyLinePlaceholder":94},[67,998,999],{"class":69,"line":145},[67,1000,95],{"emptyLinePlaceholder":94},[67,1002,1003],{"class":69,"line":154},[67,1004,1005],{},"\u002F\u002F TargetModel.php\n",[67,1007,1008],{"class":69,"line":165},[67,1009,1010],{},"public function add(array $val){\n",[67,1012,1013],{"class":69,"line":173},[67,1014,1015],{},"    $this->fill($val);\n",[67,1017,1018],{"class":69,"line":181},[67,1019,1020],{},"    $this->save();\n",[67,1022,1023],{"class":69,"line":189},[67,1024,1025],{},"    \u002F\u002F complex process...\n",[67,1027,1028],{"class":69,"line":197},[67,1029,1030],{},"    return $this;\n",[67,1032,1033],{"class":69,"line":205},[67,1034,572],{},[67,1036,1037],{"class":69,"line":213},[67,1038,1039],{},"\u002F\u002F ↑このメソッドを使用したい！\n",[67,1041,1042],{"class":69,"line":468},[67,1043,95],{"emptyLinePlaceholder":94},[67,1045,1046],{"class":69,"line":498},[67,1047,95],{"emptyLinePlaceholder":94},[67,1049,1050],{"class":69,"line":504},[67,1051,1052],{},"\u002F\u002F seeder\u002FinsertDummy.php\n",[67,1054,1055],{"class":69,"line":522},[67,1056,1057],{},"$f = factory(App\\Models\\TargetModel.php)->make(); \u002F\u002F データの挿入はせず、fakerで作成した連想配列が取得できます..!\n",[67,1059,1060],{"class":69,"line":529},[67,1061,1062],{},"$m = new TargetModel();\n",[67,1064,1065],{"class":69,"line":541},[67,1066,1067],{},"$m->add($f);\n",[13,1069,1070],{},"例として、id,title,contentのカラムを持つArticleテーブルとManyToManyのリレーションをもつTagsテーブルがあるとしましょう。作成する際のhttp bodyでは",[34,1072,1074],{"className":870,"code":1073,"language":872,"meta":43,"style":43},"[\n    'title'=>'タイトルだよ',\n    'content'=>'文章文章...'\n    'tags'=>[1,3]\n]\n",[41,1075,1076,1081,1086,1091,1096],{"__ignoreMap":43},[67,1077,1078],{"class":69,"line":70},[67,1079,1080],{},"[\n",[67,1082,1083],{"class":69,"line":91},[67,1084,1085],{},"    'title'=>'タイトルだよ',\n",[67,1087,1088],{"class":69,"line":98},[67,1089,1090],{},"    'content'=>'文章文章...'\n",[67,1092,1093],{"class":69,"line":107},[67,1094,1095],{},"    'tags'=>[1,3]\n",[67,1097,1098],{"class":69,"line":115},[67,1099,1100],{},"]\n",[13,1102,1103],{},"このようにフロントエンドから送信され、対象のArticleが作成されたのちに指定のTagsとのIDが連携されます。",[34,1105,1107],{"className":870,"code":1106,"language":872,"meta":43,"style":43},"\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",[41,1108,1109,1114,1119,1123,1128,1133,1137,1141,1145,1149,1153,1158,1162],{"__ignoreMap":43},[67,1110,1111],{"class":69,"line":70},[67,1112,1113],{},"\u002F\u002F Article.php\n",[67,1115,1116],{"class":69,"line":91},[67,1117,1118],{},"protected $guarded = ['id'];\n",[67,1120,1121],{"class":69,"line":98},[67,1122,95],{"emptyLinePlaceholder":94},[67,1124,1125],{"class":69,"line":107},[67,1126,1127],{},"public function tag(){\n",[67,1129,1130],{"class":69,"line":115},[67,1131,1132],{},"    return $this->hasMany(\\App\\Models\\Tags);\n",[67,1134,1135],{"class":69,"line":126},[67,1136,572],{},[67,1138,1139],{"class":69,"line":137},[67,1140,95],{"emptyLinePlaceholder":94},[67,1142,1143],{"class":69,"line":145},[67,1144,1010],{},[67,1146,1147],{"class":69,"line":154},[67,1148,1015],{},[67,1150,1151],{"class":69,"line":165},[67,1152,1020],{},[67,1154,1155],{"class":69,"line":173},[67,1156,1157],{},"    $this->sync($val['tags'])\n",[67,1159,1160],{"class":69,"line":181},[67,1161,1030],{},[67,1163,1164],{"class":69,"line":189},[67,1165,572],{},[13,1167,1168,1169,1172,1173,1175,1176,1179],{},"モデルでは",[41,1170,1171],{},"guarded","を指定して、fillが使用できるようにします。リレーション部分のカラムはlaravelが対応してくれます。そのため、",[41,1174,857],{},"では",[41,1177,1178],{},"tags","というカラムに挿入させるようなSQLは生成されなくなります。",[13,1181,1182,1183,1186,1187,1189,1190,1193,1194,1196],{},"しかしseederでこのメソッドを使用した時に ",[41,1184,1185],{},"Array to srting convertion"," というエラーが発生し詰まってしまいました。よくよくエラートレースを見ると、insert文のカラムに",[41,1188,1178],{},"が存在しているのがわかりました。「",[41,1191,1192],{},"filable","が正常に機能していないのか？」と予測をたてて調べたらどうやら「seeder中はfactory通りの内容が挿入されるように、",[41,1195,1171],{},"が無効になりfillの値が全て考慮される」とのことでした。fillableがワイルドカードのような状態となり、tagsがArticlesのカラムとして認識されたことが原因です。",[51,1198,1199],{"id":1199},"解決方法",[13,1201,1202,1203,1205],{},"ではseeder中でもモデルのfillable,",[41,1204,1171],{},"を有効にすれば解決します。その専用のメソッドがあります。",[34,1207,1209],{"className":870,"code":1208,"language":872,"meta":43,"style":43},"use Illuminate\\Database\\Eloquent\\Model;\n\nModel::reguard(); \u002F\u002F←これをfillの前に入れる\n",[41,1210,1211,1216,1220],{"__ignoreMap":43},[67,1212,1213],{"class":69,"line":70},[67,1214,1215],{},"use Illuminate\\Database\\Eloquent\\Model;\n",[67,1217,1218],{"class":69,"line":91},[67,1219,95],{"emptyLinePlaceholder":94},[67,1221,1222],{"class":69,"line":98},[67,1223,1224],{},"Model::reguard(); \u002F\u002F←これをfillの前に入れる\n",[13,1226,1227],{},"この静的メソッドを呼び出すことでguardedを再度有効にでき、指定・存在するカラムのみにfillが行われます。",[51,1229,1230],{"id":1230},"参考",[13,1232,1233],{},[23,1234,1235],{"href":1235,"rel":1236},"https:\u002F\u002Fstackoverflow.com\u002Fquestions\u002F67092809\u002Ffillable-doesnt-work-when-creating-model-from-seeder",[27],[810,1238,1239],{},"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":43,"searchDepth":98,"depth":98,"links":1241},[1242,1243,1244],{"id":861,"depth":91,"text":861},{"id":1199,"depth":91,"text":1199},{"id":1230,"depth":91,"text":1230},[821],"2024-06-28","LaravelのSeederでModelメソッドを用いて挿入する場合の注意点",{},"\u002Farticles\u002F2024_06_28_laravel-guard-igonore-on-seeder",{"title":838,"description":1247},"articles\u002F2024_06_28_laravel-guard-igonore-on-seeder",[1253],"laravel","V44O9yNCFMlfPQN4Yjg8vBnRzTGUlYiKV2q4aF6CG4U",{"id":1256,"title":1257,"body":1258,"category":1439,"createdAt":1440,"description":1441,"extension":824,"index":825,"meta":1442,"navigation":94,"path":1443,"publish":94,"seo":1444,"series":825,"seriesTitle":825,"stem":1445,"tag":1446,"thumbnail":1447,"updatedAt":825,"__hash__":1448},"articles\u002Farticles\u002Fnever-run-app-on-upgrade.md","ライブラリアップデート時はアプリケーションを実行させるな！",{"type":10,"value":1259,"toc":1430},[1260,1263,1266,1277,1280,1283,1286,1289,1316,1319,1325,1328,1331,1334,1337,1340,1343,1351,1358,1365,1368,1377,1380,1383,1386,1390,1393,1405,1408,1411,1415,1418,1421,1424,1427],[13,1261,1262],{},"こんにちはjunです。最近のフレームワークやミドルウェアはバージョンあたりのサポート期限を短くして、頻繁にバージョンを更新することが多いです。マイナーはともかくメジャーバージョンでは変更点が多いので慎重に行う必要があります。",[13,1264,1265],{},"私も保守をしていたLaravelプロジェクトで8→9へのアップグレード作業がありました。マイグレーションガイドに従って他ライブラリのバージョン変更などを行い、検証してデモ環境で問題なかったため本番で実施しました。しかし本番でなぜか失敗してロールバックを2回も行う事態が発生しました。",[13,1267,1268,1269,1272,1273,1276],{},"結論からいうとLaravelの",[41,1270,1271],{},"php artisan down","を実行して",[41,1274,1275],{},"composer update","諸々を実行しましたが、ユーザーのリクエストがその時にも来ていたことによってLaravelが起動。キャッシュなどの影響によってアップデートしたアプリケーションが動かなくなったことが原因と思われます。",[13,1278,1279],{},"この記事では上記事態の詳細な経緯と原因、やらかしたときのロールバック・対策、今後の対策について書こうと思います。Laravelを実例として出しますが、DjangoやRails、expressなど他のフレームワークでのバージョンアップ作業でも参考になると思います。反面教師としてご活用ください。",[51,1281,1282],{"id":1282},"経緯",[13,1284,1285],{},"Laravelプロジェクトの8→9への変更にあたりアップグレードガイドを参考にして作業を行い、テストをパス、そしてデモ環境で問題なくアップデートが出来たため本番環境での実行を進めました。",[13,1287,1288],{},"流れとしては",[1290,1291,1292,1298,1301,1310],"ol",{},[1293,1294,1295,1297],"li",{},[41,1296,1271],{},"を実行してページの表示や全てのリクエストに対して503を出すように変更。",[1293,1299,1300],{},"変更したファイルをプル",[1293,1302,1303,1305,1306,1309],{},[41,1304,1275],{},"や",[41,1307,1308],{},"php artisan migrate","を行ってライブラリとDBを更新",[1293,1311,1312,1315],{},[41,1313,1314],{},"php artisan up","でメンテナンスモードを解除。キャッシュの削除などを行う。",[13,1317,1318],{},"上記のように行いました。4で解除をしてサービスのURLを見てみると...",[1320,1321],"image-render",{":src":1322,":width":1323,":center":1324},"'never-run-app-on-upgrade\u002F500err.png'","'500px'","true",[13,1326,1327],{},"(  ０Д０)",[13,1329,1330],{},"アプリケーション・サーバキャッシュの削除を行いましたがページは変わらず。しかし、php artisan tinkerやlocalhostにcurlした場合は問題なく応答しており原因が不明でした。",[13,1332,1333],{},"しばらく修正を試みて色々行いましたが結局直らず、メンテナンス終了時刻が迫ってきたためロールバックを行いました。",[13,1335,1336],{},"とりあえず元には戻りサービスが利用可能になったので一安心。しかし本番のみ発生するこの事象に頭を悩ませて他の人と相談しました。",[51,1338,1339],{"id":1339},"解決と原因",[13,1341,1342],{},"先方に相談してまた次の日にメンテナンスを実行。相談に乗っかってくれたインフラエンジニアさんがスタンバイしながら、私が上記の操作を行いました。そして今回も発生して一通りログなどをみても特に有効打になりそうな内容が見つかりませんでした。",[13,1344,1345,1346,1350],{},"しかし",[1347,1348,1349],"strong",{},"なぜか100%登録されているはずのroutingがないというエラーがLaravelログに記録されており","、Laravelそのものが上手く起動していないことがわかりました。またロールバックを行い、今度は別のディレクトリをコピー作成してそこでアップデート作業を行いました。とりあえず本番環境中でマイグレーションを行ってそのファイル一式をローカルなりで解析しようという戦略に変更。",[13,1352,1353,1354,1357],{},"念の為ドキュメントルートの向き先を変更して同じ状態であることを確認すると...",[1347,1355,1356],{},"なぜか正常のいつものサービスの画面が表示されました。","。",[13,1359,1360,1361,1364],{},"とりあえず問題なくアップデートは終了しことなきを得ました。しかし原因関しては予測とはなりますが、",[1347,1362,1363],{},"php artisan downではcomposer update 中でもLaravel自体（index.php)が実行される。本体のライブラリは色々と更新中にも関わらず、スクリプトが実行されることで変なキャッシュが生成されたことが原因だったのではないか？"," という結論に至りました。",[51,1366,1367],{"id":1367},"対策と考察",[13,1369,1370,1372,1373,1376],{},[41,1371,1271],{},"はあくまでLarabvelが動的に503エラーを生成するだけであり、リクエスト自体はLaravelのindex.php自体は実行されていることが今回の要因と思われます。そのため今回のようなライブラリを更新する際などは",[1347,1374,1375],{},"そもそもLaravel自体起動しないようにする","必要がありました。",[13,1378,1379],{},"方法としてはドキュメントルートや（ネットワークの）ルーティングを変更して、変更先にはメンテナンス画面のHTMLを置いておく。そして全てのリクエストに対してそのファイルを見せるように設定することが一番な気がします。",[13,1381,1382],{},"今回はLaravelでしたが、Djangoや他のフレームワークでもメンテナンス時はwebサーバレベルでルーティングを変更して、それらが起動しないようにすることが大切だと思います。",[51,1384,1385],{"id":1385},"やらかす前の対策とやらかした時のロールバック",[599,1387,1389],{"id":1388},"やらかす前に","やらかす前に..",[13,1391,1392],{},"まず本番環境では大小の変更に関わらず、必ずバックアップを行います。この場合Laravelレベルでなく、",[1394,1395,1396,1399,1402],"ul",{},[1293,1397,1398],{},"DBはダンプなりしてバックアップをとっておく。",[1293,1400,1401],{},"アップロードファイルもローカルならばバックアップしておく。",[1293,1403,1404],{},"git などで戻れる体制を整える。",[13,1406,1407],{},"そしてアップデートの場合、composer.jsonなどの旧バージョンの管理ファイルをバックアップしておくのを忘れないでください。とにかくバックアップさえあればなんとかなることが多いです。",[13,1409,1410],{},"そしてデモ環境などであらかじめアップグレードの予行練習はしましょう。",[599,1412,1414],{"id":1413},"やらかしたら","やらかしたら..",[13,1416,1417],{},"まずは落ち着きます。とにかく落ち着きましょう。私の場合、その後の原因解明zoomでも指が震えたままで結構タイプミスしていました笑。このような場合、同席したインフラエンジニアさんはタバコを一旦吸うそうです。",[13,1419,1420],{},"そして落ち着きの間に連絡の取れるかに一報を必ず入れましょう。焦っているときは人間変な行動をしてしまうことが多く、余計に自体を悪化させることがあります。その時に他に冷静な人がいるだけでもすぐに解決策が見つかったり、次に何をすべきかを考える余裕ができます。",[13,1422,1423],{},"そしてメンテナンス時間内であれば何か手順をミスっていないか、忘れていないかを再度確認をしていきましょう。どうしても時間が間に合わなそうであればロールバックを行い、少なくともサービスが実行できるようにしましょう。",[13,1425,1426],{},"最後にログ取得や詳細な状況を記録して今後の対策に当てましょう。",[13,1428,1429],{},"いやーそれにしても何年経っても本番環境って怖いです。",{"title":43,"searchDepth":98,"depth":98,"links":1431},[1432,1433,1434,1435],{"id":1282,"depth":91,"text":1282},{"id":1339,"depth":91,"text":1339},{"id":1367,"depth":91,"text":1367},{"id":1385,"depth":91,"text":1385,"children":1436},[1437,1438],{"id":1388,"depth":98,"text":1389},{"id":1413,"depth":98,"text":1414},[821],"2023-08-23","LaravelやDjangoなどのフレームワークで",{},"\u002Farticles\u002Fnever-run-app-on-upgrade",{"title":1257,"description":1441},"articles\u002Fnever-run-app-on-upgrade",[],"never-run-app-on-upgrade\u002F500err.png","6Asz-KAzas2fuGFUpR0qDViaEOuIQy22DGaqgGP5kgo",{"id":1450,"title":1451,"body":1452,"category":1553,"createdAt":1554,"description":1555,"extension":824,"index":825,"meta":1556,"navigation":94,"path":1557,"publish":94,"seo":1558,"series":825,"seriesTitle":825,"stem":1559,"tag":1560,"thumbnail":1561,"updatedAt":825,"__hash__":1562},"articles\u002Farticles\u002Fhandle-cloudfront-cache.md","cloudfrontでクエリパラメータを使ってコンテンツの更新におけるキャッシュを制御する",{"type":10,"value":1453,"toc":1551},[1454,1467,1470,1477,1485,1492,1495,1498,1502,1505,1508,1511,1514,1517,1520,1523,1526,1537,1540],[13,1455,1456,1457,1462,1463,1466],{},"こんにちはjunです。",[23,1458,1461],{"href":1459,"rel":1460},"https:\u002F\u002Froute-share.net",[27],"RouteShareというユーザー投稿型の個人開発","でユーザーがアップロードしたファイルをS3に配置し、cloudfrontでキャッシュを効かせて取得するように設定しました。このときユーザーアバターや投稿コンテンツの自動生成サムネイルなど特定のファイルは",[41,1464,1465],{},"{ID}.png","のようにDB上のIDと拡張子で表現していました。IDベースのファイル名にすることでIDさえわかればユーザーや投稿のサムネイルが表示できるというメリットがありました。",[13,1468,1469],{},"しかし上記の方法ではファイル名が変わらず、更新した際のcloudfrontのキャッシュで画像が切り替わらないという事態がありました。今回はこのような「cloudfront上で同じ名前で管理しているが、都度更新があった際に確実に更新したファイルを表示させる方法」についての内容です。",[13,1471,1472],{},[23,1473,1476],{"href":1474,"rel":1475},"https:\u002F\u002Faws.amazon.com\u002Fjp\u002Fpremiumsupport\u002Fknowledge-center\u002Fcloudfront-serving-outdated-content-s3\u002F",[27],"参考記事はこちら",[13,1478,1479,1480],{},"まず最初にcloudfrontにはサービス上でキャッシュを削除する機能はあります。指定のパスを設定することで、キャッシュさせたURL（ここではファイルURL）をcloudfront上から削除することができます。キャッシュがなくなるのでオリジンに取得しにいき、更新したコンテンツを取得できます。しかしそれをコンテンツごとに行うのは大変です。単純に数も多いですし更新があったコンテンツを検知して、APIでcloudfrontの操作を行う必要があるからです。",[23,1481,1484],{"href":1482,"rel":1483},"https:\u002F\u002Fdocs.aws.amazon.com\u002Fja_jp\u002FAmazonCloudFront\u002Flatest\u002FDeveloperGuide\u002FInvalidation.html#PayingForInvalidation",[27],"また無料枠分はありますが、キャッシュの削除にはお金がかかります。",[13,1486,1487,1488,1491],{},"cloudfrontは実はキャッシュは効かさないこともできますが、折角のCDNが勿体無いです。更新を効かしつつも普段はキャッシュさせてパフォーマンスを上げる方法としては、ファイル名に",[41,1489,1490],{},"?ver=xxxx","のようなクエリパラメータをつけて、別のURLとして認識させる方法があります。これはcloudfrontのみならず、他のキャッシュ対策でもよく行われます。バージョンパラメータはクライアント側で制御します。例えば、ユーザーアバターの際はアップロード更新時にユーザーレコードのupdated_atを更新し、ユーザーレコードを参照する箇所ではそのupdated_atをクエリパラメータに入れます。ビルドしたjsやcssなどにはビルド日時とかを入れられるようにするといいかもしれません。",[13,1493,1494],{},"ただし、cloudfrontのデフォルトのキャッシュポリシーであるCachingOptimizedはこのクエリパラメータを考慮しません。どんなクエリパラメータをつけようが、同じファイル名であればキャッシュしてしまいます。そのため、新しいキャッシュポリシーを加えます。",[13,1496,1497],{},"最初にcloudfrontの画面を開き、ポリシーを選択します。",[1320,1499],{":src":1500,":width":1501,":center":1324,":current":1324},"'menu.png'","'300px'",[13,1503,1504],{},"カスタムポリシーからキャッシュポリシーを作成を選択",[1320,1506],{":src":1507,":center":1324,":current":1324},"'create.png'",[13,1509,1510],{},"クエリパラメータを有効にしたい既存のキャッシュポリシーと同じにします。今回はデフォルトのCachingOptimizedとTTLや圧縮サポートを選択。そして 「キャッシュキー設定」にてクエリ文字列を追加し、扱うパラメータを入力します。ポリシー名を設定し、問題なければ作成をクリックしてポリシーを登録します。",[1320,1512],{":src":1513,":center":1324,":current":1324},"'cache_key.png'",[13,1515,1516],{},"次にポリシーを当てはめたいディストリビューションのビヘイビアを編集します。",[1320,1518],{":src":1519,":center":1324,":current":1324},"'behavior.png'",[13,1521,1522],{},"キャッシュポリシーを先ほどの名前のものに設定します。",[1320,1524],{":src":1525,":center":1324,":current":1324},"'set_policy.png'",[13,1527,1528,1529,1532,1533,1536],{},"変更を保存をクリックして完了です。こうすれば",[41,1530,1531],{},"?ver=2022-12-01-00-00-01","から",[41,1534,1535],{},"?ver=2022-12-01-00-00-02","に変更した際に別のリソースとして認識され、オリジンへの取得が行われ再度そのクエリパラメータと共にキャッシュされます。",[13,1538,1539],{},"これの良い点は更新日時に応じてほぼ確実にキャッシュを殺すことができると同時に、次の更新まではキャッシュが効くようになることです。ヘッダー・クッキーでは容易にバージョンパラメータを追加するのが難しいので、ファイルであればクエリパラメータがおすすめです。",[13,1541,1542,1543,1532,1545,1547,1548,1550],{},"ちなみにパラメーターパターンに対するキャッシュは効いているので、",[41,1544,1531],{},[41,1546,1535],{},"に変わったとして",[41,1549,1531],{},"でアクセスするとキャッシュ期限まで古いものが表示されます。そのため「更新されたら必ず過去のキャッシュは消えるようにしないといけない」という場合は上記のcloudfrontでのキャッシュ削除が必要です。",{"title":43,"searchDepth":98,"depth":98,"links":1552},[],[821],"2022-12-01","クエリパラメータをもちいてコンテンツキャッシュによって更新が効かない事態を防ぐ",{},"\u002Farticles\u002Fhandle-cloudfront-cache",{"title":1451,"description":1555},"articles\u002Fhandle-cloudfront-cache",[831],"util\u002FArch_Amazon-CloudFront_64@5x.png","-ACQ7N5nl6EKYNvLu0Wqud1YIRKtjV-LoxVZjBGLnKw",{"id":1564,"title":1565,"body":1566,"category":1688,"createdAt":1689,"description":1565,"extension":824,"index":825,"meta":1690,"navigation":94,"path":1691,"publish":94,"seo":1692,"series":825,"seriesTitle":825,"stem":1693,"tag":1694,"thumbnail":825,"updatedAt":825,"__hash__":1696},"articles\u002Farticles\u002Fqueryselector-error-with-numeric.md","Document.querySelector() で先頭が数字のIDを指定するとエラーが起きる。",{"type":10,"value":1567,"toc":1686},[1568,1587,1593,1626,1632,1639,1647,1653,1670,1683],[13,1569,1570,1571,1574,1575,1578,1579,1582,1583,1586],{},"こんにちはjunです。最近、editor.jsだったり色々バニラJSを触る機会がありました。特定のNodeを取得する時に",[41,1572,1573],{},"document.querySelector()","を使用することでCSS・jqueryライクに要素を取得できます。今回はIDを持つ要素を",[41,1576,1577],{},"getByElementId()","を使わず、",[41,1580,1581],{},"querySelector()","を用いて",[41,1584,1585],{},"querySelector(\"#~~~\")","と取得した時に遭遇したエラーについてです。",[13,1588,1589,1590,1592],{},"タイトルの通りなのですが、",[41,1591,1581],{},"で先頭が数字のIDを指定するとエラーが起きます。",[34,1594,1596],{"className":236,"code":1595,"language":239,"meta":43,"style":43},"document.querySelector(\"#16test\");\n\u002F\u002F VM490:1 Uncaught DOMException: Failed to execute 'querySelector' on 'Document': '#16test' is not a valid selector.\n",[41,1597,1598,1621],{"__ignoreMap":43},[67,1599,1600,1603,1605,1608,1610,1612,1615,1617,1619],{"class":69,"line":70},[67,1601,1602],{"class":250},"document",[67,1604,427],{"class":77},[67,1606,1607],{"class":257},"querySelector",[67,1609,261],{"class":250},[67,1611,315],{"class":77},[67,1613,1614],{"class":84},"#16test",[67,1616,315],{"class":77},[67,1618,357],{"class":250},[67,1620,283],{"class":77},[67,1622,1623],{"class":69,"line":91},[67,1624,1625],{"class":752},"\u002F\u002F VM490:1 Uncaught DOMException: Failed to execute 'querySelector' on 'Document': '#16test' is not a valid selector.\n",[13,1627,1628,1629,1631],{},"簡単にコンソールでチェックできます。「not a valid selector」の通り、有効なセレクタじゃないよと怒っています。なぜこんなことが起きるのかを調べたところ、",[41,1630,1581],{},"はCSSセレクタの仕様を使っており、CSSのIDセレクタは「#の後に数字をつけてはいけない」という仕様があるからです。",[13,1633,1634,1635],{},"Stackoverflow\n",[23,1636,1637],{"href":1637,"rel":1638},"https:\u002F\u002Fstackoverflow.com\u002Fquestions\u002F37270787\u002Funcaught-syntaxerror-failed-to-execute-queryselector-on-document",[27],[13,1640,1641,1642],{},"上記のStackoverflowの回答が役に立ちました。HTML5の仕様としてはIDの最初の文字に数字を入れることは問題ありません。しかしCSS3のIDセレクタの仕様ではなんと、#のあとに数字を使用してはいけないと確かに書かれています。",[23,1643,1646],{"href":1644,"rel":1645},"https:\u002F\u002Fwww.w3.org\u002FTR\u002FCSS2\u002Fsyndata.html#characters",[27],"詳細はW3Cのこちらのページにあります。",[1648,1649,1650],"blockquote",{},[13,1651,1652],{},"they cannot start with a digit, two hyphens, or a hyphen followed by a digit.",[13,1654,1655,1656,1659,1660,1662,1663,1665,1666,1669],{},"第一の解決策は",[41,1657,1658],{},"getElementById()","を使うことです。そう思うと、本来DOMにIDを持つ要素は必ず一つなので",[41,1661,1658],{},"を使えばいいのに、なんで",[41,1664,1581],{},"を使っていたんだろうか？と思っていたら、ランダムに生成したIDをもつ要素配下のある子要素を取得する処理を実装する際、",[41,1667,1668],{},"querySelector(`#{id} .item`)","みたいな感じで実装してた時でした。",[13,1671,1672,1673,1676,1677,1679,1680,1682],{},"この場合ランダムに生成されるIDに数字が含まれないようにするか、",[41,1674,1675],{},"getElementById(id)?.querySelector(\".item\")","として一旦",[41,1678,1658],{},"を使ってから",[41,1681,1581],{},"を使うといいかなと思います。",[810,1684,1685],{},"html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .sC9rS, html code.shiki .sC9rS{--shiki-default:#464B5D;--shiki-default-font-style:italic}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":43,"searchDepth":98,"depth":98,"links":1687},[],[821],"2022-11-23",{},"\u002Farticles\u002Fqueryselector-error-with-numeric",{"title":1565,"description":1565},"articles\u002Fqueryselector-error-with-numeric",[1695,239],"html","-tKJHB-rOhRzOw2x_oCwOadM10j-DEzx-pvkwNQqzxA",{"id":1698,"title":1699,"body":1700,"category":1839,"createdAt":1840,"description":1699,"extension":824,"index":825,"meta":1841,"navigation":94,"path":1842,"publish":94,"seo":1843,"series":825,"seriesTitle":825,"stem":1844,"tag":1845,"thumbnail":1846,"updatedAt":825,"__hash__":1847},"articles\u002Farticles\u002Flaravel-socialite-scope-careless.md","LaravelのSociateでscopeで間違えてちょっと詰まった話",{"type":10,"value":1701,"toc":1837},[1702,1705,1708,1711,1745,1748,1755,1773,1782,1789,1794,1799,1802,1805,1835],[13,1703,1704],{},"こんにちはjunです。LaravelでGoogleとのログイン機能、連携機能を作っていた時にscopeメソッドの使い方でちょっと詰まりました。というよりか自分がよくドキュメントを読まなかったケアレスミスでしたが、どこかの開発者が詰まってこの記事を見つけられるようにインターネットの海にこの情報を書いておきます。",[13,1706,1707],{},"Laravelは本当に素晴らしいライブラリで、Laravel socialiteというライブラリを使用すれば外部サービスでのログイン機能があっという間に使用できます\n一通りログイン周りの機能を整えて、Google Driveとの連携の実装を行おうとして諸所の設定を行いました。",[13,1709,1710],{},"GCPの管理画面でGoogle Driveを追加してリファランスにしたがって以下のようにログイン時の処理にDriveへのアクセスを求めるようにスコープを追加しました。",[34,1712,1714],{"className":870,"code":1713,"language":872,"meta":43,"style":43},"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",[41,1715,1716,1721,1726,1731,1736,1741],{"__ignoreMap":43},[67,1717,1718],{"class":69,"line":70},[67,1719,1720],{},"public function redirectToGoogle(Request $request){\n",[67,1722,1723],{"class":69,"line":91},[67,1724,1725],{},"    return Socialite::driver('google')\n",[67,1727,1728],{"class":69,"line":98},[67,1729,1730],{},"    ->with([\"access_type\" => \"offline\", \"prompt\" => \"consent select_account\"])\n",[67,1732,1733],{"class":69,"line":107},[67,1734,1735],{},"    ->setScopes([\"https:\u002F\u002Fwww.googleapis.com\u002Fauth\u002Fdrive\"])\n",[67,1737,1738],{"class":69,"line":115},[67,1739,1740],{},"    ->redirect();\n",[67,1742,1743],{"class":69,"line":126},[67,1744,572],{},[13,1746,1747],{},"必要なAPIへのスコープを入力することで、取得できるアクセストークンでそのサービスにアクセスできます。しかし上記の実装でテストをしてみると、連携画面からローカルにリダイレクトする際に401が発生してしまい、連携が失敗しました。",[13,1749,1750,1751,1754],{},"なんでだろうとAPIの設定などをよく確認しましたが、原因は",[41,1752,1753],{},"setScopes()","というメソッドでした。",[13,1756,1757,1758,351,1761,1763,1764,1766,1767,1772],{},"Laravel socialiteはOauthのスコープを設定する際に２つのメソッドをしようできます。",[41,1759,1760],{},"scopes()",[41,1762,1753],{},"です。メソッドの名前的に",[41,1765,1753],{},"を使っていたのですが、これは",[23,1768,1771],{"href":1769,"rel":1770},"https:\u002F\u002Flaravel.com\u002Fdocs\u002F9.x\u002Fsocialite#access-scopes",[27],"ドキュメントにもある通り"," に",[1648,1774,1775],{},[13,1776,1777,1778,1781],{},"You can ",[1347,1779,1780],{},"overwrite all existing scopes"," on the authentication request using the setScopes method:",[13,1783,1784,1785,1788],{},"と",[1347,1786,1787],{},"すべての存在するスコープを上書きする","と書いてあります。",[13,1790,1791,1793],{},[41,1792,1760],{},"はドキュメントでは",[1648,1795,1796],{},[13,1797,1798],{},"This method will merge all previously specified scopes with the scopes that you specify:",[13,1800,1801],{},"と前々にあったスコープにマージすると書かれています。",[13,1803,1804],{},"細かい理由は分かりませんが、多分socialiteが提供する標準のスコープが消されてしまったのが原因かもしれません。とりあえず以下のように修正したら直りました。",[34,1806,1808],{"className":870,"code":1807,"language":872,"meta":43,"style":43},"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",[41,1809,1810,1814,1818,1822,1827,1831],{"__ignoreMap":43},[67,1811,1812],{"class":69,"line":70},[67,1813,1720],{},[67,1815,1816],{"class":69,"line":91},[67,1817,1725],{},[67,1819,1820],{"class":69,"line":98},[67,1821,1730],{},[67,1823,1824],{"class":69,"line":107},[67,1825,1826],{},"    ->scopes([\"https:\u002F\u002Fwww.googleapis.com\u002Fauth\u002Fdrive\"]) \u002F\u002F scopesに変更\n",[67,1828,1829],{"class":69,"line":115},[67,1830,1740],{},[67,1832,1833],{"class":69,"line":126},[67,1834,572],{},[810,1836,1239],{},{"title":43,"searchDepth":98,"depth":98,"links":1838},[],[821],"2022-05-23",{},"\u002Farticles\u002Flaravel-socialite-scope-careless",{"title":1699,"description":1699},"articles\u002Flaravel-socialite-scope-careless",[1253],"_common\u002Flaravel.png","tdenFwXM57aAqHk4siNzWCo8FXhh1YpEFCNAWNUhrnw",{"id":1849,"title":1850,"body":1851,"category":3521,"createdAt":3522,"description":1850,"extension":824,"index":825,"meta":3523,"navigation":94,"path":3524,"publish":94,"seo":3525,"series":825,"seriesTitle":825,"stem":3526,"tag":3527,"thumbnail":1846,"updatedAt":825,"__hash__":3528},"articles\u002Farticles\u002Flaravel-i18n-json-dump.md","Laravelで多言語用のJSONを出力するコマンドを作る",{"type":10,"value":1852,"toc":3509},[1853,1868,1898,1905,1920,1933,1937,1940,1946,2019,2026,2029,2037,2040,2042,2049,2052,2055,2065,2071,2074,2077,2080,2086,2093,2302,2309,2315,2318,2321,2328,2490,2517,2520,2523,2621,2641,2645,2679,2694,2697,2712,2726,2729,2732,3060,3063,3076,3084,3089,3092,3298,3496,3503,3506],[13,1854,1855,1856,1859,1860,1863,1864,1867],{},"こんにちはjunです。個人開発で多言語対応のLaravelアプリを作っています。多言語では各種言語の単語・文章のマッピング（辞書）をする配列・JSONを作成し、レンダリング時にメソッドを使用して文字を表示します。Laravelでは",[41,1857,1858],{},"resources\u002Flang"," 配下に",[41,1861,1862],{},"en","、",[41,1865,1866],{},"ja","のようなディレクトリを作成し、その中に言語のマッピングをします。大体は以下のような配列を作ります。",[34,1869,1872],{"className":870,"code":1870,"filename":1871,"language":872,"meta":43,"style":43},"\u003C?php\nreturn [\n    'login'=>'ログイン',\n    'logout'=>'ログアウト'\n]\n\n","resources\u002Flang\u002Fja\u002Fwords.php",[41,1873,1874,1879,1884,1889,1894],{"__ignoreMap":43},[67,1875,1876],{"class":69,"line":70},[67,1877,1878],{},"\u003C?php\n",[67,1880,1881],{"class":69,"line":91},[67,1882,1883],{},"return [\n",[67,1885,1886],{"class":69,"line":98},[67,1887,1888],{},"    'login'=>'ログイン',\n",[67,1890,1891],{"class":69,"line":107},[67,1892,1893],{},"    'logout'=>'ログアウト'\n",[67,1895,1896],{"class":69,"line":115},[67,1897,1100],{},[13,1899,1900,1901,1904],{},"そしてビューファイルなどで",[41,1902,1903],{},"__('words.login')","のように使用します。",[1906,1907,1911,1912,1915,1916,1919],"div",{"className":1908},[1909,1910],"alert","alert-info","\n多言語のメソッドが",[41,1913,1914],{},"__","みたいな名称である理由としては、使いまくるので簡単な名称になっています。Vueとかでは",[41,1917,1918],{},"$t()","みたいなものを使用します。\n",[13,1921,1922,1923,1925,1926,1863,1929,1932],{},"上記のようなPHPファイルを作成してもできますが、",[41,1924,1858],{}," 直下に",[41,1927,1928],{},"en.json",[41,1930,1931],{},"ja.json","のようなJSONファイルを作成しても、多言語メソッドで呼び出せます。",[51,1934,1936],{"id":1935},"jsonの弱点","JSONの弱点",[13,1938,1939],{},"JSONで作るメリットはマッピングのデータを他のアプリでも利用できることです。例えば、Vue・ReactではVuei18n、react-i18nextなどを使用します。同じように多言語メソッドを使用します。その際のマッピングデータとしてJSONを使用します。であればJSONファイルを作っておくことで、Vueや外部へマッピングデータを提供しやすくなります。",[13,1941,1942,1943,1945],{},"ただしJSONで作成するとLaravel側で",[41,1944,1903],{},"のような呼び出しができません。この時JSONは以下のようになっています。",[34,1947,1951],{"className":1948,"code":1949,"language":1950,"meta":43,"style":43},"language-json shiki shiki-themes material-theme-ocean","{\n    \"words\":{\n        \"login\":\"ログイン\",\n        \"logout\":\"ログアウト\"\n    }\n}\n","json",[41,1952,1953,1957,1970,1992,2010,2015],{"__ignoreMap":43},[67,1954,1955],{"class":69,"line":70},[67,1956,408],{"class":77},[67,1958,1959,1962,1965,1967],{"class":69,"line":91},[67,1960,1961],{"class":77},"    \"",[67,1963,1964],{"class":246},"words",[67,1966,315],{"class":77},[67,1968,1969],{"class":77},":{\n",[67,1971,1972,1975,1979,1981,1983,1985,1988,1990],{"class":69,"line":98},[67,1973,1974],{"class":77},"        \"",[67,1976,1978],{"class":1977},"s5Dmg","login",[67,1980,315],{"class":77},[67,1982,78],{"class":77},[67,1984,315],{"class":77},[67,1986,1987],{"class":84},"ログイン",[67,1989,315],{"class":77},[67,1991,318],{"class":77},[67,1993,1994,1996,1999,2001,2003,2005,2008],{"class":69,"line":107},[67,1995,1974],{"class":77},[67,1997,1998],{"class":1977},"logout",[67,2000,315],{"class":77},[67,2002,78],{"class":77},[67,2004,315],{"class":77},[67,2006,2007],{"class":84},"ログアウト",[67,2009,88],{"class":77},[67,2011,2012],{"class":69,"line":115},[67,2013,2014],{"class":77},"    }\n",[67,2016,2017],{"class":69,"line":126},[67,2018,572],{"class":77},[13,2020,2021,2022,2025],{},"すべて一次元にしてしまうと後で混乱してしまうので、カスケードさせておくと良いです。Vuei18nでは",[41,2023,2024],{},"$t('words.login')","で呼び出せますが、なぜかLaravelではJSONのカスケード配下のキーを呼び出すことができません。",[13,2027,2028],{},"そのため",[1394,2030,2031,2034],{},[1293,2032,2033],{},"Laravel以外のアプリへの提供→JSON",[1293,2035,2036],{},"Laravelの多言語対応→PHP",[13,2038,2039],{},"という２重管理でそれぞれ設定しないといけなくなり、非効率的です。",[51,2041,1199],{"id":1199},[13,2043,2044,2045,2048],{},"解決方法としてはPHPで作成した配列をJSONにダンプすることです。そうすることでPHPファイルとJSONの言語ファイルを作成することができます。そこで今回の記事ではPHPの言語ファイルをカスケードさせたJSONに出力するような",[41,2046,2047],{},"artisan","コマンドを作ってみたいと思います。",[51,2050,2051],{"id":2051},"コマンドの実装",[599,2053,2054],{"id":2054},"対応する言語ディレクトリ",[13,2056,2057,2058,1859,2060,1863,2062,2064],{},"まずはLaravelの言語ファイルのドキュメントの通り、",[41,2059,1858],{},[41,2061,1862],{},[41,2063,1866],{},"のようなディレクトリを作成しておきましょう。今回は英語と日本語にしておきます。",[34,2066,2069],{"className":2067,"code":2068,"language":39},[37],"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",[41,2070,2068],{"__ignoreMap":43},[13,2072,2073],{},"そしてそのディレクトリごとにphpファイルを分けます。とりあえずこのようにしておきます。",[599,2075,2076],{"id":2076},"コマンドのファイルを作成",[13,2078,2079],{},"それではカスタムのコマンドを作成しましょう。",[34,2081,2084],{"className":2082,"code":2083,"language":39},[37],"php artisan make:command Dumplang\n",[41,2085,2083],{"__ignoreMap":43},[13,2087,2088,2089,2092],{},"コマンドもartisanで作成できます。",[41,2090,2091],{},"app\u002FConsole\u002FCommands","というディレクトリが作成され、そこにファイルが作成されます。",[34,2094,2096],{"className":870,"code":2095,"language":872,"meta":43,"style":43},"\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",[41,2097,2098,2102,2106,2111,2115,2120,2124,2129,2133,2138,2143,2148,2153,2158,2163,2167,2171,2176,2180,2184,2188,2193,2197,2201,2206,2210,2216,2221,2227,2232,2238,2243,2248,2253,2259,2264,2270,2275,2281,2286,2292,2297],{"__ignoreMap":43},[67,2099,2100],{"class":69,"line":70},[67,2101,1878],{},[67,2103,2104],{"class":69,"line":91},[67,2105,95],{"emptyLinePlaceholder":94},[67,2107,2108],{"class":69,"line":98},[67,2109,2110],{},"namespace App\\Console\\Commands;\n",[67,2112,2113],{"class":69,"line":107},[67,2114,95],{"emptyLinePlaceholder":94},[67,2116,2117],{"class":69,"line":115},[67,2118,2119],{},"use Illuminate\\Console\\Command;\n",[67,2121,2122],{"class":69,"line":126},[67,2123,95],{"emptyLinePlaceholder":94},[67,2125,2126],{"class":69,"line":137},[67,2127,2128],{},"class Dumblang extends Command\n",[67,2130,2131],{"class":69,"line":145},[67,2132,408],{},[67,2134,2135],{"class":69,"line":154},[67,2136,2137],{},"    \u002F**\n",[67,2139,2140],{"class":69,"line":165},[67,2141,2142],{},"     * The name and signature of the console command.\n",[67,2144,2145],{"class":69,"line":173},[67,2146,2147],{},"     *\n",[67,2149,2150],{"class":69,"line":181},[67,2151,2152],{},"     * @var string\n",[67,2154,2155],{"class":69,"line":189},[67,2156,2157],{},"     *\u002F\n",[67,2159,2160],{"class":69,"line":197},[67,2161,2162],{},"    protected $signature = 'lang:dump';\n",[67,2164,2165],{"class":69,"line":205},[67,2166,95],{"emptyLinePlaceholder":94},[67,2168,2169],{"class":69,"line":213},[67,2170,2137],{},[67,2172,2173],{"class":69,"line":468},[67,2174,2175],{},"     * The console command description.\n",[67,2177,2178],{"class":69,"line":498},[67,2179,2147],{},[67,2181,2182],{"class":69,"line":504},[67,2183,2152],{},[67,2185,2186],{"class":69,"line":522},[67,2187,2157],{},[67,2189,2190],{"class":69,"line":529},[67,2191,2192],{},"    protected $description = 'Convert each php lang files to JSON files.';\n",[67,2194,2195],{"class":69,"line":541},[67,2196,95],{"emptyLinePlaceholder":94},[67,2198,2199],{"class":69,"line":558},[67,2200,2137],{},[67,2202,2203],{"class":69,"line":563},[67,2204,2205],{},"     * Create a new command instance.\n",[67,2207,2208],{"class":69,"line":569},[67,2209,2147],{},[67,2211,2213],{"class":69,"line":2212},26,[67,2214,2215],{},"     * @return void\n",[67,2217,2219],{"class":69,"line":2218},27,[67,2220,2157],{},[67,2222,2224],{"class":69,"line":2223},28,[67,2225,2226],{},"    public function __construct()\n",[67,2228,2229],{"class":69,"line":4},[67,2230,2231],{},"    {\n",[67,2233,2235],{"class":69,"line":2234},30,[67,2236,2237],{},"        parent::__construct();\n",[67,2239,2241],{"class":69,"line":2240},31,[67,2242,2014],{},[67,2244,2246],{"class":69,"line":2245},32,[67,2247,95],{"emptyLinePlaceholder":94},[67,2249,2251],{"class":69,"line":2250},33,[67,2252,2137],{},[67,2254,2256],{"class":69,"line":2255},34,[67,2257,2258],{},"     * Execute the console command.\n",[67,2260,2262],{"class":69,"line":2261},35,[67,2263,2147],{},[67,2265,2267],{"class":69,"line":2266},36,[67,2268,2269],{},"     * @return int\n",[67,2271,2273],{"class":69,"line":2272},37,[67,2274,2157],{},[67,2276,2278],{"class":69,"line":2277},38,[67,2279,2280],{},"    public function handle()\n",[67,2282,2284],{"class":69,"line":2283},39,[67,2285,2231],{},[67,2287,2289],{"class":69,"line":2288},40,[67,2290,2291],{},"        \n",[67,2293,2295],{"class":69,"line":2294},41,[67,2296,2014],{},[67,2298,2300],{"class":69,"line":2299},42,[67,2301,572],{},[13,2303,2304,2305,2308],{},"まずは上記のように、シグネチャと説明を記述します。シグネチャでは",[41,2306,2307],{},"lang:dump"," とすると",[34,2310,2313],{"className":2311,"code":2312,"language":39},[37],"php artisan lang:dump\n",[41,2314,2312],{"__ignoreMap":43},[13,2316,2317],{},"と入力するとこのコマンドを実行できます。",[599,2319,2320],{"id":2320},"言語ディレクトリからファイルを読み込む",[13,2322,2323,2324,2327],{},"コマンドの内容は",[41,2325,2326],{},"handle()","に記述します。全体は以下の通りです。",[34,2329,2331],{"className":870,"code":2330,"language":872,"meta":43,"style":43},"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",[41,2332,2333,2338,2343,2347,2352,2356,2361,2366,2371,2376,2381,2386,2391,2395,2400,2405,2410,2414,2419,2424,2429,2434,2439,2444,2448,2453,2458,2463,2468,2473,2477,2481,2486],{"__ignoreMap":43},[67,2334,2335],{"class":69,"line":70},[67,2336,2337],{},"use Illuminate\\Support\\Facades\\File;\n",[67,2339,2340],{"class":69,"line":91},[67,2341,2342],{},"use Illuminate\\Support\\Facades\\Lang;\n",[67,2344,2345],{"class":69,"line":98},[67,2346,95],{"emptyLinePlaceholder":94},[67,2348,2349],{"class":69,"line":107},[67,2350,2351],{},"public function handle()\n",[67,2353,2354],{"class":69,"line":115},[67,2355,408],{},[67,2357,2358],{"class":69,"line":126},[67,2359,2360],{},"    $suports = [\"ja\",\"en\"];\n",[67,2362,2363],{"class":69,"line":137},[67,2364,2365],{},"    \n",[67,2367,2368],{"class":69,"line":145},[67,2369,2370],{},"    foreach($suports as $lng){\n",[67,2372,2373],{"class":69,"line":154},[67,2374,2375],{},"        $langdir = resource_path('lang\u002F'.$lng);\n",[67,2377,2378],{"class":69,"line":165},[67,2379,2380],{},"        if(is_dir($langdir)){\n",[67,2382,2383],{"class":69,"line":173},[67,2384,2385],{},"            $files = scandir($langdir);\n",[67,2387,2388],{"class":69,"line":181},[67,2389,2390],{},"            if($files === false) continue;\n",[67,2392,2393],{"class":69,"line":189},[67,2394,95],{"emptyLinePlaceholder":94},[67,2396,2397],{"class":69,"line":197},[67,2398,2399],{},"            $files = array_filter($files,function($f){\n",[67,2401,2402],{"class":69,"line":205},[67,2403,2404],{},"                return strpos($f,'.php') !== false;\n",[67,2406,2407],{"class":69,"line":213},[67,2408,2409],{},"            });\n",[67,2411,2412],{"class":69,"line":468},[67,2413,95],{"emptyLinePlaceholder":94},[67,2415,2416],{"class":69,"line":498},[67,2417,2418],{},"            $trans = [];\n",[67,2420,2421],{"class":69,"line":504},[67,2422,2423],{},"            foreach($files as $f){\n",[67,2425,2426],{"class":69,"line":522},[67,2427,2428],{},"                $content_key = str_replace('.php','',$f);\n",[67,2430,2431],{"class":69,"line":529},[67,2432,2433],{},"                $content = include resource_path(\"lang\u002F$lng\u002F$f\");\n",[67,2435,2436],{"class":69,"line":541},[67,2437,2438],{},"                $trans[$content_key] = $content;\n",[67,2440,2441],{"class":69,"line":558},[67,2442,2443],{},"            }\n",[67,2445,2446],{"class":69,"line":563},[67,2447,95],{"emptyLinePlaceholder":94},[67,2449,2450],{"class":69,"line":569},[67,2451,2452],{},"            $json = json_encode($trans,JSON_UNESCAPED_UNICODE);\n",[67,2454,2455],{"class":69,"line":2212},[67,2456,2457],{},"            File::put(resource_path('lang\u002F'.$lng.'.json'),$json);\n",[67,2459,2460],{"class":69,"line":2218},[67,2461,2462],{},"            File::put(base_path('nuxt\u002Flang\u002F'.$lng.'.json'),$json);\n",[67,2464,2465],{"class":69,"line":2223},[67,2466,2467],{},"        }else{\n",[67,2469,2470],{"class":69,"line":4},[67,2471,2472],{},"            print(\"Lang directory for ${$lng} dose not exisits\");\n",[67,2474,2475],{"class":69,"line":2234},[67,2476,566],{},[67,2478,2479],{"class":69,"line":2240},[67,2480,2014],{},[67,2482,2483],{"class":69,"line":2245},[67,2484,2485],{},"    return Command::SUCCESS;\n",[67,2487,2488],{"class":69,"line":2250},[67,2489,572],{},[34,2491,2493],{"className":870,"code":2492,"language":872,"meta":43,"style":43},"$suports = [\"ja\",\"en\"];\n\nforeach($suports as $lng){\n\n}\n",[41,2494,2495,2500,2504,2509,2513],{"__ignoreMap":43},[67,2496,2497],{"class":69,"line":70},[67,2498,2499],{},"$suports = [\"ja\",\"en\"];\n",[67,2501,2502],{"class":69,"line":91},[67,2503,95],{"emptyLinePlaceholder":94},[67,2505,2506],{"class":69,"line":98},[67,2507,2508],{},"foreach($suports as $lng){\n",[67,2510,2511],{"class":69,"line":107},[67,2512,95],{"emptyLinePlaceholder":94},[67,2514,2515],{"class":69,"line":115},[67,2516,572],{},[13,2518,2519],{},"まずは取得予定の言語の配列を用意します。それを再帰的に処理します。",[13,2521,2522],{},"元となる言語ディレクトリからファイルの一覧を取得します。",[34,2524,2526],{"className":870,"code":2525,"language":872,"meta":43,"style":43},"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",[41,2527,2528,2532,2537,2542,2547,2552,2556,2561,2566,2571,2575,2580,2585,2590,2595,2600,2604,2608,2613,2617],{"__ignoreMap":43},[67,2529,2530],{"class":69,"line":70},[67,2531,2508],{},[67,2533,2534],{"class":69,"line":91},[67,2535,2536],{},"    $langdir = resource_path('lang\u002F'.$lng);\n",[67,2538,2539],{"class":69,"line":98},[67,2540,2541],{},"    if(is_dir($langdir)){\n",[67,2543,2544],{"class":69,"line":107},[67,2545,2546],{},"        $files = scandir($langdir);\n",[67,2548,2549],{"class":69,"line":115},[67,2550,2551],{},"        if($files === false) continue;\n",[67,2553,2554],{"class":69,"line":126},[67,2555,95],{"emptyLinePlaceholder":94},[67,2557,2558],{"class":69,"line":137},[67,2559,2560],{},"        $files = array_filter($files,function($f){\n",[67,2562,2563],{"class":69,"line":145},[67,2564,2565],{},"            return strpos($f,'.php') !== false;\n",[67,2567,2568],{"class":69,"line":154},[67,2569,2570],{},"        });\n",[67,2572,2573],{"class":69,"line":165},[67,2574,95],{"emptyLinePlaceholder":94},[67,2576,2577],{"class":69,"line":173},[67,2578,2579],{},"        $trans = [];\n",[67,2581,2582],{"class":69,"line":181},[67,2583,2584],{},"        foreach($files as $f){\n",[67,2586,2587],{"class":69,"line":189},[67,2588,2589],{},"            $content_key = str_replace('.php','',$f);\n",[67,2591,2592],{"class":69,"line":197},[67,2593,2594],{},"            $content = include resource_path(\"lang\u002F$lng\u002F$f\");\n",[67,2596,2597],{"class":69,"line":205},[67,2598,2599],{},"            $trans[$content_key] = $content;\n",[67,2601,2602],{"class":69,"line":213},[67,2603,566],{},[67,2605,2606],{"class":69,"line":468},[67,2607,95],{"emptyLinePlaceholder":94},[67,2609,2610],{"class":69,"line":498},[67,2611,2612],{},"        \u002F\u002F ....\n",[67,2614,2615],{"class":69,"line":504},[67,2616,2014],{},[67,2618,2619],{"class":69,"line":522},[67,2620,572],{},[13,2622,2623,2626,2627,2630,2631,2633,2634,2636,2637,2640],{},[41,2624,2625],{},"resource_path('lang\u002F'.$lng)","で先程の言語ディレクトリのパスを取得し、",[41,2628,2629],{},"scandir()","を用いて内部のファイルを配列で取得します。",[41,2632,2629],{},"はphp以外のファイルや",[41,2635,427],{},"みたいなSELinuxが勝手に作る謎ファイルも読み取ってしまうので、",[41,2638,2639],{},"array_filter()","を用いてフィルターします。",[599,2642,2644],{"id":2643},"一つの配列に打ち込んでjson化する","一つの配列に打ち込んでJSON化する",[34,2646,2648],{"className":870,"code":2647,"language":872,"meta":43,"style":43},"$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",[41,2649,2650,2655,2660,2665,2670,2675],{"__ignoreMap":43},[67,2651,2652],{"class":69,"line":70},[67,2653,2654],{},"$trans = [];\n",[67,2656,2657],{"class":69,"line":91},[67,2658,2659],{},"foreach($files as $f){\n",[67,2661,2662],{"class":69,"line":98},[67,2663,2664],{},"    $content_key = str_replace('.php','',$f);\n",[67,2666,2667],{"class":69,"line":107},[67,2668,2669],{},"    $content = include resource_path(\"lang\u002F$lng\u002F$f\");\n",[67,2671,2672],{"class":69,"line":115},[67,2673,2674],{},"    $trans[$content_key] = $content;\n",[67,2676,2677],{"class":69,"line":126},[67,2678,572],{},[13,2680,2681,2682,2685,2686,2689,2690,2693],{},"JSONではphpファイル名を一次キーとして利用したいので ",[41,2683,2684],{},"str_replace('.php','',$f)","でファイル名を取得します。",[41,2687,2688],{},"include resource_path(\"lang\u002F$lng\u002F$f\")","でphpファイルの記述を取得ます。そしてJSONにする配列に、ファイル名をキーとして打ち込みます。",[41,2691,2692],{},"$trans[$content_key] = $content;"," それをスキャンした言語PHPファイル全てに行います。",[599,2695,2696],{"id":2696},"ファイルを出力",[34,2698,2700],{"className":870,"code":2699,"language":872,"meta":43,"style":43},"$json = json_encode($trans,JSON_UNESCAPED_UNICODE);\nFile::put(resource_path('lang\u002F'.$lng.'.json'),$json);\n",[41,2701,2702,2707],{"__ignoreMap":43},[67,2703,2704],{"class":69,"line":70},[67,2705,2706],{},"$json = json_encode($trans,JSON_UNESCAPED_UNICODE);\n",[67,2708,2709],{"class":69,"line":91},[67,2710,2711],{},"File::put(resource_path('lang\u002F'.$lng.'.json'),$json);\n",[13,2713,2714,2715,2718,2719,2721,2722,2725],{},"そして１つにまとめた配列を",[41,2716,2717],{},"json_encode","をしておき、それを",[41,2720,1858],{}," 配下します。その際には",[41,2723,2724],{},"言語名.json","となるようにしておきます。",[51,2727,2728],{"id":2728},"全容と実行",[13,2730,2731],{},"コードは以下の通りです。",[34,2733,2735],{"className":870,"code":2734,"language":872,"meta":43,"style":43},"\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",[41,2736,2737,2741,2745,2749,2753,2757,2761,2765,2769,2773,2777,2781,2785,2789,2793,2797,2801,2805,2809,2813,2817,2821,2825,2829,2833,2837,2841,2845,2849,2853,2857,2861,2865,2869,2873,2877,2881,2885,2889,2893,2897,2901,2906,2911,2917,2923,2929,2935,2941,2946,2952,2958,2964,2969,2975,2981,2987,2993,2999,3005,3010,3016,3022,3028,3034,3039,3044,3050,3055],{"__ignoreMap":43},[67,2738,2739],{"class":69,"line":70},[67,2740,1878],{},[67,2742,2743],{"class":69,"line":91},[67,2744,95],{"emptyLinePlaceholder":94},[67,2746,2747],{"class":69,"line":98},[67,2748,2110],{},[67,2750,2751],{"class":69,"line":107},[67,2752,95],{"emptyLinePlaceholder":94},[67,2754,2755],{"class":69,"line":115},[67,2756,2119],{},[67,2758,2759],{"class":69,"line":126},[67,2760,2337],{},[67,2762,2763],{"class":69,"line":137},[67,2764,2342],{},[67,2766,2767],{"class":69,"line":145},[67,2768,95],{"emptyLinePlaceholder":94},[67,2770,2771],{"class":69,"line":154},[67,2772,2128],{},[67,2774,2775],{"class":69,"line":165},[67,2776,408],{},[67,2778,2779],{"class":69,"line":173},[67,2780,2137],{},[67,2782,2783],{"class":69,"line":181},[67,2784,2142],{},[67,2786,2787],{"class":69,"line":189},[67,2788,2147],{},[67,2790,2791],{"class":69,"line":197},[67,2792,2152],{},[67,2794,2795],{"class":69,"line":205},[67,2796,2157],{},[67,2798,2799],{"class":69,"line":213},[67,2800,2162],{},[67,2802,2803],{"class":69,"line":468},[67,2804,95],{"emptyLinePlaceholder":94},[67,2806,2807],{"class":69,"line":498},[67,2808,2137],{},[67,2810,2811],{"class":69,"line":504},[67,2812,2175],{},[67,2814,2815],{"class":69,"line":522},[67,2816,2147],{},[67,2818,2819],{"class":69,"line":529},[67,2820,2152],{},[67,2822,2823],{"class":69,"line":541},[67,2824,2157],{},[67,2826,2827],{"class":69,"line":558},[67,2828,2192],{},[67,2830,2831],{"class":69,"line":563},[67,2832,95],{"emptyLinePlaceholder":94},[67,2834,2835],{"class":69,"line":569},[67,2836,2137],{},[67,2838,2839],{"class":69,"line":2212},[67,2840,2205],{},[67,2842,2843],{"class":69,"line":2218},[67,2844,2147],{},[67,2846,2847],{"class":69,"line":2223},[67,2848,2215],{},[67,2850,2851],{"class":69,"line":4},[67,2852,2157],{},[67,2854,2855],{"class":69,"line":2234},[67,2856,2226],{},[67,2858,2859],{"class":69,"line":2240},[67,2860,2231],{},[67,2862,2863],{"class":69,"line":2245},[67,2864,2237],{},[67,2866,2867],{"class":69,"line":2250},[67,2868,2014],{},[67,2870,2871],{"class":69,"line":2255},[67,2872,95],{"emptyLinePlaceholder":94},[67,2874,2875],{"class":69,"line":2261},[67,2876,2137],{},[67,2878,2879],{"class":69,"line":2266},[67,2880,2258],{},[67,2882,2883],{"class":69,"line":2272},[67,2884,2147],{},[67,2886,2887],{"class":69,"line":2277},[67,2888,2269],{},[67,2890,2891],{"class":69,"line":2283},[67,2892,2157],{},[67,2894,2895],{"class":69,"line":2288},[67,2896,2280],{},[67,2898,2899],{"class":69,"line":2294},[67,2900,2231],{},[67,2902,2903],{"class":69,"line":2299},[67,2904,2905],{},"        $suports = config('app.support_langs');\n",[67,2907,2909],{"class":69,"line":2908},43,[67,2910,2291],{},[67,2912,2914],{"class":69,"line":2913},44,[67,2915,2916],{},"        foreach($suports as $lng){\n",[67,2918,2920],{"class":69,"line":2919},45,[67,2921,2922],{},"            $langdir = resource_path('lang\u002F'.$lng);\n",[67,2924,2926],{"class":69,"line":2925},46,[67,2927,2928],{},"            if(is_dir($langdir)){\n",[67,2930,2932],{"class":69,"line":2931},47,[67,2933,2934],{},"                $files = scandir($langdir);\n",[67,2936,2938],{"class":69,"line":2937},48,[67,2939,2940],{},"                if($files === false) continue;\n",[67,2942,2944],{"class":69,"line":2943},49,[67,2945,95],{"emptyLinePlaceholder":94},[67,2947,2949],{"class":69,"line":2948},50,[67,2950,2951],{},"                $files = array_filter($files,function($f){\n",[67,2953,2955],{"class":69,"line":2954},51,[67,2956,2957],{},"                    return strpos($f,'.php') !== false;\n",[67,2959,2961],{"class":69,"line":2960},52,[67,2962,2963],{},"                });\n",[67,2965,2967],{"class":69,"line":2966},53,[67,2968,95],{"emptyLinePlaceholder":94},[67,2970,2972],{"class":69,"line":2971},54,[67,2973,2974],{},"                $trans = [];\n",[67,2976,2978],{"class":69,"line":2977},55,[67,2979,2980],{},"                foreach($files as $f){\n",[67,2982,2984],{"class":69,"line":2983},56,[67,2985,2986],{},"                    $content_key = str_replace('.php','',$f);\n",[67,2988,2990],{"class":69,"line":2989},57,[67,2991,2992],{},"                    $content = include resource_path(\"lang\u002F$lng\u002F$f\");\n",[67,2994,2996],{"class":69,"line":2995},58,[67,2997,2998],{},"                    $trans[$content_key] = $content;\n",[67,3000,3002],{"class":69,"line":3001},59,[67,3003,3004],{},"                }\n",[67,3006,3008],{"class":69,"line":3007},60,[67,3009,95],{"emptyLinePlaceholder":94},[67,3011,3013],{"class":69,"line":3012},61,[67,3014,3015],{},"                $json = json_encode($trans,JSON_UNESCAPED_UNICODE);\n",[67,3017,3019],{"class":69,"line":3018},62,[67,3020,3021],{},"                File::put(resource_path('lang\u002F'.$lng.'.json'),$json);\n",[67,3023,3025],{"class":69,"line":3024},63,[67,3026,3027],{},"            }else{\n",[67,3029,3031],{"class":69,"line":3030},64,[67,3032,3033],{},"                print(\"Lang directory for ${$lng} dose not exisits\");\n",[67,3035,3037],{"class":69,"line":3036},65,[67,3038,2443],{},[67,3040,3042],{"class":69,"line":3041},66,[67,3043,566],{},[67,3045,3047],{"class":69,"line":3046},67,[67,3048,3049],{},"        return Command::SUCCESS;\n",[67,3051,3053],{"class":69,"line":3052},68,[67,3054,2014],{},[67,3056,3058],{"class":69,"line":3057},69,[67,3059,572],{},[13,3061,3062],{},"ちょっと気をつける点としては",[1394,3064,3065,3071],{},[1293,3066,3067,3070],{},[41,3068,3069],{},"is_dir($langdir)"," で言語ディレクトリの存在チェック",[1293,3072,3073,3075],{},[41,3074,2629],{}," で取得したファイルをフィルタする",[13,3077,3078,3079,1863,3081,3083],{},"実行してみると",[41,3080,1931],{},[41,3082,1928],{},"というのが作成され、みてみると",[34,3085,3087],{"className":3086,"code":2068,"language":39},[37],[41,3088,2068],{"__ignoreMap":43},[13,3090,3091],{},"↓",[34,3093,3095],{"className":1948,"code":3094,"filename":1931,"language":1950,"meta":43,"style":43},"{\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",[41,3096,3097,3101,3112,3130,3148,3153,3163,3183,3201,3205,3216,3236,3247,3268,3286,3290,3294],{"__ignoreMap":43},[67,3098,3099],{"class":69,"line":70},[67,3100,408],{"class":77},[67,3102,3103,3105,3108,3110],{"class":69,"line":91},[67,3104,1961],{"class":77},[67,3106,3107],{"class":246},"auth",[67,3109,315],{"class":77},[67,3111,1969],{"class":77},[67,3113,3114,3116,3118,3120,3122,3124,3126,3128],{"class":69,"line":98},[67,3115,1974],{"class":77},[67,3117,1978],{"class":1977},[67,3119,315],{"class":77},[67,3121,78],{"class":77},[67,3123,315],{"class":77},[67,3125,1987],{"class":84},[67,3127,315],{"class":77},[67,3129,318],{"class":77},[67,3131,3132,3134,3136,3138,3140,3142,3144,3146],{"class":69,"line":107},[67,3133,1974],{"class":77},[67,3135,1998],{"class":1977},[67,3137,315],{"class":77},[67,3139,78],{"class":77},[67,3141,315],{"class":77},[67,3143,2007],{"class":84},[67,3145,315],{"class":77},[67,3147,318],{"class":77},[67,3149,3150],{"class":69,"line":115},[67,3151,3152],{"class":77},"    },\n",[67,3154,3155,3157,3159,3161],{"class":69,"line":126},[67,3156,1961],{"class":77},[67,3158,1964],{"class":246},[67,3160,315],{"class":77},[67,3162,1969],{"class":77},[67,3164,3165,3167,3170,3172,3174,3176,3179,3181],{"class":69,"line":137},[67,3166,1974],{"class":77},[67,3168,3169],{"class":1977},"save",[67,3171,315],{"class":77},[67,3173,78],{"class":77},[67,3175,315],{"class":77},[67,3177,3178],{"class":84},"保存",[67,3180,315],{"class":77},[67,3182,318],{"class":77},[67,3184,3185,3187,3190,3192,3194,3196,3199],{"class":69,"line":145},[67,3186,1974],{"class":77},[67,3188,3189],{"class":1977},"update",[67,3191,315],{"class":77},[67,3193,78],{"class":77},[67,3195,315],{"class":77},[67,3197,3198],{"class":84},"更新",[67,3200,88],{"class":77},[67,3202,3203],{"class":69,"line":154},[67,3204,3152],{"class":77},[67,3206,3207,3209,3212,3214],{"class":69,"line":165},[67,3208,1961],{"class":77},[67,3210,3211],{"class":246},"exceptions",[67,3213,315],{"class":77},[67,3215,1969],{"class":77},[67,3217,3218,3220,3223,3225,3227,3229,3232,3234],{"class":69,"line":173},[67,3219,1974],{"class":77},[67,3221,3222],{"class":1977},"401",[67,3224,315],{"class":77},[67,3226,78],{"class":77},[67,3228,315],{"class":77},[67,3230,3231],{"class":84},"ログインしてください。",[67,3233,315],{"class":77},[67,3235,318],{"class":77},[67,3237,3238,3240,3243,3245],{"class":69,"line":181},[67,3239,1974],{"class":77},[67,3241,3242],{"class":1977},"model",[67,3244,315],{"class":77},[67,3246,1969],{"class":77},[67,3248,3249,3252,3255,3257,3259,3261,3264,3266],{"class":69,"line":189},[67,3250,3251],{"class":77},"            \"",[67,3253,3254],{"class":462},"403",[67,3256,315],{"class":77},[67,3258,78],{"class":77},[67,3260,315],{"class":77},[67,3262,3263],{"class":84},"このデータにアクセスできません。",[67,3265,315],{"class":77},[67,3267,318],{"class":77},[67,3269,3270,3272,3275,3277,3279,3281,3284],{"class":69,"line":197},[67,3271,3251],{"class":77},[67,3273,3274],{"class":462},"404",[67,3276,315],{"class":77},[67,3278,78],{"class":77},[67,3280,315],{"class":77},[67,3282,3283],{"class":84},"対応するデータが見つかりません。",[67,3285,88],{"class":77},[67,3287,3288],{"class":69,"line":205},[67,3289,566],{"class":77},[67,3291,3292],{"class":69,"line":213},[67,3293,2014],{"class":77},[67,3295,3296],{"class":69,"line":468},[67,3297,572],{"class":77},[34,3299,3301],{"className":1948,"code":3300,"filename":1928,"language":1950,"meta":43,"style":43},"{\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",[41,3302,3303,3307,3317,3336,3355,3359,3369,3388,3405,3409,3419,3438,3448,3467,3484,3488,3492],{"__ignoreMap":43},[67,3304,3305],{"class":69,"line":70},[67,3306,408],{"class":77},[67,3308,3309,3311,3313,3315],{"class":69,"line":91},[67,3310,1961],{"class":77},[67,3312,3107],{"class":246},[67,3314,315],{"class":77},[67,3316,1969],{"class":77},[67,3318,3319,3321,3323,3325,3327,3329,3332,3334],{"class":69,"line":98},[67,3320,1974],{"class":77},[67,3322,1978],{"class":1977},[67,3324,315],{"class":77},[67,3326,78],{"class":77},[67,3328,315],{"class":77},[67,3330,3331],{"class":84},"Login",[67,3333,315],{"class":77},[67,3335,318],{"class":77},[67,3337,3338,3340,3342,3344,3346,3348,3351,3353],{"class":69,"line":107},[67,3339,1974],{"class":77},[67,3341,1998],{"class":1977},[67,3343,315],{"class":77},[67,3345,78],{"class":77},[67,3347,315],{"class":77},[67,3349,3350],{"class":84},"Logout",[67,3352,315],{"class":77},[67,3354,318],{"class":77},[67,3356,3357],{"class":69,"line":115},[67,3358,3152],{"class":77},[67,3360,3361,3363,3365,3367],{"class":69,"line":126},[67,3362,1961],{"class":77},[67,3364,1964],{"class":246},[67,3366,315],{"class":77},[67,3368,1969],{"class":77},[67,3370,3371,3373,3375,3377,3379,3381,3384,3386],{"class":69,"line":137},[67,3372,1974],{"class":77},[67,3374,3169],{"class":1977},[67,3376,315],{"class":77},[67,3378,78],{"class":77},[67,3380,315],{"class":77},[67,3382,3383],{"class":84},"Saving",[67,3385,315],{"class":77},[67,3387,318],{"class":77},[67,3389,3390,3392,3394,3396,3398,3400,3403],{"class":69,"line":145},[67,3391,1974],{"class":77},[67,3393,3189],{"class":1977},[67,3395,315],{"class":77},[67,3397,78],{"class":77},[67,3399,315],{"class":77},[67,3401,3402],{"class":84},"Updating",[67,3404,88],{"class":77},[67,3406,3407],{"class":69,"line":154},[67,3408,3152],{"class":77},[67,3410,3411,3413,3415,3417],{"class":69,"line":165},[67,3412,1961],{"class":77},[67,3414,3211],{"class":246},[67,3416,315],{"class":77},[67,3418,1969],{"class":77},[67,3420,3421,3423,3425,3427,3429,3431,3434,3436],{"class":69,"line":173},[67,3422,1974],{"class":77},[67,3424,3222],{"class":1977},[67,3426,315],{"class":77},[67,3428,78],{"class":77},[67,3430,315],{"class":77},[67,3432,3433],{"class":84},"Please login.",[67,3435,315],{"class":77},[67,3437,318],{"class":77},[67,3439,3440,3442,3444,3446],{"class":69,"line":181},[67,3441,1974],{"class":77},[67,3443,3242],{"class":1977},[67,3445,315],{"class":77},[67,3447,1969],{"class":77},[67,3449,3450,3452,3454,3456,3458,3460,3463,3465],{"class":69,"line":189},[67,3451,3251],{"class":77},[67,3453,3254],{"class":462},[67,3455,315],{"class":77},[67,3457,78],{"class":77},[67,3459,315],{"class":77},[67,3461,3462],{"class":84},"You can not access to this data.",[67,3464,315],{"class":77},[67,3466,318],{"class":77},[67,3468,3469,3471,3473,3475,3477,3479,3482],{"class":69,"line":197},[67,3470,3251],{"class":77},[67,3472,3274],{"class":462},[67,3474,315],{"class":77},[67,3476,78],{"class":77},[67,3478,315],{"class":77},[67,3480,3481],{"class":84},"The data you request is not found.",[67,3483,88],{"class":77},[67,3485,3486],{"class":69,"line":205},[67,3487,566],{"class":77},[67,3489,3490],{"class":69,"line":213},[67,3491,2014],{"class":77},[67,3493,3494],{"class":69,"line":468},[67,3495,572],{"class":77},[13,3497,3498,3499,3502],{},"これでPHPファイルだけでJSONの言語ファイルも作成して、フロントでのVueの言語ファイルなどに提供することができるようになります。動的にこのJSONファイルは生成するので、バージョン管理をするときは",[41,3500,3501],{},".gitignore","で指定しておくといいです。そしてデプロイや更新時にはこのコマンドを打ち忘れないようにしましょう。",[13,3504,3505],{},"ちなみにですが、実際にやっていることは特定のディレクトリのPHPファイルの内容を取得して、それをJSONにして生成したファイルを置いているだけなので素のPHP、pythonやrubyとか他の言語でも行けると思いますよ。",[810,3507,3508],{},"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":43,"searchDepth":98,"depth":98,"links":3510},[3511,3512,3513,3520],{"id":1935,"depth":91,"text":1936},{"id":1199,"depth":91,"text":1199},{"id":2051,"depth":91,"text":2051,"children":3514},[3515,3516,3517,3518,3519],{"id":2054,"depth":98,"text":2054},{"id":2076,"depth":98,"text":2076},{"id":2320,"depth":98,"text":2320},{"id":2643,"depth":98,"text":2644},{"id":2696,"depth":98,"text":2696},{"id":2728,"depth":91,"text":2728},[821],"2022-05-22",{},"\u002Farticles\u002Flaravel-i18n-json-dump",{"title":1850,"description":1850},"articles\u002Flaravel-i18n-json-dump",[872,1253,239],"tdUMQGHoIH7SI9TXJlID3MGccNOOjq6udWMjq_HTqC0",{"id":3530,"title":3531,"body":3532,"category":4001,"createdAt":4002,"description":4003,"extension":824,"index":825,"meta":4004,"navigation":94,"path":4005,"publish":94,"seo":4006,"series":825,"seriesTitle":825,"stem":4007,"tag":4008,"thumbnail":1846,"updatedAt":825,"__hash__":4009},"articles\u002Farticles\u002Flaravel-custom-faker.md","Laravelでカスタムなフェイカーを作成する。",{"type":10,"value":3533,"toc":3992},[3534,3537,3546,3549,3569,3572,3575,3579,3593,3599,3631,3638,3651,3654,3661,3714,3717,3720,3723,3726,3732,3879,3897,3900,3909,3971,3978,3981,3984,3990],[13,3535,3536],{},"こんにちはjunです。Laravelはフルスタックフレームワークと言われるほど開発者に嬉しい機能が揃っています。その中でFakerと呼ばれるダミーデータを挿入する機能はよく使用します。ページングやいろんな文字を入れてみて、ビュー側やロジックなどが問題ないかを確かめることができるので、効率的な開発には必要不可欠です。",[13,3538,3539,3540,3545],{},"Fakerは標準で英語ですが、設定によって日本語にすることができます。FakerはPHPfakerというDevライブラリを使用しており、",[23,3541,3544],{"href":3542,"rel":3543},"https:\u002F\u002Ffakerphp.github.io\u002Fformatters\u002Fnumbers-and-strings\u002F",[27],"PHP Faker Formatters","で使用できるFakerの一覧を見れます。これらのFakerはおもにFactoryで使用します。",[13,3547,3548],{},"例えば氏名、メールアドレス、文章などは以下の通りです。",[34,3550,3552],{"className":870,"code":3551,"language":872,"meta":43,"style":43},"$this->faker->name();\n$this->faker->email();\n$this->faker->realText();\n",[41,3553,3554,3559,3564],{"__ignoreMap":43},[67,3555,3556],{"class":69,"line":70},[67,3557,3558],{},"$this->faker->name();\n",[67,3560,3561],{"class":69,"line":91},[67,3562,3563],{},"$this->faker->email();\n",[67,3565,3566],{"class":69,"line":98},[67,3567,3568],{},"$this->faker->realText();\n",[13,3570,3571],{},"上記の通りそれっぽいダミーデータを作れるのですが、時たまにアプリで必要なデータ形式のFakerがほしかったり、ランダム・特定条件のマスターのIDを出してほしい、もう少し実装するサービスに即した内容を出してほしいと言った要望がある場合はFakerを自作する必要があります。",[13,3573,3574],{},"今回はそのカスタムFakerの実装を解説したいと思います。",[51,3576,3578],{"id":3577},"カスタムfakerのクラスを作成","カスタムFakerのクラスを作成",[13,3580,3581,3582,3585,3586,3589,3590,3592],{},"最初にカスタムFakerのクラスを作成します。",[41,3583,3584],{},"App\u002FFaker\u002FCustome.php","を作成します。今回は",[41,3587,3588],{},"Custome.php","に実装したいFakerを作成しますが、もしクラスごとにわたい場合は",[41,3591,3588],{},"をFakerごとのクラスに分けてください。",[13,3594,3595,3596,3598],{},"そして",[41,3597,3588],{},"は以下のように記述します。",[34,3600,3602],{"className":870,"code":3601,"filename":3584,"language":872,"meta":43,"style":43},"namespace App\\Faker;\nuse Faker\\Provider\\Base;\n\nclass Custome extends Base{\n\n}\n\n",[41,3603,3604,3609,3614,3618,3623,3627],{"__ignoreMap":43},[67,3605,3606],{"class":69,"line":70},[67,3607,3608],{},"namespace App\\Faker;\n",[67,3610,3611],{"class":69,"line":91},[67,3612,3613],{},"use Faker\\Provider\\Base;\n",[67,3615,3616],{"class":69,"line":98},[67,3617,95],{"emptyLinePlaceholder":94},[67,3619,3620],{"class":69,"line":107},[67,3621,3622],{},"class Custome extends Base{\n",[67,3624,3625],{"class":69,"line":115},[67,3626,95],{"emptyLinePlaceholder":94},[67,3628,3629],{"class":69,"line":126},[67,3630,572],{},[13,3632,3633,3634,3637],{},"ここでは",[41,3635,3636],{},"Faker\\Provider\\Base","を継承させてください。Fakerの元ファイルもFakerメソッドを定義しているクラスで継承しています。",[13,3639,3640,3642,3643,3646,3647,3650],{},[41,3641,3636],{},"からは",[41,3644,3645],{},"randomElements()","といったランダムな配列を取得する、任意範囲の数字を取得する",[41,3648,3649],{},"numberBetween()","といった便利なメソッドを使用できます。",[599,3652,3653],{"id":3653},"フェイカーの書き方",[13,3655,3656,3657,3660],{},"例えば、食品の名前を出してくれる",[41,3658,3659],{},"foodname()","というFakerを作ってみるとします。",[34,3662,3664],{"className":870,"code":3663,"filename":3584,"language":872,"meta":43,"style":43},"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",[41,3665,3666,3670,3674,3678,3682,3686,3691,3695,3700,3705,3710],{"__ignoreMap":43},[67,3667,3668],{"class":69,"line":70},[67,3669,3608],{},[67,3671,3672],{"class":69,"line":91},[67,3673,3613],{},[67,3675,3676],{"class":69,"line":98},[67,3677,95],{"emptyLinePlaceholder":94},[67,3679,3680],{"class":69,"line":107},[67,3681,3622],{},[67,3683,3684],{"class":69,"line":115},[67,3685,95],{"emptyLinePlaceholder":94},[67,3687,3688],{"class":69,"line":126},[67,3689,3690],{},"     protected static $foodName = ['ラーメン','パスタ','おにぎり','パン','炒飯']\n",[67,3692,3693],{"class":69,"line":137},[67,3694,95],{"emptyLinePlaceholder":94},[67,3696,3697],{"class":69,"line":145},[67,3698,3699],{},"     public function foodname(){\n",[67,3701,3702],{"class":69,"line":154},[67,3703,3704],{},"        return static::randomElement(static::$foodName);\n",[67,3706,3707],{"class":69,"line":165},[67,3708,3709],{},"     }\n",[67,3711,3712],{"class":69,"line":173},[67,3713,572],{},[13,3715,3716],{},"このような感じで、配列に静的なプロパティを作成します。そしてメソッドでそのリストからランダムで呼び出す処理を実装すれば大丈夫です。DB上にあるマスターなどを使用したい場合、DBと接続して取得してもいいと思います。",[13,3718,3719],{},"引数を設定できるので、特定の食品だけ取り出すみたいな処理を加えていいでしょう。まずカスタムFakerメソッドを実装できたので、実際に使えるようにします。",[51,3721,3722],{"id":3722},"サービスプロバイダを作成",[13,3724,3725],{},"Laravelの標準のFakerはサービスプロバイダでシングルトンとして登録されています。それをオーバーライドするような処理を行っています。",[13,3727,3728,3731],{},[41,3729,3730],{},"App\u002FProviders\u002FFakerServiceProvider.php","配下にサービスプロバイダを作成します。",[34,3733,3735],{"className":870,"code":3734,"filename":3730,"language":872,"meta":43,"style":43},"\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",[41,3736,3737,3741,3746,3751,3756,3761,3765,3770,3774,3778,3783,3787,3791,3795,3800,3804,3809,3814,3819,3824,3828,3832,3836,3840,3845,3849,3853,3857,3862,3866,3871,3875],{"__ignoreMap":43},[67,3738,3739],{"class":69,"line":70},[67,3740,95],{"emptyLinePlaceholder":94},[67,3742,3743],{"class":69,"line":91},[67,3744,3745],{},"namespace App\\Providers;\n",[67,3747,3748],{"class":69,"line":98},[67,3749,3750],{},"use Faker\\{Factory, Generator};\n",[67,3752,3753],{"class":69,"line":107},[67,3754,3755],{},"use Illuminate\\Support\\ServiceProvider;\n",[67,3757,3758],{"class":69,"line":115},[67,3759,3760],{},"use App\\Faker\\Custome;\n",[67,3762,3763],{"class":69,"line":126},[67,3764,95],{"emptyLinePlaceholder":94},[67,3766,3767],{"class":69,"line":137},[67,3768,3769],{},"class FakerServiceProvider extends ServiceProvider\n",[67,3771,3772],{"class":69,"line":145},[67,3773,408],{},[67,3775,3776],{"class":69,"line":154},[67,3777,2137],{},[67,3779,3780],{"class":69,"line":165},[67,3781,3782],{},"     * Register services.\n",[67,3784,3785],{"class":69,"line":173},[67,3786,2147],{},[67,3788,3789],{"class":69,"line":181},[67,3790,2215],{},[67,3792,3793],{"class":69,"line":189},[67,3794,2157],{},[67,3796,3797],{"class":69,"line":197},[67,3798,3799],{},"    public function register()\n",[67,3801,3802],{"class":69,"line":205},[67,3803,2231],{},[67,3805,3806],{"class":69,"line":213},[67,3807,3808],{},"        $this->app->singleton(Generator::class, function () {\n",[67,3810,3811],{"class":69,"line":468},[67,3812,3813],{},"            $faker = Factory::create(config('app.faker_locale'));\n",[67,3815,3816],{"class":69,"line":498},[67,3817,3818],{},"            $faker->addProvider(new Custome($faker));\n",[67,3820,3821],{"class":69,"line":504},[67,3822,3823],{},"            return $faker;\n",[67,3825,3826],{"class":69,"line":522},[67,3827,2570],{},[67,3829,3830],{"class":69,"line":529},[67,3831,2014],{},[67,3833,3834],{"class":69,"line":541},[67,3835,95],{"emptyLinePlaceholder":94},[67,3837,3838],{"class":69,"line":558},[67,3839,2137],{},[67,3841,3842],{"class":69,"line":563},[67,3843,3844],{},"     * Bootstrap services.\n",[67,3846,3847],{"class":69,"line":569},[67,3848,2147],{},[67,3850,3851],{"class":69,"line":2212},[67,3852,2215],{},[67,3854,3855],{"class":69,"line":2218},[67,3856,2157],{},[67,3858,3859],{"class":69,"line":2223},[67,3860,3861],{},"    public function boot()\n",[67,3863,3864],{"class":69,"line":4},[67,3865,2231],{},[67,3867,3868],{"class":69,"line":2234},[67,3869,3870],{},"        \u002F\u002F\n",[67,3872,3873],{"class":69,"line":2240},[67,3874,2014],{},[67,3876,3877],{"class":69,"line":2245},[67,3878,572],{},[13,3880,3881,3884,3885,3888,3889,3892,3893,3896],{},[41,3882,3883],{},"Factory::create(config('app.faker_locale'))","としておくと、",[41,3886,3887],{},"config\u002Fapp.php","の",[41,3890,3891],{},"faker_locale","を用いて日本語化できます。そしてFakerのインスタンスに",[41,3894,3895],{},"addProvider()","を使用して、作成したカスタムフェイカーを追加します。",[599,3898,3899],{"id":3899},"サービスプロバイダの登録",[13,3901,3902,3903,3888,3905,3908],{},"このサービスプロバイダを",[41,3904,3887],{},[41,3906,3907],{},"providers"," に追加します。",[34,3910,3912],{"className":870,"code":3911,"filename":3887,"language":872,"meta":43,"style":43},"'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",[41,3913,3914,3919,3924,3929,3934,3939,3943,3947,3952,3957,3961,3966],{"__ignoreMap":43},[67,3915,3916],{"class":69,"line":70},[67,3917,3918],{},"'providers' => [\n",[67,3920,3921],{"class":69,"line":91},[67,3922,3923],{},"\u002F*\n",[67,3925,3926],{"class":69,"line":98},[67,3927,3928],{},"* Laravel Framework Service Providers...\n",[67,3930,3931],{"class":69,"line":107},[67,3932,3933],{},"*\u002F\n",[67,3935,3936],{"class":69,"line":115},[67,3937,3938],{},"\u002F\u002F 省略\n",[67,3940,3941],{"class":69,"line":126},[67,3942,95],{"emptyLinePlaceholder":94},[67,3944,3945],{"class":69,"line":137},[67,3946,3923],{},[67,3948,3949],{"class":69,"line":145},[67,3950,3951],{},"* Application Service Providers...\n",[67,3953,3954],{"class":69,"line":154},[67,3955,3956],{},"　追加\n",[67,3958,3959],{"class":69,"line":165},[67,3960,3933],{},[67,3962,3963],{"class":69,"line":173},[67,3964,3965],{},"    App\\Providers\\FakerServiceProvider::class,\n",[67,3967,3968],{"class":69,"line":181},[67,3969,3970],{},"],\n",[13,3972,3973,3974,3977],{},"そうするとfakerにて",[41,3975,3976],{},"$this->faker->foodname()","でランダムな食品名が出てきます。",[51,3979,3980],{"id":3980},"すぐに検証したい場合の方法",[13,3982,3983],{},"上記のFakerはFactoryなどで使用できますが、Tinkerなどですぐに確かめたいということがあると思います。そんな時はTinkerで以下のようにFakerのインスタンスを生成して、チェックできます。なお上記のサービスプロバイダーを登録している必要があります。",[34,3985,3988],{"className":3986,"code":3987,"language":39},[37],"php artian tinker\n>> $faker = app()->make(Faker\\Generator::class)\n>> $faker->foodname()\n=>'パスタ'\n",[41,3989,3987],{"__ignoreMap":43},[810,3991,1239],{},{"title":43,"searchDepth":98,"depth":98,"links":3993},[3994,3997,4000],{"id":3577,"depth":91,"text":3578,"children":3995},[3996],{"id":3653,"depth":98,"text":3653},{"id":3722,"depth":91,"text":3722,"children":3998},[3999],{"id":3899,"depth":98,"text":3899},{"id":3980,"depth":91,"text":3980},[821],"2022-04-02","Laravelでのカスタムフェイカーの作り方",{},"\u002Farticles\u002Flaravel-custom-faker",{"title":3531,"description":4003},"articles\u002Flaravel-custom-faker",[872,1253],"J5_Trsafdn5yWViWjr3IsUw74QmcNr3WsF_8_DUEepU",{"id":4011,"title":4012,"body":4013,"category":4498,"createdAt":4499,"description":4012,"extension":824,"index":825,"meta":4500,"navigation":94,"path":4501,"publish":94,"seo":4502,"series":825,"seriesTitle":825,"stem":4503,"tag":4504,"thumbnail":1846,"updatedAt":825,"__hash__":4505},"articles\u002Farticles\u002Flaravel-validation-unit-test.md","Laravelでカスタムバリデーションのユニットテストをする方法",{"type":10,"value":4014,"toc":4491},[4015,4022,4025,4028,4031,4099,4102,4109,4278,4281,4284,4287,4290,4310,4319,4322,4325,4380,4383,4436,4457,4463,4466,4486,4489],[13,4016,4017,4018,4021],{},"Laravelでは",[41,4019,4020],{},"Illuminate\\Contracts\\Validation\\Rule","を継承したクラスを用いてカスタムなバリデーションを作成することができます。そしてサービスプロバイダに登録することで、FormRequestで文字列で指定することでリクエストのバリデーションを拡張できます。",[13,4023,4024],{},"ただしこの様な独自コードはきちんとユニットテストをすることが大切です。Laravelではこのカスタムバリデーションを簡単にユニットテストをすることができます。",[51,4026,4027],{"id":4027},"サンプルのバリデーション",[13,4029,4030],{},"とりあえず以下の様な郵便番号のバリデーションを作成したとします。７桁のハイフンなしの数字が郵便番号の形式とします。",[34,4032,4034],{"className":870,"code":4033,"language":872,"meta":43,"style":43},"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",[41,4035,4036,4041,4046,4050,4055,4059,4064,4068,4073,4077,4081,4086,4091,4095],{"__ignoreMap":43},[67,4037,4038],{"class":69,"line":70},[67,4039,4040],{},"namespace App\\Rules;\n",[67,4042,4043],{"class":69,"line":91},[67,4044,4045],{},"use Illuminate\\Contracts\\Validation\\Rule;\n",[67,4047,4048],{"class":69,"line":98},[67,4049,95],{"emptyLinePlaceholder":94},[67,4051,4052],{"class":69,"line":107},[67,4053,4054],{},"class Zipcode implements Rule{\n",[67,4056,4057],{"class":69,"line":115},[67,4058,95],{"emptyLinePlaceholder":94},[67,4060,4061],{"class":69,"line":126},[67,4062,4063],{},"    public function passes($attribute, $value)\n",[67,4065,4066],{"class":69,"line":137},[67,4067,2231],{},[67,4069,4070],{"class":69,"line":145},[67,4071,4072],{},"        return preg_match('\u002F^[0-9]{3}-?[0-9]{4}$\u002F', $value);\n",[67,4074,4075],{"class":69,"line":154},[67,4076,2014],{},[67,4078,4079],{"class":69,"line":165},[67,4080,95],{"emptyLinePlaceholder":94},[67,4082,4083],{"class":69,"line":173},[67,4084,4085],{},"    public function message(){\n",[67,4087,4088],{"class":69,"line":181},[67,4089,4090],{},"        return '郵便番号は7桁の半角数字で入力してください。';\n",[67,4092,4093],{"class":69,"line":189},[67,4094,2014],{},[67,4096,4097],{"class":69,"line":197},[67,4098,572],{},[51,4100,4101],{"id":4101},"ユニットテストファイルを作成",[13,4103,4104,4105,4108],{},"ひとまず ",[41,4106,4107],{},"tests\u002FUnit\u002FRules.php"," というものを作成します。",[34,4110,4113],{"className":870,"code":4111,"filename":4112,"language":872,"meta":43,"style":43},"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",[41,4114,4115,4120,4124,4129,4134,4139,4144,4149,4153,4158,4163,4167,4172,4177,4182,4187,4192,4197,4202,4207,4212,4217,4222,4227,4232,4237,4242,4247,4252,4257,4262,4266,4270,4274],{"__ignoreMap":43},[67,4116,4117],{"class":69,"line":70},[67,4118,4119],{},"namespace Tests\\Unit;\n",[67,4121,4122],{"class":69,"line":91},[67,4123,95],{"emptyLinePlaceholder":94},[67,4125,4126],{"class":69,"line":98},[67,4127,4128],{},"use App\\Rules\\Zipcode;\n",[67,4130,4131],{"class":69,"line":107},[67,4132,4133],{},"use Illuminate\\Foundation\\Testing\\TestCase;\n",[67,4135,4136],{"class":69,"line":115},[67,4137,4138],{},"use Tests\\CreatesApplication;\n",[67,4140,4141],{"class":69,"line":126},[67,4142,4143],{},"use Illuminate\\Support\\Facades\\Validator;\n",[67,4145,4146],{"class":69,"line":137},[67,4147,4148],{},"use \\Illuminate\\Validation\\ValidationException;\n",[67,4150,4151],{"class":69,"line":145},[67,4152,95],{"emptyLinePlaceholder":94},[67,4154,4155],{"class":69,"line":154},[67,4156,4157],{},"class Rules extends TestCase{\n",[67,4159,4160],{"class":69,"line":165},[67,4161,4162],{},"    use CreatesApplication;\n",[67,4164,4165],{"class":69,"line":173},[67,4166,95],{"emptyLinePlaceholder":94},[67,4168,4169],{"class":69,"line":181},[67,4170,4171],{},"    public function test_zipcode_validation(){\n",[67,4173,4174],{"class":69,"line":189},[67,4175,4176],{},"        $tests = [\n",[67,4178,4179],{"class":69,"line":197},[67,4180,4181],{},"            '1234567'=>true,\n",[67,4183,4184],{"class":69,"line":205},[67,4185,4186],{},"            '0012344'=>true,\n",[67,4188,4189],{"class":69,"line":213},[67,4190,4191],{},"            '0012340'=>true,\n",[67,4193,4194],{"class":69,"line":468},[67,4195,4196],{},"            '12345678'=>false,\n",[67,4198,4199],{"class":69,"line":498},[67,4200,4201],{},"            '123456'=>false,\n",[67,4203,4204],{"class":69,"line":504},[67,4205,4206],{},"            '1234 56'=>false,\n",[67,4208,4209],{"class":69,"line":522},[67,4210,4211],{},"            '1234a56'=>false,\n",[67,4213,4214],{"class":69,"line":529},[67,4215,4216],{},"            '1234_56'=>false,\n",[67,4218,4219],{"class":69,"line":541},[67,4220,4221],{},"        ];\n",[67,4223,4224],{"class":69,"line":558},[67,4225,4226],{},"        foreach($tests as $key => $condition){\n",[67,4228,4229],{"class":69,"line":563},[67,4230,4231],{},"            try{\n",[67,4233,4234],{"class":69,"line":569},[67,4235,4236],{},"                Validator::make(['test'=>$key],[\n",[67,4238,4239],{"class":69,"line":2212},[67,4240,4241],{},"                    'test'=> new Zipcode()\n",[67,4243,4244],{"class":69,"line":2218},[67,4245,4246],{},"                ])->validate();\n",[67,4248,4249],{"class":69,"line":2223},[67,4250,4251],{},"                $this->assertTrue($condition===true);\n",[67,4253,4254],{"class":69,"line":4},[67,4255,4256],{},"            }catch(ValidationException $e){\n",[67,4258,4259],{"class":69,"line":2234},[67,4260,4261],{},"                $this->assertFalse($condition);\n",[67,4263,4264],{"class":69,"line":2240},[67,4265,2443],{},[67,4267,4268],{"class":69,"line":2245},[67,4269,566],{},[67,4271,4272],{"class":69,"line":2250},[67,4273,2014],{},[67,4275,4276],{"class":69,"line":2255},[67,4277,572],{},[13,4279,4280],{},"バリデーションテストでは正しい形式は正しいと判断（ポジティブテスト）し、間違っているものは間違っていると判断（ネガティブテスト）できているかをテストします。\nもしも正しいのに間違っていると判断したり、間違っているのに正しいとなったらテストが失敗する様になっています。",[13,4282,4283],{},"ここでバリデーションテストの詳細を解説します。",[599,4285,4286],{"id":4286},"バリデーションのインスタンスを作成",[13,4288,4289],{},"最初にテストしたいバリデーションのインスタンス、そしてバリデーターインスタンスを作成します。",[34,4291,4293],{"className":870,"code":4292,"language":872,"meta":43,"style":43},"Validator::make(['test'=>$key],[\n    'test'=> new Zipcode()\n])->validate();\n",[41,4294,4295,4300,4305],{"__ignoreMap":43},[67,4296,4297],{"class":69,"line":70},[67,4298,4299],{},"Validator::make(['test'=>$key],[\n",[67,4301,4302],{"class":69,"line":91},[67,4303,4304],{},"    'test'=> new Zipcode()\n",[67,4306,4307],{"class":69,"line":98},[67,4308,4309],{},"])->validate();\n",[13,4311,4312,1175,4315,4318],{},[41,4313,4314],{},"Validator",[41,4316,4317],{},"make()","を使用して第一引数に、バリデーションをする値とキーを設定します。第二引数にはバリデーションのキーとバリデーションインスタンスを指定します。",[599,4320,4321],{"id":4321},"いろんなパターンをテストする",[13,4323,4324],{},"いろいろなパターンをテストするため以下の様に配列でテストパターンと予期する正誤を設定します。Trueはバリデーション通過で、Falseはバリデーション違反（間違った形式）であることを示します。",[34,4326,4328],{"className":870,"code":4327,"language":872,"meta":43,"style":43},"$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",[41,4329,4330,4335,4340,4345,4350,4355,4360,4365,4370,4375],{"__ignoreMap":43},[67,4331,4332],{"class":69,"line":70},[67,4333,4334],{},"$tests = [\n",[67,4336,4337],{"class":69,"line":91},[67,4338,4339],{},"    '1234567'=>true,\n",[67,4341,4342],{"class":69,"line":98},[67,4343,4344],{},"    '0012344'=>true,\n",[67,4346,4347],{"class":69,"line":107},[67,4348,4349],{},"    '0012340'=>true,\n",[67,4351,4352],{"class":69,"line":115},[67,4353,4354],{},"    '12345678'=>false,\n",[67,4356,4357],{"class":69,"line":126},[67,4358,4359],{},"    '123456'=>false,\n",[67,4361,4362],{"class":69,"line":137},[67,4363,4364],{},"    '1234 56'=>false,\n",[67,4366,4367],{"class":69,"line":145},[67,4368,4369],{},"    '1234a56'=>false,\n",[67,4371,4372],{"class":69,"line":154},[67,4373,4374],{},"    '1234_56'=>false,\n",[67,4376,4377],{"class":69,"line":165},[67,4378,4379],{},"];\n",[13,4381,4382],{},"そしてforeachでそれぞれチェックします。",[34,4384,4386],{"className":870,"code":4385,"language":872,"meta":43,"style":43},"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",[41,4387,4388,4393,4398,4403,4408,4413,4418,4423,4428,4432],{"__ignoreMap":43},[67,4389,4390],{"class":69,"line":70},[67,4391,4392],{},"foreach($tests as $key => $condition){\n",[67,4394,4395],{"class":69,"line":91},[67,4396,4397],{},"    try{\n",[67,4399,4400],{"class":69,"line":98},[67,4401,4402],{},"        Validator::make(['test'=>$key],[\n",[67,4404,4405],{"class":69,"line":107},[67,4406,4407],{},"            'test'=> new Zipcode()\n",[67,4409,4410],{"class":69,"line":115},[67,4411,4412],{},"        ])->validate();\n",[67,4414,4415],{"class":69,"line":126},[67,4416,4417],{},"        $this->assertTrue($condition===true);\n",[67,4419,4420],{"class":69,"line":137},[67,4421,4422],{},"    }catch(ValidationException $e){\n",[67,4424,4425],{"class":69,"line":145},[67,4426,4427],{},"        $this->assertFalse($condition);\n",[67,4429,4430],{"class":69,"line":154},[67,4431,2014],{},[67,4433,4434],{"class":69,"line":165},[67,4435,572],{},[13,4437,4438,4441,4442,4445,4446,4448,4449,4452,4453,4456],{},[41,4439,4440],{},"validate()","メソッドは失敗すると",[41,4443,4444],{},"ValidationException"," を投げます。そのため例外処理で",[41,4447,4444],{}," をキャッチします。正しい形式を正しいと判断できれば",[41,4450,4451],{},"$this->assertTrue($condition===true);","となり、まちがった形を間違っていると判断できればキャッチして",[41,4454,4455],{},"$this->assertFalse($condition);","でアサートされます。",[34,4458,4461],{"className":4459,"code":4460,"language":39},[37],"php artisan test \n",[41,4462,4460],{"__ignoreMap":43},[13,4464,4465],{},"にてテストを行い問題なければそのまま通過します。テストは以上の方法でいろんなパターンをテストできます。パターンは思いついたものを配列に書いてもいいですし、プログラム的に大量に配列を生成してもいいかもしれません。まとめると",[1394,4467,4468,4471,4476],{},[1293,4469,4470],{},"バリデーションインスタンスを作成",[1293,4472,4473,4475],{},[41,4474,4444],{},"を用いて間違っている形を識別",[1293,4477,4478,4479,1784,4482,4485],{},"バリデーション対象の値が正誤かどうかは",[41,4480,4481],{},"assertTrue",[41,4483,4484],{},"assertFalse","で正しく判断できているかをアサート",[13,4487,4488],{},"こんな感じです。私はこのテストで結構救われたので、バリデーションを作った時は必ずユニットテストをしましょう。",[810,4490,1239],{},{"title":43,"searchDepth":98,"depth":98,"links":4492},[4493,4494],{"id":4027,"depth":91,"text":4027},{"id":4101,"depth":91,"text":4101,"children":4495},[4496,4497],{"id":4286,"depth":98,"text":4286},{"id":4321,"depth":98,"text":4321},[821],"2022-03-25",{},"\u002Farticles\u002Flaravel-validation-unit-test",{"title":4012,"description":4012},"articles\u002Flaravel-validation-unit-test",[872,1253],"_6kCaD6uDnBdQkj-XY_8G39zixCeVekVE-xS127shV4",{"id":4507,"title":4508,"body":4509,"category":4582,"createdAt":4583,"description":4508,"extension":824,"index":825,"meta":4584,"navigation":94,"path":4585,"publish":94,"seo":4586,"series":825,"seriesTitle":825,"stem":4587,"tag":4588,"thumbnail":4590,"updatedAt":825,"__hash__":4591},"articles\u002Farticles\u002Fwordpress-asset-chache.md","WordpressのJS・CSSファイルのキャッシュ対策",{"type":10,"value":4510,"toc":4578},[4511,4514,4518,4528,4534,4541,4547,4550,4553,4556,4559,4566,4572,4575],[13,4512,4513],{},"こんにちはjunです。wordpressのテーマを本番運用してスタイルに修正があり、修正をアップロードしてもクライアントのキャッシュが原因でユーザー側で変更されないことがあります。今回はテーマ内で読み込むjs,cssのキャッシュ対策について忘備録がてら記事として共有したいと思います。",[51,4515,4517],{"id":4516},"wp_enqueue_stylewp_enqueue_scriptに記述","wp_enqueue_style,wp_enqueue_scriptに記述",[13,4519,4520,4521,351,4524,4527],{},"wordpressのアセット読み込みには",[41,4522,4523],{},"wp_enqueue_style",[41,4525,4526],{},"wp_enqueue_script","を使用します。それらの第４引数にはバージョンの文字列を入力することができます。",[13,4529,4530],{},[23,4531,4526],{"href":4532,"rel":4533},"https:\u002F\u002Fdeveloper.wordpress.org\u002Freference\u002Ffunctions\u002Fwp_enqueue_script\u002F",[27],[13,4535,4536,4537,4540],{},"例えば、",[41,4538,4539],{},"1.3","など入力すれば読み込んだアセットのURLで以下の様に設定されます。",[34,4542,4545],{"className":4543,"code":4544,"language":39},[37],"https:\u002F\u002Fexample.com\u002Fwp-content\u002Fthemes\u002Fminato\u002Fassets\u002Fcss\u002Fstyle.css?ver=1.3\n",[41,4546,4544],{"__ignoreMap":43},[13,4548,4549],{},"GETパラメーターでバージョンの文字列をつけることで、バージョンを識別できる様になります。ブラウザは同じURLのCSS、JS、画像を手元にキャッシュします。普段は通信が早くなるのでありがたいですが、修正が発生した時などは古いコードが残るのでユーザーによって動きに差異が生まれる原因になります。そして大体のユーザーはキャッシュクリアのやり方を知らないですし、スマホは特に強力なキャッシュが効いています。",[13,4551,4552],{},"更新時のファイルをユーザーに届けるためには、上記の様なバージョンのGETパラメーターをつけるなどして、別のURLを指定する必要があります。",[13,4554,4555],{},"そのため納品時には今後の修正を考えてバージョンを変化させる様にした方がいいです。マーケットプレイスならば上記の引数をリリース・修正ごとに変えていればいいです。しかし開発中や毎回バージョンを変えるのが面倒な時、Gitで管理していてバージョンの文字列をあまり変えたくない時はファイルの更新日時でバージョンを変えてあげる方法があります。",[51,4557,4558],{"id":4558},"filemtimeを使用する",[13,4560,4561,4562,4565],{},"PHPには対象ファイルの更新日時を取得する",[41,4563,4564],{},"filemtime()","という関数があります。引数には以下の様にサーバー上でのファイルパスを入力します。",[34,4567,4570],{"className":4568,"code":4569,"language":39},[37],"filemtime(get_theme_file_path('\u002Fcss\u002Feditor-style.css'))\n",[41,4571,4569],{"__ignoreMap":43},[13,4573,4574],{},"返り値はUnixタイムスタンプです。更新時はその時間が変わるので、更新のたびに変化する値を取得して変更したファイルを確実に届けることができます。対象ファイルを編集するだけで自動的にバージョンを示すことができます。",[13,4576,4577],{},"毎回更新日時を取得するのでその分のパフォーマンスがきりなりますが、自分はよくこの方法でwordpressを構築しています。",{"title":43,"searchDepth":98,"depth":98,"links":4579},[4580,4581],{"id":4516,"depth":91,"text":4517},{"id":4558,"depth":91,"text":4558},[821],"2022-03-20",{},"\u002Farticles\u002Fwordpress-asset-chache",{"title":4508,"description":4508},"articles\u002Fwordpress-asset-chache",[872,4589],"wordpress","_common\u002Fwordpress.png","S6seqQRftTxMf1j4j6_e1GgS9Q_wBpOnHTPv4lrW-Hc",1780987132880]