帰ってきた!平成最後のオレオレフレームワークの作り方

271fad8d53cd1f12f2b4b6d38e3d7bd3?s=47 uzulla
March 31, 2019

 帰ってきた!平成最後のオレオレフレームワークの作り方

PHPerKaigi 2019
2019/03/31 Track A
uzulla

271fad8d53cd1f12f2b4b6d38e3d7bd3?s=128

uzulla

March 31, 2019
Tweet

Transcript

  1. 3.
  2. 4.
  3. 5.
  4. 6.
  5. 7.
  6. 8.
  7. 10.

    #ΦϨΦϨϑϨʔ ϜϫʔΫ ͱ͸ʁ » (ϑϨʔϜϫʔΫ(ҎޙFW)ͷઆ໌ ͸͠·ͤΜ) » ࣗ෼Ͱ࡞ͬͨFW -> ΦϨΦϨFW

    » ݱ୅Ͱ͸ϑϨʔϜϫʔΫ͸ ʮબͿʯ΋ͷ » ੲ͸ϑϨʔϜϫʔΫ͸ͳ͔ͬͨ » ΈΜͳϑϨʔϜϫʔΫΛͭͬ͘ ͨ΋Μ͡Ό…ʢॾઆ͋Γ·͢ʣ
  8. 12.

    ͦ΋ͦ΋ɺFWͷར఺͕େମͳ͘ͳΔ » ੜ࢈ੑɺ඼࣭ͷ޲্ » ݱ࣮Ͱςετ͞ΕͨϥΠϒϥϦ » ϕλʔϓϥΫςΟεͳख๏ » େن໛։ൃͷجૅ »

    υΩϡϝϯτͷ੔උ » ΤϯδχΞͷֶश » ΤίγεςϜ » ར༻ऀͷϒϩά΍࣭໰αΠτͰͷճ౴ » ϓϥάΠϯͷ๛෋͞
  9. 19.

    ंྠ͸࠶ൃ໌͢Δ΋ͷ » ਓؾͷंྠ͸།Ұ࠷ߴͷंྠͰ͸ͳ͍ » ͋ͳͨͷυϝΠϯʹϚον͍ͯ͠ͳ͍͔΋ » ࠷ߴ͸৭ʑ༗Δ » ྫɿάϦοϓྗ͕௿ͯ͘΋ɺύϯΫ͠ͳ͍λΠϠ͕͍͍ »

    ྫɿτϥϯΫʹੵΉۓٸ༻͔ͩΒখ͍͞ͷ͕ྑ͍ » ଞਓ͕࠷ߴͱײ͡Δඞཁ͸ͳ͍ » ʮ˓˓ͷFWͷ͜Ε͸࠷ߴʂʯ͸ΦϨΦϨͱ૬൓͠ͳ͍ » श࡞ͳΒʮࣗ෼ͷʯ࠷ߴΛ໨ࢦ͢ͱΑ͍
  10. 22.

    Α͋͘ΔਥೋපɺREST(ful)ฤ » (APIʹ͓͍ͯ͸)REST෩ͳύεύϥϝʔλʹ΋ٙ໰͕͋Δ » /book/1ͱ͔΍Δͱɺཪଆʹbookςʔϒϧͷid1͕ඞਢͱ͔ ͋Γ͕ͪ » ϦιʔεʢDBͷςʔϒϧͱີ݁߹ʣࢤ޲͸ݶք͕͋ΔͷͰ ͸ »

    ʮ_methodͬͯͳΜͩΑϫϩλɺେࣗવʹPATCHͳ͍΍Ζʯ » ʮഁյ͕ͳ͍ͳΒGETʯͷ༥௨ͷ͖͔ͳ͞ » ߴ౓Ͱগͳ͍ίʔυΑΓɺόΧͰ΋Θ͔Δྔͷ͋Δίʔυ ͷ΄͏͕ͳΜͱ͔ͳΓ΍͍͢ʢओ؍ʣ
  11. 31.

    …ʢॳ৺ऀʹ͸ʣແཧͰ͸ʁʁ » ࢖͏ػೳ͕ࣗ෼Ͱͭ͘Εͳ͍ʁ » ΦϨΦϨ͔ͩΒఘΊΔʁ » SUGOIϥΠϒϥϦΛ࢖͏ʁ » ࢓༷ଆΛม͑Δʁ »

    खஈ͸ͳΜͰ΋ΞϦͩΑɺͩͬͯΦϨΦϨͩ΋Μ » (ྫྷ੩ʹͳͬͯ͜͜ͰΦϨΦϨ࡞੒ΛࢭΊΔ((?ʁ
  12. 32.

    ॳ৺ऀ͕ݟΔ΂͖ϑϨʔϜϫʔΫ » ݹ͍Μ͚ͩͲɺFWͷίʔυΛಡΜͩ͜ͱ͕ͳ͍ͳΒ·ͣ͸͜ͷลΓΛ ಡΉͱྑ͛͞ » ੿࡞ͷOre1,2 » https://github.com/uzulla/Ore » https://github.com/uzulla/Ore2

    » Limonade » Slim Frameworkͷ2.xܥ » phpixieͷ2.xܥ » ͜ΕΒ͸ൺֱతಡΈ΍͍͚ͩ͢Ͱ (Ұൠతʹ͸)࠷ߴͷ'8Ͱ͸ͳ͍ɺ೦಄ʹ͓͘͜ͱ
  13. 40.

    தڃ » ϩάΠϯʢSessionʣ͕͋Δ΋ͷ » αΠϯΞοϓ͕͋Δ΋ͷ » Delete FlagͰͷ࡟আ » (Del

    flag͕ྑ͍ͱݴ͏͜ͱͰ͸ͳ͍ʣ » ೚ҙͷΧϥϜͰͷ߱ॱɾঢॱιʔτ » N+1తͳɺෳ਺ͷςʔϒϧΛ݁߹͢Δඞཁ͕͋Δ΋ͷ » Ϣʔβʔը໘ͱ෼཭͞Εͨ؅ཧը໘͕͋Δ΋ͷ » = ձһ੍ܝࣔ൘ʁ
  14. 44.

    ΦϨΦϨFWͱηΩϡϦςΟʹ͍ͭͯ » ΦϨΦϨ͔ͩΒͱආ͚ͯ௨Δ΂͖Ͱ͸ͳ͍ » ݹ͍FWͰΑ͋ͬͨ͘ϠόΠྫ » ύϥϝʔλΛͦͷ··ม਺ʹల։͢Δͱศརʂ » Ϣʔβʔͷೖྗ͸֬ೝը໘ͰνΣοΫͨ͠͠ɺhiddenΛͦͷ··Ϟ σϧʹ͍ΕΕ͹ศརʂ

    » อଘ͢Δςʔϒϧ໊ΛϢʔβʔೖྗ͔Βϓϧμ΢ϯͰબϕΔ͚Ͳɺϓ ϧμ΢ϯͰબ୒ࢶݻఆ͔ͩΒSQL૊Έཱͯʹ͔ͭ͑͹ศརʂ » URL͔ΒΫϥε΍ϝιου໊ΛҾͬுΕΔͱϧʔλʔෆཁͰศརʂ » ௕ظؒɺߏ଄Խ͞Ε໊ͨલ΍ΞΠίϯ౳ͷϩάΠϯ৘ใ഑ྻΛอଘ͠ ͍͚ͨͲserialize()Ͱจࣈྻʹͯ͠Cookieʹ͍ΕΔͱศརʂ
  15. 45.

    » ݹ͍ίʔυ͸ ౰࣌͸ΞϦͩͬͨ ϠόΠίʔυ͕͍ͬͺ ͍ɺਅࣅΔͱةݥ » Ͱ΋ɺʮ͜ͷίʔυ͸ةݥʯͱ͔Θ͔Βͳ͍͓… » ݹ͍ઃܭ͚ͩͲόʔδϣϯ͕ͪΌΜͱ্͕͍ͬͯΔ FWΛਅࣅ͠Α͏ʢ͋Δͷ͔ʁʣ

    » ษڧ͠·͠ΐ͏ » ಙؙຊ » աڈͷCVEΛͲΜͲΜಡΉ » (େม͚ͩͲ࣮ྫ͕ͲΜͲΜΘ͔Δ͠ɺલޙͷVerUpΛ ୳Ε͹࣮ࡍͷରࡦίʔυ͕Έ͔ͭΓɺ͓͢͢Ί)
  16. 54.

    ࡢࠓͷଟ༷ͳ؀ڥ΁ͷରԠ » όον΍ɺLambdaɺSwooleɾReactPHPΈ͍ͨͳมΘ࣮ͬͨߦ ؀ڥ » άάΔͱͰͯ͘Δɺී௨ͷFWͱͭͳ͙ʮΞμϓλʯͱ͍͏΍ͭ ͸ » ORM΍ίϯϑΟάɺDI͕࿩Λ΍΍͕ͪ͘͜͠͠ »

    ʮFW͔Β੾Γग़ͤ·͢Αʈʈʯʮ৴͡ͳ͍Αʂʯ » ʮ͏·͚͍ͤͬ͘͢Ε͹͍ͩ͡ΐ͏Ϳʯ͚ͩͲFWͷҙຯ͕ബ ͘ͳΔ » POPOʢPlain old PHP ObjectʣͩͱେମͲ͜ʹͰ΋͍͚࣋ͬͯΔ
  17. 56.

    » (Ұ෦ͷҊ݅Ͱ)FW΁ͷఘΊ » ڋ൱͍ͨ͠Θ͚Ͱ͸ͳ͍͕ɺൣғΛݮΒ͔͢ʁ » ͔ͦ͠͠ΕͰ͸FWͷҙ͕ٛ… » …Α͠ɺΦϨΦϨ͔ͳʂʢ࿦ཧͷඈ༂ʣ » (͜Ε͸ੈͷதͷΦϨΦϨFWͭ͘Δਓؒͷయܕత

    ͳݴ͍༁Ͱ͢ɻಉ྅͕͜͏͍͏͜ͱΛݴ͍ग़ͨ͠ ΒԥͬͯࢭΊ·͠ΐ͏) » (ಉ྅Ͱͳ͍ͳΒ(ྫ͑͹๻͕ݴ͍ग़ͨ͠Β)ɺͦͬ ͱ۪ஒΛฉ͍͍ͯ͋͛ͯͩ͘͞)
  18. 59.

    Ore3 FW » https://github.com/uzulla/Ore3 » (Ore1,2͸લ࡞ͬͨͷͰ(҆қ)) » ༻్ɺJSON API »

    ໨ࢦ͢ॴ » ΤϥʔϋϯυϦϯά » ୯७͞ » αόΠϒੑʢओ؍ » ςετ͠΍͢͞
  19. 61.

    ࠓճͷΦϨΦϨFWʹ͓͚Δࡦ » ʮίʔυͷ֎ʯʹཔΔ͕ɺੜ੒͸͠ͳ͍ » *%&ͳͲπʔϧͷαϙʔτʢิ׬ɺ੩తղੳɺϦϑΝΫλػೳʣ Λ࠷େݶʹड͚ΒΕΔΑ͏ʹ » ੩తղੳ(ྫ:IDEͷInspect Code)ͷ׆༻ »

    ૉ๿ͳςετͷίετΛԼ͛Δ » ๨Εͯ΋͓͍͔͚ΒΕΔΑ͏ʹ » ҉໧ͷίʔυΛ΁Β͢(഑ྻΛҾ͖ճ͞ͳ͍ɺΫϥε΍ϓϩύςΟ Λ࡞Δ) » VCSͱͷ਌࿨ੑΛ্͛Δ(#MBNF͠΍͘͢ɺ%J⒎ΛΈ΍͘͢ ओ؍ )
  20. 67.

    ϑΝΠϧߏ଄ !"" composer.json !"" api_real.php !"" shell.php !"" lib #

    $"" MyApp # !"" Api # # !"" Action # # !"" Request # # $"" Response # !"" Model # !"" Repo # $"" Service $"" tests # $"" testTest.php $"" vendor
  21. 68.

    !"" composer.json !"" api_real.php !" !"" shell.php !"" lib #

    $"" MyApp # !"" Api # # !"" Action # # !"" Request # # $"" Response # !"" Model # !"" Repo # $"" Service $"" tests # $"" testTest.php $"" vendor
  22. 69.

    api_realͷྲྀΕ » 0. Τϥʔϋϯυϥొ࿥ » 1. σΟεύον » 2. ϦΫΤετΦϒδΣΫτੜ੒(*Request.php)

    » 3. ΞΫγϣϯ࣮ߦ(*Action.php) » 4. (ΞΫγϣϯͷதͰ)ϨεϙϯεΦϒδΣΫτੜ ੒(*Response.php) » 5. ϨεϙϯεΦϒδΣΫτΛૹ৴
  23. 70.

    ୯७Խͨ͠΋ͷ set_error_handler(/* ... */); // 0. Τϥʔϋϯυϥ try{ if($_SERVER['REQUEST_URI']='/'){ //

    1. σΟεύν $req = new SomeRequest($_POST); // 2. reqੜ੒ $res = SomeAction::run($req); // 3. resੜ੒ }else{ /* ... */ } } // $res 4. Ϩεϙϯε $res->writeHeader($http_origin_header); // 5. ૹ৴ $res->writeBody();// 5. ૹ৴
  24. 71.

    0. Τϥʔϋϯυϥઃఆ౳-1 (গʑ؆ུԽͯ͠·͢ set_error_handler(function ($severity, $message, $file, $line) { throw

    new ErrorException($message, 0, $severity, $file, $line); }); register_shutdown_function(function () { $error = error_get_last(); if (!is_array($error) || !($error['type'] & (E_ERROR|E_PARSE|E_USER_ERROR|E_RECOVERABLE_ERROR))) { return; } // ਖ਼ৗऴྃܥ error_log("Uncaught Error: ..."); // Ωϟονͨ͠ΤϥʔΛϩά http_response_code(500); // response error header("Content-type: application/json"); echo json_encode([ "code" => 500, "message" => "internal server error" ]); }); // ࣍ͷεϥΠυʹଓ͘
  25. 72.

    0. Τϥʔϋϯυϥઃఆ౳-2 (গʑ؆ུԽͯ͠·͢ try { //લͷεϥΠυ͔Βଓ͘ ob_start(); require(__DIR__ . "/vendor/autoload.php");

    // ========= ‼ ͜͜ʹΞϓϦίʔυ ‼ ============= ob_end_flush(); } catch (\Throwable $e) { // Uncaught Exception $error_class_name = get_class($e); error_log("Uncaught Exception {$error_class_name}: ..."); // ྫ֎Λϩά http_response_code(500); // response error header("Content-type: application/json"); echo json_encode([ "code" => 500, "message" => "internal server error" ]); }
  26. 74.

    Output bufferͰ༧ظͤ͵ग़ྗΛ཈੍ ob_start();// Output bufferingΛ։࢝ $res = MyApp(); // ͜͜Ͱ͸ͳʹ΋ग़ྗ͠ͳ͍ɺ͋ͬͨΒΤϥʔ͔Կ͔

    $something = ob_get_contents(); // ҙਤ͠ͳ͍ग़ྗ͕͋ͬͨΒϩά΁ if (strlen($something) > 0) { error_log($something); } ob_end_clean(); // (্Ͱϩάʹग़͍ͯ͠ΔͷͰɺࣺͯΔ) ob_start(); // ͔͜͜Β࣮ࡍʹૹ৴ $res->writeHeader($http_origin_header); $res->writeBody(); ob_end_flush();
  27. 75.

    » obଟ༻ͷ໨త͸ » ʮΤϥʔ࣌ʹ่Εͨग़ྗΛͩ͞ͳ͍͜ͱʯ » ʮ૝ఆ͍ͯ͠ͳ͍ग़ྗΛޡͬͯग़͞ͳ͍͜ͱʯ » ʮޡͬͯ200Λฦ͞ͳ͍͜ͱʯ » PHP͸ɺΤϥʔ౳Ͱ༧ظͤ͵ग़ྗ͕ग़Δͱૹ৴͞Εͯ͠·

    ͏ɺ͢Δͱϔομʔ΋ૹΕͳ͍ɺ͜Ε͸Web APIͩͱඍົ » ͳͷͰɺϨεϙϯεΦϒδΣΫτͰͷૹ৴Ҏ֎͸ɺશ෦ྫ ֎తͳ΋ͷͱͯ͠obͰ֬อͯ͠ϩάߦ͖ʹ͢Δɻ » var_dump()͕print debugͰ࢖͑Δͧʂศརʂ
  28. 76.

    ࠶ܝࡌʣ୯७Խͨ͠΋ͷ set_error_handler(/* ... */); try{ if($_SERVER['REQUEST_URI']='/'){ $req = new SomeRequest($_POST);

    $res = SomeAction::run($re }else{ /* ... */ } } $res->writeHeader($http_origin_header); $res->writeBody();
  29. 77.

    1. σΟεύονͱ͍͏໊લͷifจ $request_uri = $_SERVER['REQUEST_URI']; $method = $_SERVER['REQUEST_METHOD']; $http_origin_header =

    $_SERVER["HTTP_ORIGIN"] ?? null; $base_uri = "/api"; $path = substr($request_uri, strlen($base_uri)); $path = explode("?", $path, 2)[0]; if ($method === "OPTIONS" && !is_null($http_origin_header)) { $res = new \MyApp\Api\Response\CORSPreFlightResponse(); } else if ($method === "GET" && $path === "/hello") { $req = new \MyApp\Api\Request\HelloRequest($_GET); $res = \MyApp\Api\Action\HelloAction::run($req); } else { $res = new \MyApp\Api\Response\Response(); $res->code = 404; }
  30. 78.

    » ʮ̎̌೥લ͔ͳʁʯ ʮύεύϥϝʔλΛ࢖Θͳ͚Ε͹͑͑Μ΍ʯ » ʢRESTfulͷ͜ͱΛߟ͍͑ͯͳ͍ʣ » ඞཁʹͳΕ͹ϧʔλʔΛೖΕΕ͹͍͍Ͱ͠ΐ » ʮpreg_matchͰ΋࣮ݱͰ͖ΔͰ͠ΐʢʁʁʯ »

    ྑ͍ϧʔλʔΛ͍Εͯ΋ɺେ఍ଟػೳΛ࢖Θͳ͍… » ελοΫτϨʔε͕ബ͍ʢϩά͕ݟ΍͍͢ʣ » ಥવݱΕͨ୹ظతͳṖ࢓༷ͷ૬ख͕Ͱ͖ΔɺϦΫΤε τΛ٧Ίସ͑ͯ͠ΞΫγϣϯΛίʔϧ͢Δͱ͔
  31. 79.

    ࠶ܝࡌʣ୯७Խͨ͠΋ͷ set_error_handler(/* ... */); // 0. Τϥʔϋϯυϥ try{ if($_SERVER['REQUEST_URI']='/'){ //

    1. σΟεύν $req = new SomeRequest($_POST); // $res = SomeAction::run($req); // 3. resੜ੒ }else{ /* ... */ } } // $res 4. Ϩεϙϯε $res->writeHeader($http_origin_header); // 5. ૹ৴ $res->writeBody();// 5. ૹ৴
  32. 80.

    !"" composer.json !"" api_real.php !"" shell.php !"" lib # $""

    MyApp # !"" Api # # !"" Action # # !"" Request !" # # $"" Response # !"" Model # !"" Repo # $"" Service $"" tests # $"" testTest.php $"" vendor
  33. 81.

    2. ϦΫΤετΦϒδΣΫτ class HelloRequest extends Request implements RequestInterface { use

    ValidateRegexTrait; public $name; public function __construct(array $list) { $el = []; $result = $this->validateWithRegex($el, $list, 'name', "/\A[a-zA-Z0-9]{1,32}\z/u", true); $el = $result->error_list; $this->name = $result->val; $this->_error_list = $el; } }
  34. 82.

    » جຊ1ϧʔτʹ͖ͭɺ1Ϋϥε » ίϯετϥΫλʹ഑ྻ($_POST,$_COOKIE౳ʣΛ౉͢ » εʔύʔάϩʔόϧ͸ɺ͜ͷޙ͸Ұ੾࢖Θ͵੤͍ » ςετ࣌ʹָˍͦΕ͕ͳ͍ಛघ؀ڥͱͭͳ͗΍͘͢ » Ϋϥε͸༧ఆ͞ΕΔύϥϝλ෼ͷϓϩύςΟม਺Λ࣋ͭ

    » ϗϫΠτϦετͰ໌ࣔత » ޙड़ͷΞΫγϣϯ͸ɺϓϩύςΟม਺Λ௚઀৮Δ » *%&Ͱิ׬ɾ*OTQFDU͠΍͍͢ » ΧϓηϧԽ͕໨తͰ͸ͳ͍ͷͰpublic » ̍ʙׂ̎Ͱ࢖ΘΕΔϝιου͸ɺܧঝͰͳ͘Trait
  35. 83.

    !"" composer.json !"" api_real.php !"" shell.php !"" lib # $""

    MyApp # !"" Api # # !"" Action !" # # !"" Request # # $"" Response # !"" Model # !"" Repo # $"" Service $"" tests # $"" testTest.php $"" vendor
  36. 84.

    3. ΞΫγϣϯ class HelloAction { public static function run(HelloRequest $req):

    ResponseInterface { if (!$req->isValid()) { return new ErrorResponse($req, 400, "bad request", $req->getErrorList()); } $name_san = NameSanService::getByName($req->name); return new HelloSuccessResponse($name_san); } }
  37. 86.

    ࠶ܝࡌʣ୯७Խͨ͠΋ͷ set_error_handler(/* ... */); try{ if($_SERVER['REQUEST_URI']='/'){ $req = new SomeRequest($_POST);

    $res = SomeAction::run($req); // ͜͜·ͰऴΘͬͨ }else{ /* ... */ } } // $res 4. Ϩεϙϯε ! $res->writeHeader($http_origin_header); // 5. ૹ৴ $res->writeBody();// 5. ૹ৴
  38. 87.

    !"" composer.json !"" api_real.php !"" shell.php !"" lib # $""

    MyApp # !"" Api # # !"" Action # # !"" Request # # $"" Response !" # !"" Model # !"" Repo # $"" Service $"" tests # $"" testTest.php $"" vendor
  39. 88.

    4. Ϩεϙϯε class HelloSuccessResponse extends Response implements ResponseInterface { public

    $code = 200; public $name_san = null; public function __construct(NameSan $name_san) { $this->code = 200; $this->name_san = $name_san; } public function getBody(): string { return json_encode([ 'code' => $this->code, 'name_with_san' => $this->name_san->name ]); } public function writeBody(): void { echo $this->getBody(); } }
  40. 89.

    » ϨεϙϯεΦϒδΣΫτ΋ɺίϯετϥΫλͰʮ࣮ࡍʹඞཁͳ Ҿ਺ʯΛड͚औΔ » ͜͜Ͱ͸εΧϥʔͰ͸ͳ͘ɺϞσϧͷΫϥε໊ͰܕΛࢦఆ͢Δ » ϓϩύςΟʢ͜͜Ͱ͸$name_san)ʹ͸ϞσϧͷΠϯελϯε ΛೖΕ͓ͯ͘ » ֤छϞσϧΛϨεϙϯε·Ͱ࣋ͪࠐΉ͓࡞๏ͩͱɺtoArray

    લ͕ݟΕΔͷͰJSONΛ૬खʹจࣈྻςετͤͣʹࡁΉ » मਖ਼ൣғ͕ڱ͍ͷͰɺίϛοτϩά͔Β௥͍͔͚΍͍͢ ʢओ؍ʣ » ͜ͷ""ͱ͍͏ۭจࣈྻ్͕தͷΩϟετϛε͔ɺͦ͏Ͱͳ ͍͔൑அ͠΍͍͢ɺ౳ɻ
  41. 90.

    » get/writeBody()Ͱॴఆͷܗʹม׵͢ΔͷΛखͰॻ͘ » Ϩεϙϯεͷܗ͸Ϟσϧͷߏ଄ʹґଘͤ͞ͳ͍ » (ఆܗͷdefinitionͷ৔߹ʔͨͱ͑͹UserInfoͱ͔ʔ͸Ͳ͔͜ʹ toArray()ͱ͔ϔϧύΛ࡞Δࣄ΋͋Δ) » ӡ༻ϑΣʔζʹ͸͍Δͱɺςʔϒϧ͸ม͑ͳ͍͚Ͳݟͤํ͸͔ΘΓ͕ͪ »

    ಥવඇਖ਼نͳΧϥϜ͕૿͑Δ࣌ʹɺϞσϧ΍ActionΛ͍͡Βͳͯ͘ ΋ResͰٵऩͰ͖Δ » ྫɿ͋ΔAPI͚ͩauthor͕CSVͰ΄͍͠ʢ΄͔͸഑ྻͳͷʹ…ʣ » ྫɿ͜ͷϨεϙϯε͸Ձ֨ΛӅ͍ͨ͠ɺΫϩʔϧ͞Εͨ͘ͳ͍ » ʮ͜ͷ࢓༷Ͱ߹ҙ͕ͱΕͨΜͩɺ͔ͩΒே·Ͱʹ࡞ͬͯʯ » ͋Δ͍͸ɺϞσϧ͕͔Θͬͯ΋API࢓༷ΛഁյͰ͖ͳ͍
  42. 92.

    4. Ϩεϙϯε(ϔομʔؔ࿈) ؆ུԽͯ͠·͢ class Response implements ResponseInterface { public $code;

    protected $header_list = [ "Content-type" => "application/json" ]; protected $cookie_list = []; public function __construct($code, $name){ $this->code = $code; } public function writeHeader(string $http_origin = null): void { foreach ($this->getHeader($http_origin) as $key => $val) { header(sprintf("%s: %s", $key, $val)); // ϔομૹ৴ } foreach ($this->cookie_list as $cookie) { CookieService::setCookie($cookie); // ΫοΩʔૹ৴ } http_response_code($this->code); // Ϩεϙϯείʔυηοτ /* ... */
  43. 93.

    » ϔομʔ΍ΫοΩʔ͸ಉ༷ʹϓϩύςΟʹʢ഑ྻͱ͠ ͯʣ΋ͭɻ » cookieͳΜ͔͸CookieͷϞσϧͰ͸͍͍ͬͯΔ » ςετͷͨΊʹ » ࣮ࡍʹwriteHeader͢Δ·Ͱ͸ૹ৴͠ͳ͍ »

    writeBodyಉ༷ʹɺ௚઀ϔομʔΛૹ৴͢Δίʔυ Λ͜͜ʹॻ͘ » Etag΍TTLͱ͔ɺPrivateɺno-cacheΈ͍ͨͳͷ΋ ॻ͘
  44. 94.

    5. api_realʹ΋Ͳͬͯૹ৴ set_error_handler(/* ... */); try{ if($_SERVER['REQUEST_URI']='/'){ $req = new

    SomeRequest($_POST); $res = SomeAction::run($req); }else{ /* ... */ } } $res->writeHeader($http_origin_header); // 5. ૹ৴ $res->writeBody();// 5. ૹ৴
  45. 97.

    ͭΒ͍ͱ͜Ζʢଞਓͷ૝૾ʣ » ίʔυ͕ଟ͍ » IDEͱͰ͔͍Ϟχλʔਪ঑ » मਖ਼Օॴ͕ଟ͍ » ҰൃͰόʔϯʂͬͯͳΒͳ͍ »

    IDE͔ͭͬͯͳ͍ͱॻ͖ଛ͕͡ଟ͍ » ϦϑΝΫλ΋IDE͕ͳ͍ͱͭΒ͍ » ແ৺Ͱॻ͖׵͑ΔࠜੑϓϨΠ
  46. 99.
  47. 100.

    Ore3 ·ͱΊ » ੩తղੳπʔϧ͔ΒཧղͰ͖ΔΑ͏ʹ » ܕΛՄೳͳݶΓॻ͘ » DIͳ͠ » ʮઃఆϑΝΠϧʯ͸࠷খݶʹ

    » ςετͷखؒΛ΁Β͢ » ೖྗͱग़ྗͷςετΛॻ͖΍͘͢ʢओ؍ʣ » PHPຊମͷػೳ͸ૉ௚ʹ͔ͭ͏
  48. 101.

    » ܽ఺͸΋ͪΖΜमਖ਼ൣғ͕޿͘ͳΔ͜ͱ » IDEͱςετͰࠜੑͰ۪௚ʹ࣏͢ » ςετ΍ܕͰɺ੔߹ੑΛ୲อ͍ͯ͘͠ » ʮ͜ΕFWͷҙຯ͋Δʁʯ » ͔͠͠मਖ਼࣌ؒΛݟੵ΋Γ͠΍͍͢ʢओ؍ʣ

    » 90%ͷཁ݅Λ̍࣌ؒͰ௚ͤΔ୅ΘΓʹ10%Ͱ͸ϋϚͬͯҰि ͔͔ؒΔΑΓɺ95%͕̍ప໷ͰऴΘͬͯ΄͍͠ʢओ؍ʣ » ಥવJOINͱ͔UNIONඞཁʹͳΔͱORMΉ͔ͣ͗ͯ͢͠ ʮSQL͔͔ͤͯ͘ΕʂʂʯͱͳΔʢษڧෆ଍ʣ » ORMͷΉ͔͍ͣ͠࢖͍ํ͸๨Ε͕ͪɺSQLͩͱ͙͢ʹΘ͔ Δ…ʢ࿝ਓʣ
  49. 102.

    » ʮδϟόͰ͍͍ͷͰ͸ʁʯʮsorry, I am PHPerʯ » ʮhackͰ͍͍ͷͰ͸ʁʯʮhackʹPHPStorm΄͠ ͍ʯ » Typed

    Properties͖ͯ΄͍͠ʂࠓ͙͢ʂ » Enum͖ͯ΄͍͠ʂࠓ͙͢ʂ » listड͚͍ͨ͠ʂδΣωϦΫεʂ » (େମ͜͏͍͏ͷHackʹ͋ΔΜͩΑͳΝ) » ·͋ɺࢲ͸PHPerͳͷͰ
  50. 106.
  51. 110.

    தڃऀ޲͚·ͱΊ » ͝ଘ஌ͷ௨Γ(?)ʮϝδϟʔͳFWΛ͔ͭͬͨΒສࣄղܾʯ͠ͳ͍͕ɺ ਏͯ͘΋͕Μ͹Δͷ͕ਖ਼͍͠ಓͩ » ͢Ͱʹ͋Δ஌ݟɾઃܭɾϥΠϒϥϦΛ୳͠ɺֶ΅͏ » ͨͩɺڝ߹ͱಉ͡Ͱڝ૪ͰউͯΔͷ͔ʁ » FWΛʮ৐Γ׵͑Δʯͱ͍͏ߦҝࣗମ͕͔ͳΓܦݧ͕ͨ·ΔͷͰἚͷ

    ಓΛΏ͜͏ » ΦϨΦϨFW͔Β୤٫Ͱ͖Δɺ͋Δ͍͸ΦϨΦϨFWʹҠߦͰ͖Δ ઃܭ » ʮࠓͲ͖͸͜Εͩʂʯͱ͔ࣖ೥૿ʹͳΔͱɺPHPerϥΠϑͷָ͠͞ ͕൒ݮͯ͠͠·͏ͧʂΈΜͳΦϨΦϨͯ͠Δʂ܅΋ΦϨΦϨ͠Α͏ʂ
  52. 113.

    ׬

  53. 116.

    σʔλ » Service, Repo, Modelͷࡾ૚ » Service: Model/RepoΛ·͙ͨॲཧɺτϥϯβΫ γϣϯΛఆٛͰ͖Δ »

    Repo: SQLΛॻ͘ʢʂʣɺModelΛฦͨ͠ΓɺӬଓ Խ » Model: ໨తͱͯ͠͸ߏ଄ମɺ͍ΖΜͳॴͰҾ਺ͷܕ ͱͯ͠ࢦఆɻ->isEqual($val)ɺ->toArray()ͱ͔ ΦϒδΣΫτΒ͍͠؆୯ͳϩδοΫ΋ؚΉ
  54. 117.

    Service !"" lib # $"" MyApp # !"" Api #

    # !"" Action # # !"" Request # # $"" Response # !"" Model # !"" Repo # $"" Service !" $"" tests # $"" testTest.php
  55. 118.

    class UserAccountService { public static function getByEmail(string $email, UserAccountRepo $repo=null):

    ?UserAccount { $repo = $repo ?? new UserAccountRepo(); return $repo->getByEmail($email); } public static function getByEmailAndPassword(string $email, string $password, UserAccountRepo $repo): ?UserAccount { $ua = static::getByEmail($email, $repo); if (is_null($ua)) return null; if (!password_verify($password, $ua->hashed_password)) return null; return $ua; } public static function update(UserAccount $ua, UserAccountRepo $repo = null): bool { $repo = $repo ?? new UserAccountRepo(); return $repo->update($ua); }
  56. 120.

    Repo !"" lib # $"" MyApp # !"" Api #

    # !"" Action # # !"" Request # # $"" Response # !"" Model # !"" Repo !" # $"" Service $"" tests # $"" testTest.php
  57. 121.

    class UserAccountRepo extends DB { public function getByEmail(string $email): ?UserAccount

    { $pdo = static::getPdo(); $stmt = $pdo->prepare("SELECT * FROM `user_account` WHERE email = :email"); $stmt->bindValue('email', $email, \PDO::PARAM_STR); $stmt->execute(); $stmt->setFetchMode(\PDO::FETCH_CLASS, '\MyApp\Model\UserAccount'); return $stmt->fetch() ?: null; } public function create(UserAccount $ua): int { if(!$ua->isValid()) throw new \InvalidArgumentException("invalid user account model"); $pdo = static::getPdo(); $stmt = $pdo->prepare(" INSERT INTO user_account (email, hashed_password, name) VALUES (:email, :hashed_password, :name) "); $stmt->bindValue('hashed_password', $ua->hashed_password, \PDO::PARAM_STR); $stmt->bindValue('name', $ua->name, \PDO::PARAM_STR); $stmt->bindValue('email', $ua->email, \PDO::PARAM_STR); $result = $stmt->execute(); if (!$result) throw new \RuntimeException("DB query error."); return $pdo->lastInsertId('id'); }
  58. 122.

    » ӬଓԽ΍σʔλͷϩʔυΛ࢘Δ » ModelΛੜ੒ͨ͠ΓɺModelΛอଘͨ͠Γ͢Δ » όϦόϦʹSQLΛॻ͍ͯ΋͍͍͠ɺ֎෦APIΛୟ͘ࣄ ΋͋Δ » ϛεϚονΛٵऩ͢Δ »

    ྫ֎͸جຊ࢖Θͳ͍ɺ0݅औಘͱ͔͸جຊNULLฦ͠ Ͱ্ҐͰڍಈΛ൑ఆ͢Δ » Goͷval, errorΈ͍ͨͳͷ΄͍ͬ͢͠Ͷ… listड ͚Ͱ΋͍͍͚Ͳ…ɻ
  59. 124.

    Service !"" lib # $"" MyApp # !"" Api #

    # !"" Action # # !"" Request # # $"" Response # !"" Model !" # !"" Repo # $"" Service $"" tests # $"" testTest.php
  60. 125.

    Modelྫ class UserAccount { public $id = 0; public $email

    = ""; public $hashed_password = ""; public $name = ""; public function __construct() { if ($this->id === 0) return; // PDO fetchͳΒҎ߱΋࣮ߦ͞ΕΔ $this->id = (int)$this->id; } public function getPublicData(): array { $list = get_object_vars($this); unset($list['id']); unset($list['hashed_password']); return $list; }
  61. 126.

    » ཉ͍͠σʔλߏ଄͕͋Ε͹ͭ͘ΔɺςʔϒϧͱҰରҰͰ ରԠඞਢͰ͸ͳ͍ » ϓϩύςΟ͸ඞཁͳ෼ͭ͘ΔɺIDEͷิ׬ʹ΋͔ͭ͏ » ଟ͘ͷέʔεͰ͸ɺίϯετϥΫλ͸FETCH_CLASSܦ༝ Λ૝ఆɺศརʂ » PDO

    FETCH_CLASS͸શ෦strʹͳΔͷͰɺModelͷίϯ ετϥΫλͰม׵ɺ͍ͩ͞ » Typed Properties͕͖ͨΒઈରʹ͜͜Ͳ͏ʹ͔ͳͬ ͯͯ΄͍͠… » ͳ͓ɺhydrate΋(΍Ζ͏ͱ͓΋͑͹)͜͜ͰͰ͖Δ
  62. 127.

    // ଓ͖ public function setPassword(string $new_plain_password): void { $this->hashed_password =

    password_hash($new_plain_password, PASSWORD_DEFAULT); } public function passwordVerification(string $plain_pass): bool { return password_verify($plain_pass, $this->hashed_password); } public function isValid(): bool { return count($this->validate()) === 0; } public function validate(): array { /* ... লུ ... */ return []; // is OK }
  63. 130.