[{"data":1,"prerenderedAt":1069},["ShallowReactive",2],{"article-tweet-check-laravel-jpb-scheduler":3},{"id":4,"title":5,"body":6,"category":1056,"createdAt":1058,"description":5,"extension":1059,"index":1060,"meta":1061,"navigation":141,"path":1062,"publish":141,"seo":1063,"series":1060,"seriesTitle":1060,"stem":1064,"tag":1065,"thumbnail":1067,"updatedAt":1060,"__hash__":1068},"articles\u002Farticles\u002Ftweet-check-laravel-jpb-scheduler.md","Larave 7.0でキュー・ジョブ・スケジューラー触ってみる。取得したツイートが存在するか毎夜毎夜チェックするジョブとスケジューラー。",{"type":7,"value":8,"toc":1043},"minimark",[9,13,16,20,23,26,29,39,42,45,52,65,68,71,75,78,89,92,96,99,105,108,112,115,258,268,590,593,604,607,610,740,743,750,764,767,773,776,779,786,802,809,958,968,980,986,989,996,1002,1005,1011,1017,1024,1030,1033,1036,1039],[10,11,12],"p",{},"こんにちはjunです。仕事でLaravelを触ることになり必死にドキュメントを読んだりして対策をしています。フルスタックフレームワークと言われているほど、Laravelは機能が豊富なので想像していた物が作れて感動しています。",[10,14,15],{},"そこで今回はドキュメントをみてもイマイチパッとしない、ジョブ(Job)、キュー(Queue)、スケジューラー(Scheduler)を触りながら機能を確かめていきたいと思います。",[17,18,19],"h2",{"id":19},"作りたい物",[10,21,22],{},"Laravelを用いて外部APIと通信して、取得したデータをDBに入れたりブラウザ上で操作するアプリケーションを作成していたときにある問題にぶつかりました。TwitterAPIを用いてツイートの情報を保存していたのですが、非公開もしくは削除されたため表示できないツイートがありました。",[10,24,25],{},"削除されたツイートを破棄するシステムを構築する必要があります。システム自体は簡単でDBから保存されたツイート一つ一つのIDを取得してTwitterAPIをを投げて、ツイートが存在するかの結果を確認すればいいだけです。スクレイピングするような感じです。しかしこの方法には課題があります。",[10,27,28],{},"保存されたツイートが大量にある場合、Twitterのサーバーに大量のリクエストを短時間に送信してしまい、Dosとなってしまいます。そのため各リクエスト毎に最低1秒程でも緩急を置く必要があります。その場合、その処理が終了する時間は保存されたツイート量によってはかなり長くなります。",[10,30,31,32,38],{},"大体、このような長くなりがちな処理はキューとして、サーバー上で非同期に行わせるのがベストです。そしてLarvelではジョブを簡単に実装できる",[33,34,37],"a",{"href":35,"target":36},"https:\u002F\u002Freadouble.com\u002Flaravel\u002F7.x\u002Fja\u002Fscheduling.html","_blank","「タスクスケジュール」","という物があります。",[10,40,41],{},"今回は24時になると保存したツイートの存在を確認してくれる自動実行ジョブを構築したいと思います。",[17,43,44],{"id":44},"タスクスケージューラーを理解する",[10,46,47,51],{},[33,48,50],{"href":49,"target":36},"https:\u002F\u002Flaravel.com\u002Fdocs\u002F7.x\u002Fscheduling","公式のドキュメント","を見てみましょう。おおよそですが実装の手順としては",[53,54,55,59,62],"ol",{},[56,57,58],"li",{},"ジョブの定義（実際の処理ロジック）をapp\u002Fjob\u002F 配下に作成する。",[56,60,61],{},"行うジョブやその実行時間は app\u002Fconsole\u002Fkernel.php に記述。",[56,63,64],{},"cronに毎分Larvelスケジューラーを実行させるコードを記述。",[10,66,67],{},"こんな感じです。細かく解説していきます。",[17,69,70],{"id":70},"ジョブを作成する",[72,73,74],"h3",{"id":74},"キューを使えるようにする",[10,76,77],{},"まずはLaracelでジョブを実行できる環境を整えます。「キュー」に関するドキュメントを見ると以下のコマンドを唱えてジョブを記録するテーブルを作成します。",[79,80,85],"pre",{"className":81,"code":83,"language":84},[82],"language-text","php artisan queue:table\n\nphp artisan migrate\n","text",[86,87,83],"code",{"__ignoreMap":88},"",[10,90,91],{},"このコマンドを唱えると「jobs」「failed_jobs」というテーブルが作成されます。ジョブとキューはセットで使われることが多いので上記のコマンドを唱えておきましょう",[93,94,95],"h4",{"id":95},"ジョブファイルを作成する",[10,97,98],{},"ジョブファイルは app\u002Fjob 配下に作成します。しかし最初はこのjobディレクトリは存在しないので以下のコマンドを唱えて、ジョブファイルのテンプレートとディレクトリを作ってもらいます。",[79,100,103],{"className":101,"code":102,"language":84},[82],"php artisan make:job YOUR_JOB_NAME\n",[86,104,102],{"__ignoreMap":88},[10,106,107],{},"「YOUR_JOB_NAME」の部分にジョブの名前を入力してください。他のmakeコマンドと同じ感じですね。このコマンドを唱えるとjobディレクトリとjobクラスが書かれたファイルが配下に作成されます。そのファイルにジョブの実行処理を書いていきます。",[72,109,111],{"id":110},"twitterにapiを送るジョブを記述","TwitterにAPIを送るジョブを記述",[10,113,114],{},"今回はジョブ名を「CheckNotFoundUrls」として作成します。ジョブファイルは作成するとまず下記のように書かれています。",[79,116,120],{"className":117,"code":118,"language":119,"meta":88,"style":88},"language-php shiki shiki-themes material-theme-ocean","\u003C?php\nnamespace App\\Jobs;\n\nuse Illuminate\\Bus\\Queueable;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Illuminate\\Foundation\\Bus\\Dispatchable;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Illuminate\\Queue\\SerializesModels;\n\nclass CheckNotFoundUrls implements ShouldQueue\n{\n    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;\n\n    public function __construct()\n    {\n        \u002F\u002F\n    }\n\n    public function handle()\n    {\n        \u002F\u002Fここに処理を記述\n    }\n}\n","php",[86,121,122,130,136,143,149,155,161,167,173,178,184,190,196,201,207,213,219,225,230,236,241,247,252],{"__ignoreMap":88},[123,124,127],"span",{"class":125,"line":126},"line",1,[123,128,129],{},"\u003C?php\n",[123,131,133],{"class":125,"line":132},2,[123,134,135],{},"namespace App\\Jobs;\n",[123,137,139],{"class":125,"line":138},3,[123,140,142],{"emptyLinePlaceholder":141},true,"\n",[123,144,146],{"class":125,"line":145},4,[123,147,148],{},"use Illuminate\\Bus\\Queueable;\n",[123,150,152],{"class":125,"line":151},5,[123,153,154],{},"use Illuminate\\Contracts\\Queue\\ShouldQueue;\n",[123,156,158],{"class":125,"line":157},6,[123,159,160],{},"use Illuminate\\Foundation\\Bus\\Dispatchable;\n",[123,162,164],{"class":125,"line":163},7,[123,165,166],{},"use Illuminate\\Queue\\InteractsWithQueue;\n",[123,168,170],{"class":125,"line":169},8,[123,171,172],{},"use Illuminate\\Queue\\SerializesModels;\n",[123,174,176],{"class":125,"line":175},9,[123,177,142],{"emptyLinePlaceholder":141},[123,179,181],{"class":125,"line":180},10,[123,182,183],{},"class CheckNotFoundUrls implements ShouldQueue\n",[123,185,187],{"class":125,"line":186},11,[123,188,189],{},"{\n",[123,191,193],{"class":125,"line":192},12,[123,194,195],{},"    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;\n",[123,197,199],{"class":125,"line":198},13,[123,200,142],{"emptyLinePlaceholder":141},[123,202,204],{"class":125,"line":203},14,[123,205,206],{},"    public function __construct()\n",[123,208,210],{"class":125,"line":209},15,[123,211,212],{},"    {\n",[123,214,216],{"class":125,"line":215},16,[123,217,218],{},"        \u002F\u002F\n",[123,220,222],{"class":125,"line":221},17,[123,223,224],{},"    }\n",[123,226,228],{"class":125,"line":227},18,[123,229,142],{"emptyLinePlaceholder":141},[123,231,233],{"class":125,"line":232},19,[123,234,235],{},"    public function handle()\n",[123,237,239],{"class":125,"line":238},20,[123,240,212],{},[123,242,244],{"class":125,"line":243},21,[123,245,246],{},"        \u002F\u002Fここに処理を記述\n",[123,248,250],{"class":125,"line":249},22,[123,251,224],{},[123,253,255],{"class":125,"line":254},23,[123,256,257],{},"}\n",[10,259,260,263,264,267],{},[86,261,262],{},"handle()","に処理内容を記述します。ジョブを実行するときはこのクラスを読み込み、ジョブインスタンスを作成し、",[86,265,266],{},"dispatch()","するとジョブが実行されます。まあ、とりあえずTwitterにAPIを飛ばすコードを書きます。コードは載せますが詳細の説明は省きます。",[79,269,271],{"className":117,"code":270,"language":119,"meta":88,"style":88},"\u003C?php\nnamespace App\\Jobs;\n\n\u002F\u002FTwitter API ライブラリ\nuse Abraham\\TwitterOAuth\\TwitterOAuth;\n\nuse App\\Models\\Tweets; \u002F\u002F ツイート情報が格納されたテーブルに接続するモデル\nuse App\\Models\\JobReport;　\u002F\u002F 自作ジョブの結果を記録しておくテーブル\n\nuse Illuminate\\Bus\\Queueable;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Illuminate\\Foundation\\Bus\\Dispatchable;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Illuminate\\Queue\\SerializesModels;\n\nclass CheckNotFoundUrls implements ShouldQueue\n{\n    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;\n\n    public function __construct()\n    {\n        \u002F\u002F\n    }\n\n    public function handle()\n    {\n        try{\n            \u002F\u002F ツイート情報があるテーブルからツイート情報（JSON）と必要なレコードを取得\n            $datas = Tweets::select('tweet_rawdata','id','user_id')->get();\n            $deletedTweetOwner = array();\n    \n            foreach($datas as $val){\n                \u002F\u002F'tweet_rawdata'カラムはjsonなので一旦decode。\n                \u002F\u002Fjsonからid_strというツイートIDを取得\n                $decodeTweetData = json_decode($val->tweet_rawdata);\n                $tweet_id = $decodeTweetData->id_str;\n\n                \u002F\u002FAPIに接続するための準備（ライブラリ）をして、APIを送信\n                $connection = new TwitterOAuth(env('TW_CONSUMER_KEY'), env('TW_CONSUMER_KEY_SECRET'), env('TW_ACCESS_TOKEN'), env('TW_ACCESS_TOKEN_SECRET'));\n                $connection->setTimeouts(10, 15);\n                $content = $connection->get(\"statuses\u002Fshow\",['id'=>intval($tweet_id)]);\n    \n                \u002F\u002F請求したツイートIDのツイートが存在しないという結果ならばレコードのIDを $deletedTweetOwnerにユーザーIDを記録\n                if(property_exists($content,'errors')){\n                    switch($content->errors[0]->code){\n                        case 144:\n                            Tweets::destroy($val->id);\n                            array_push($deletedTweetOwner,$val->id);\n                        break;\n                    }\n                }\n                \u002F\u002FDosにならないように1秒止める\n                sleep(1.0);\n            }\n  \n        }catch(\\Exception $e){\n            \u002F\u002F処理が失敗したら管理者用のレポートにエラ〜メッセージを記録する。\n            JobReport::reportException(0,$e->getMessage());\n        }\n    }\n}\n",[86,272,273,277,281,285,290,295,299,304,309,313,317,321,325,329,333,337,341,345,349,353,357,361,365,369,374,379,384,390,396,402,408,414,420,426,432,438,444,449,455,461,467,473,478,484,490,496,502,508,514,520,526,532,538,544,550,556,562,568,574,580,585],{"__ignoreMap":88},[123,274,275],{"class":125,"line":126},[123,276,129],{},[123,278,279],{"class":125,"line":132},[123,280,135],{},[123,282,283],{"class":125,"line":138},[123,284,142],{"emptyLinePlaceholder":141},[123,286,287],{"class":125,"line":145},[123,288,289],{},"\u002F\u002FTwitter API ライブラリ\n",[123,291,292],{"class":125,"line":151},[123,293,294],{},"use Abraham\\TwitterOAuth\\TwitterOAuth;\n",[123,296,297],{"class":125,"line":157},[123,298,142],{"emptyLinePlaceholder":141},[123,300,301],{"class":125,"line":163},[123,302,303],{},"use App\\Models\\Tweets; \u002F\u002F ツイート情報が格納されたテーブルに接続するモデル\n",[123,305,306],{"class":125,"line":169},[123,307,308],{},"use App\\Models\\JobReport;　\u002F\u002F 自作ジョブの結果を記録しておくテーブル\n",[123,310,311],{"class":125,"line":175},[123,312,142],{"emptyLinePlaceholder":141},[123,314,315],{"class":125,"line":180},[123,316,148],{},[123,318,319],{"class":125,"line":186},[123,320,154],{},[123,322,323],{"class":125,"line":192},[123,324,160],{},[123,326,327],{"class":125,"line":198},[123,328,166],{},[123,330,331],{"class":125,"line":203},[123,332,172],{},[123,334,335],{"class":125,"line":209},[123,336,142],{"emptyLinePlaceholder":141},[123,338,339],{"class":125,"line":215},[123,340,183],{},[123,342,343],{"class":125,"line":221},[123,344,189],{},[123,346,347],{"class":125,"line":227},[123,348,195],{},[123,350,351],{"class":125,"line":232},[123,352,142],{"emptyLinePlaceholder":141},[123,354,355],{"class":125,"line":238},[123,356,206],{},[123,358,359],{"class":125,"line":243},[123,360,212],{},[123,362,363],{"class":125,"line":249},[123,364,218],{},[123,366,367],{"class":125,"line":254},[123,368,224],{},[123,370,372],{"class":125,"line":371},24,[123,373,142],{"emptyLinePlaceholder":141},[123,375,377],{"class":125,"line":376},25,[123,378,235],{},[123,380,382],{"class":125,"line":381},26,[123,383,212],{},[123,385,387],{"class":125,"line":386},27,[123,388,389],{},"        try{\n",[123,391,393],{"class":125,"line":392},28,[123,394,395],{},"            \u002F\u002F ツイート情報があるテーブルからツイート情報（JSON）と必要なレコードを取得\n",[123,397,399],{"class":125,"line":398},29,[123,400,401],{},"            $datas = Tweets::select('tweet_rawdata','id','user_id')->get();\n",[123,403,405],{"class":125,"line":404},30,[123,406,407],{},"            $deletedTweetOwner = array();\n",[123,409,411],{"class":125,"line":410},31,[123,412,413],{},"    \n",[123,415,417],{"class":125,"line":416},32,[123,418,419],{},"            foreach($datas as $val){\n",[123,421,423],{"class":125,"line":422},33,[123,424,425],{},"                \u002F\u002F'tweet_rawdata'カラムはjsonなので一旦decode。\n",[123,427,429],{"class":125,"line":428},34,[123,430,431],{},"                \u002F\u002Fjsonからid_strというツイートIDを取得\n",[123,433,435],{"class":125,"line":434},35,[123,436,437],{},"                $decodeTweetData = json_decode($val->tweet_rawdata);\n",[123,439,441],{"class":125,"line":440},36,[123,442,443],{},"                $tweet_id = $decodeTweetData->id_str;\n",[123,445,447],{"class":125,"line":446},37,[123,448,142],{"emptyLinePlaceholder":141},[123,450,452],{"class":125,"line":451},38,[123,453,454],{},"                \u002F\u002FAPIに接続するための準備（ライブラリ）をして、APIを送信\n",[123,456,458],{"class":125,"line":457},39,[123,459,460],{},"                $connection = new TwitterOAuth(env('TW_CONSUMER_KEY'), env('TW_CONSUMER_KEY_SECRET'), env('TW_ACCESS_TOKEN'), env('TW_ACCESS_TOKEN_SECRET'));\n",[123,462,464],{"class":125,"line":463},40,[123,465,466],{},"                $connection->setTimeouts(10, 15);\n",[123,468,470],{"class":125,"line":469},41,[123,471,472],{},"                $content = $connection->get(\"statuses\u002Fshow\",['id'=>intval($tweet_id)]);\n",[123,474,476],{"class":125,"line":475},42,[123,477,413],{},[123,479,481],{"class":125,"line":480},43,[123,482,483],{},"                \u002F\u002F請求したツイートIDのツイートが存在しないという結果ならばレコードのIDを $deletedTweetOwnerにユーザーIDを記録\n",[123,485,487],{"class":125,"line":486},44,[123,488,489],{},"                if(property_exists($content,'errors')){\n",[123,491,493],{"class":125,"line":492},45,[123,494,495],{},"                    switch($content->errors[0]->code){\n",[123,497,499],{"class":125,"line":498},46,[123,500,501],{},"                        case 144:\n",[123,503,505],{"class":125,"line":504},47,[123,506,507],{},"                            Tweets::destroy($val->id);\n",[123,509,511],{"class":125,"line":510},48,[123,512,513],{},"                            array_push($deletedTweetOwner,$val->id);\n",[123,515,517],{"class":125,"line":516},49,[123,518,519],{},"                        break;\n",[123,521,523],{"class":125,"line":522},50,[123,524,525],{},"                    }\n",[123,527,529],{"class":125,"line":528},51,[123,530,531],{},"                }\n",[123,533,535],{"class":125,"line":534},52,[123,536,537],{},"                \u002F\u002FDosにならないように1秒止める\n",[123,539,541],{"class":125,"line":540},53,[123,542,543],{},"                sleep(1.0);\n",[123,545,547],{"class":125,"line":546},54,[123,548,549],{},"            }\n",[123,551,553],{"class":125,"line":552},55,[123,554,555],{},"  \n",[123,557,559],{"class":125,"line":558},56,[123,560,561],{},"        }catch(\\Exception $e){\n",[123,563,565],{"class":125,"line":564},57,[123,566,567],{},"            \u002F\u002F処理が失敗したら管理者用のレポートにエラ〜メッセージを記録する。\n",[123,569,571],{"class":125,"line":570},58,[123,572,573],{},"            JobReport::reportException(0,$e->getMessage());\n",[123,575,577],{"class":125,"line":576},59,[123,578,579],{},"        }\n",[123,581,583],{"class":125,"line":582},60,[123,584,224],{},[123,586,588],{"class":125,"line":587},61,[123,589,257],{},[10,591,592],{},"ちなみにTwitterへのAPIアクセスには Abraham\\TwitterOAuth というTwttiter公式が紹介するサードパーティライブラリを使用しています。composerで読み込んでuseすればすぐに使え、上記のようにouthインスタンスを作成するだけで使えます。簡単なのでLarvelでTwitterAPIを使う時などにおすすめです。",[594,595,599,600],"div",{"className":596},[597,598],"alert","alert-info","\n    ",[33,601,603],{"href":602,"target":36},"https:\u002F\u002Fgithub.com\u002Fabraham\u002Ftwitteroauth","GITHUB：abraham\u002Ftwitteroauth\n    ",[10,605,606],{},"とりあえずこれでジョブは完成しました。試しにチェックをしてみたいですが、なんとかしてこのジョブを実行する必要があります。しかしLaravelには定義したジョブを実行するコマンドが見当たりません。（もしかしたら見落としてるだけかも）",[10,608,609],{},"コマンドでジョブを叩いて実行したいので、以下のカスタムコマンドを自作します。",[79,611,613],{"className":117,"code":612,"language":119,"meta":88,"style":88},"\u003C?php\n\nnamespace App\\Console\\Commands;\n\nuse Illuminate\\Console\\Command;\n\nclass DispatchJob extends Command\n{\n    protected $signature = 'job:dispatch {job}';\n\n    \u002F**\n     * The console command description.\n     *\n     * @var string\n     *\u002F\n    protected $description = 'Dispatch job';\n\n    public function __construct()\n    {\n        parent::__construct();\n    }\n\n    public function handle()\n    {\n        $class = '\\\\App\\\\Jobs\\\\' . $this->argument('job');\n        dispatch(new $class());\n    }\n}\n",[86,614,615,619,623,628,632,637,641,646,650,655,659,664,669,674,679,684,689,693,697,701,706,710,714,718,722,727,732,736],{"__ignoreMap":88},[123,616,617],{"class":125,"line":126},[123,618,129],{},[123,620,621],{"class":125,"line":132},[123,622,142],{"emptyLinePlaceholder":141},[123,624,625],{"class":125,"line":138},[123,626,627],{},"namespace App\\Console\\Commands;\n",[123,629,630],{"class":125,"line":145},[123,631,142],{"emptyLinePlaceholder":141},[123,633,634],{"class":125,"line":151},[123,635,636],{},"use Illuminate\\Console\\Command;\n",[123,638,639],{"class":125,"line":157},[123,640,142],{"emptyLinePlaceholder":141},[123,642,643],{"class":125,"line":163},[123,644,645],{},"class DispatchJob extends Command\n",[123,647,648],{"class":125,"line":169},[123,649,189],{},[123,651,652],{"class":125,"line":175},[123,653,654],{},"    protected $signature = 'job:dispatch {job}';\n",[123,656,657],{"class":125,"line":180},[123,658,142],{"emptyLinePlaceholder":141},[123,660,661],{"class":125,"line":186},[123,662,663],{},"    \u002F**\n",[123,665,666],{"class":125,"line":192},[123,667,668],{},"     * The console command description.\n",[123,670,671],{"class":125,"line":198},[123,672,673],{},"     *\n",[123,675,676],{"class":125,"line":203},[123,677,678],{},"     * @var string\n",[123,680,681],{"class":125,"line":209},[123,682,683],{},"     *\u002F\n",[123,685,686],{"class":125,"line":215},[123,687,688],{},"    protected $description = 'Dispatch job';\n",[123,690,691],{"class":125,"line":221},[123,692,142],{"emptyLinePlaceholder":141},[123,694,695],{"class":125,"line":227},[123,696,206],{},[123,698,699],{"class":125,"line":232},[123,700,212],{},[123,702,703],{"class":125,"line":238},[123,704,705],{},"        parent::__construct();\n",[123,707,708],{"class":125,"line":243},[123,709,224],{},[123,711,712],{"class":125,"line":249},[123,713,142],{"emptyLinePlaceholder":141},[123,715,716],{"class":125,"line":254},[123,717,235],{},[123,719,720],{"class":125,"line":371},[123,721,212],{},[123,723,724],{"class":125,"line":376},[123,725,726],{},"        $class = '\\\\App\\\\Jobs\\\\' . $this->argument('job');\n",[123,728,729],{"class":125,"line":381},[123,730,731],{},"        dispatch(new $class());\n",[123,733,734],{"class":125,"line":386},[123,735,224],{},[123,737,738],{"class":125,"line":392},[123,739,257],{},[10,741,742],{},"このstack overflowの記事を引用しています。ありがとうございます。",[594,744,599,746],{"className":745},[597,598],[33,747,749],{"href":748,"target":36},"https:\u002F\u002Fstackoverflow.com\u002Fquestions\u002F43357472\u002Fhow-to-manually-run-a-laravel-lumen-job-using-command-line\u002F51045851","StackOverflow How to manually run a laravel\u002Flumen job using command line\n    ",[10,751,752,753,756,757,760,761,763],{},"このファイルをapp\u002FConsole\u002FCommands 配下に作成します。Console\u002FCommands 配下では",[86,754,755],{},"php artisan"," を用いたコマンドを自分で作成することができます。ここでは ",[86,758,759],{},"php artisan job:dispatch JOB_NAME"," と唱えればJOB_NAMEで書かれたジョブクラスの",[86,762,262],{},"を実行してくれます。",[10,765,766],{},"これを用いて以下のように唱えます。",[79,768,771],{"className":769,"code":770,"language":84},[82],"php artisan job:dispatch CheckNotFoundUrls\n",[86,772,770],{"__ignoreMap":88},[10,774,775],{},"するとprintなどを一時的につけて、結果を見ると無事に404のツイートの数が検知され、削除を実行してくれました。",[72,777,778],{"id":778},"ジョブが実行される時間を定める",[10,780,781,782,785],{},"それでは次に上で作成したジョブをスケジューラーに登録します。スケジューラーで実行させるジョブは ",[86,783,784],{},"app\u002FConsole\u002FKernel.php"," にジョブインスタンスを作って実行します。",[10,787,788,790,791,794,795,798,799,801],{},[86,789,784],{}," には ",[86,792,793],{},"schedule()"," というメソッドがあり ",[86,796,797],{},"php artisan run:schedule"," を実行すると ",[86,800,793],{}," が実行されます。",[10,803,804,805,808],{},"先ほどのジョブクラスをこの",[86,806,807],{},"Kernel.php","で使えるようにして、ジョブインスタンスを作成します。",[79,810,812],{"className":117,"code":811,"language":119,"meta":88,"style":88},"\u003C?php\n\nnamespace App\\Console;\n\nuse Illuminate\\Console\\Scheduling\\Schedule;\nuse Illuminate\\Foundation\\Console\\Kernel as ConsoleKernel;\nuse App\\Jobs\\CheckNotFoundUrls; \u002F\u002Fここで先ほど作成したジョブクラス\nuse DateTime;\n\nclass Kernel extends ConsoleKernel\n{\n    \u002F**\n     * The Artisan commands provided by your application.\n     *\n     * @var array\n     *\u002F\n    protected $commands = [\n        \u002F\u002F\n    ];\n\n    \u002F**\n     * Define the application's command schedule.\n     *\n     * @param  \\Illuminate\\Console\\Scheduling\\Schedule  $schedule\n     * @return void\n     *\u002F\n    protected function schedule(Schedule $schedule)\n    {\n        \u002F\u002F実行するjobをキューに投入する。そして実行時間を設定。\n        $schedule->job(new CheckNotFoundUrls(),'checkUrl')->diary()\n    }\n}\n",[86,813,814,818,822,827,831,836,841,846,851,855,860,864,868,873,877,882,886,891,895,900,904,908,913,917,922,927,931,936,940,945,950,954],{"__ignoreMap":88},[123,815,816],{"class":125,"line":126},[123,817,129],{},[123,819,820],{"class":125,"line":132},[123,821,142],{"emptyLinePlaceholder":141},[123,823,824],{"class":125,"line":138},[123,825,826],{},"namespace App\\Console;\n",[123,828,829],{"class":125,"line":145},[123,830,142],{"emptyLinePlaceholder":141},[123,832,833],{"class":125,"line":151},[123,834,835],{},"use Illuminate\\Console\\Scheduling\\Schedule;\n",[123,837,838],{"class":125,"line":157},[123,839,840],{},"use Illuminate\\Foundation\\Console\\Kernel as ConsoleKernel;\n",[123,842,843],{"class":125,"line":163},[123,844,845],{},"use App\\Jobs\\CheckNotFoundUrls; \u002F\u002Fここで先ほど作成したジョブクラス\n",[123,847,848],{"class":125,"line":169},[123,849,850],{},"use DateTime;\n",[123,852,853],{"class":125,"line":175},[123,854,142],{"emptyLinePlaceholder":141},[123,856,857],{"class":125,"line":180},[123,858,859],{},"class Kernel extends ConsoleKernel\n",[123,861,862],{"class":125,"line":186},[123,863,189],{},[123,865,866],{"class":125,"line":192},[123,867,663],{},[123,869,870],{"class":125,"line":198},[123,871,872],{},"     * The Artisan commands provided by your application.\n",[123,874,875],{"class":125,"line":203},[123,876,673],{},[123,878,879],{"class":125,"line":209},[123,880,881],{},"     * @var array\n",[123,883,884],{"class":125,"line":215},[123,885,683],{},[123,887,888],{"class":125,"line":221},[123,889,890],{},"    protected $commands = [\n",[123,892,893],{"class":125,"line":227},[123,894,218],{},[123,896,897],{"class":125,"line":232},[123,898,899],{},"    ];\n",[123,901,902],{"class":125,"line":238},[123,903,142],{"emptyLinePlaceholder":141},[123,905,906],{"class":125,"line":243},[123,907,663],{},[123,909,910],{"class":125,"line":249},[123,911,912],{},"     * Define the application's command schedule.\n",[123,914,915],{"class":125,"line":254},[123,916,673],{},[123,918,919],{"class":125,"line":371},[123,920,921],{},"     * @param  \\Illuminate\\Console\\Scheduling\\Schedule  $schedule\n",[123,923,924],{"class":125,"line":376},[123,925,926],{},"     * @return void\n",[123,928,929],{"class":125,"line":381},[123,930,683],{},[123,932,933],{"class":125,"line":386},[123,934,935],{},"    protected function schedule(Schedule $schedule)\n",[123,937,938],{"class":125,"line":392},[123,939,212],{},[123,941,942],{"class":125,"line":398},[123,943,944],{},"        \u002F\u002F実行するjobをキューに投入する。そして実行時間を設定。\n",[123,946,947],{"class":125,"line":404},[123,948,949],{},"        $schedule->job(new CheckNotFoundUrls(),'checkUrl')->diary()\n",[123,951,952],{"class":125,"line":410},[123,953,224],{},[123,955,956],{"class":125,"line":416},[123,957,257],{},[10,959,960,963,964,967],{},[86,961,962],{},"$schedule->()","の第一引数にジョブインスタンス、第二引数にキュー名を入れます。キューというのは実行登録したジョブを一時的にためておく場所のことです。私もよくわかっていませんが、ジョブの実行数を制限したり、どの順に実行をしていくかなども決められるそうです。とりあえず今回は404チェック用のジョブキューとして ",[86,965,966],{},"checkUrl"," としておきます。",[10,969,970,971,974,975,979],{},"そして行末に",[86,972,973],{},"diary()","などがありますが、",[33,976,978],{"href":977,"target":36},"https:\u002F\u002Flaravel.com\u002Fdocs\u002F7.x\u002Fscheduling#schedule-frequency-options","公式ドキュメント","の通り実行する時間を指定できます。cron通りの時刻設定もできますし、1日ごと、週末ごと、月初に１回などよくある時間設定はメソッドを指定すれば設定できます。",[10,981,982,983,985],{},"上記のコードでは",[86,984,973],{},"なので毎日0時になるとジョブが実行されます。",[72,987,988],{"id":988},"cronを設定する",[10,990,991,992,995],{},"ジョブを定義し、その時間も定めたのでもう自動で実行されそうな気もしますが、これだけでは動きません。指定の時間になったら特定のコマンドつまり",[86,993,994],{},"php artisan schedule:run"," を唱えてphpを動かさないといけません。",[10,997,998,999,1001],{},"ここはサーバー(Linux)のcronというものを用いて、常に",[86,1000,994],{}," を唱えるように設定します。cronとは時間設定基づいてコマンドを定期的に唱えてくれるUNIX系のOSに入っているプログラムです。",[10,1003,1004],{},"cronに以下の設定を記述します。",[79,1006,1009],{"className":1007,"code":1008,"language":84},[82],"~ $ crontab -e #これでcronの設定ファイルを編集できます。\n",[86,1010,1008],{"__ignoreMap":88},[79,1012,1015],{"className":1013,"code":1014,"language":84},[82],"* * * * * cd \u002Fpath-to-your-project && php artisan schedule:run >> \u002Fdev\u002Fnull 2>&1\n",[86,1016,1014],{"__ignoreMap":88},[10,1018,1019,1020,1023],{},"行の最初にある",[86,1021,1022],{},"(* * * * *)","は時間の設定をしています。この書き方の場合は毎分実行するようになります。つまり毎分Laravelのスケジューラーを叩いているだけなんです。",[10,1025,1026,1029],{},[86,1027,1028],{},"cd \u002Fpath-to-your-project"," はLaravelプロジェクトのルートへ移動するという意味です。そしてプロジェクトルートでスケジュールを実行します。",[17,1031,1032],{"id":1032},"以上でスケジューラー実装完了",[10,1034,1035],{},"以上でスケジューラーの実装が完了しました。今回のような404を毎日チェックしたり、定期的に時間を設定して行う処理などをアプリ内で行う機能を実装する時に便利です。",[10,1037,1038],{},"ユーザーごとに実行するかどうか、いつ実行するかの情報を格納しておくことで一般ユーザーが定期的な処理を行う処理を設定することもできます。",[1040,1041,1042],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":88,"searchDepth":138,"depth":138,"links":1044},[1045,1046,1047,1055],{"id":19,"depth":132,"text":19},{"id":44,"depth":132,"text":44},{"id":70,"depth":132,"text":70,"children":1048},[1049,1052,1053,1054],{"id":74,"depth":138,"text":74,"children":1050},[1051],{"id":95,"depth":145,"text":95},{"id":110,"depth":138,"text":111},{"id":778,"depth":138,"text":778},{"id":988,"depth":138,"text":988},{"id":1032,"depth":132,"text":1032},[1057],"devstack","2020-07-11","md",null,{},"\u002Farticles\u002Ftweet-check-laravel-jpb-scheduler",{"title":5,"description":5},"articles\u002Ftweet-check-laravel-jpb-scheduler",[1066],"laravel","_mix\u002Flaravel-schedular-768x768.jpg","4qvDlezjHnhP7U6K9l6HlE2ilA1kcrRrD19TfDyuMNc",1780987152549]