- 投稿日:2020-04-21T23:53:17+09:00
Column not found: 1054 Unknown column '_token' in 'field list',Column not found: 1054 Unknown column '_method' in 'field list'
Column not found: 1054 Unknown column 'token' in 'field list',
Column not found: 1054 Unknown column 'method' in 'field list'の解決方法Column not found: 1054 Unknown column '_token' in 'field list
原因箇所
->update($request->all());解決策
->update($request->except(['_token']));Column not found: 1054 Unknown column '_method' in 'field list'
上記の解決策を試したところ、私の場合PUTメソッドを使用しており、Laravelが自動的に非表示の_methodフィールドを追加していたため、新たなエラーが起きたようです。
原因箇所
->update($request->except(['_token']));解決策
->update($request->except(['_token', '_method']));
- 投稿日:2020-04-21T22:43:45+09:00
Laravelを知らない中級(中年)プログラマーがマイグレーションファイルの仕組みを調べてみたけど全くわからない!その4:「Larabelアプリケーションの初期化の流れ」
INDEX
Laravelを知らない中級(中年)プログラマーがマイグレーションファイルの仕組みを調べてみたけど全くわからない!
- その1「マイグレーションファイルを見てみよう」
- その2「$app['db']って何者?」
- その3「Repositoryクラス」
- その4「Larabelアプリケーションの初期化の流れ」
- その5「リポジトリの読込」
- その6「データベース接続」
- その7「スキーマビルダー」
Larabelアプリケーションの初期化の流れ
Laravelをマイグレーションファイルから少しずつ読み始めて、どうやらお作法であろう存在にいくつか出会いました。そろそろ初期化の流れを追ってみようと思います。
artisan
が叩かれると、PROJECT_ROOT/bootstrap/app.php
が呼び出されます。その最初で Illuminate\Foundation\Application が生成されます。PROJECT_ROOT/bootstrap/app.php抜粋$app = new Illuminate\Foundation\Application( $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__) );
Application
クラスを見てみましょう。実体はPROJECT_ROOT/vendor/laravel/framework/src/Illuminate/Foundation/Application.php
です。このクラスのコンストラクタと関連する処理の一部は以下です。Illuminate/Foundation/Application/** * Create a new Illuminate application instance. * * @param string|null $basePath * @return void */ public function __construct($basePath = null) { if ($basePath) { $this->setBasePath($basePath); } $this->registerBaseBindings(); $this->registerBaseServiceProviders(); $this->registerCoreContainerAliases(); } /** * Register the basic bindings into the container. * * @return void */ protected function registerBaseBindings() { static::setInstance($this); $this->instance('app', $this); $this->instance(Container::class, $this); $this->singleton(Mix::class); $this->instance(PackageManifest::class, new PackageManifest( new Filesystem, $this->basePath(), $this->getCachedPackagesPath() )); } /** * Set the base path for the application. * * @param string $basePath * @return $this */ public function setBasePath($basePath) { $this->basePath = rtrim($basePath, '\/'); $this->bindPathsInContainer(); return $this; } /** * Bind all of the application paths in the container. * * @return void */ protected function bindPathsInContainer() { $this->instance('path', $this->path()); $this->instance('path.base', $this->basePath()); $this->instance('path.lang', $this->langPath()); $this->instance('path.config', $this->configPath()); $this->instance('path.public', $this->publicPath()); $this->instance('path.storage', $this->storagePath()); $this->instance('path.database', $this->databasePath()); $this->instance('path.resources', $this->resourcePath()); $this->instance('path.bootstrap', $this->bootstrapPath()); } /** * Get the path to the application "app" directory. * * @param string $path * @return string */ public function path($path = '') { $appPath = $this->appPath ?: $this->basePath.DIRECTORY_SEPARATOR.'app'; return $appPath.($path ? DIRECTORY_SEPARATOR.$path : $path); } /** * Get the base path of the Laravel installation. * * @param string $path Optionally, a path to append to the base path * @return string */ public function basePath($path = '') { return $this->basePath.($path ? DIRECTORY_SEPARATOR.$path : $path); } /** * Get the base path of the Laravel installation. * * @param string $path Optionally, a path to append to the base path * @return string */ public function basePath($path = '') { return $this->basePath.($path ? DIRECTORY_SEPARATOR.$path : $path); } /** * Get the path to the language files. * * @return string */ public function langPath() { return $this->resourcePath().DIRECTORY_SEPARATOR.'lang'; } /** * Get the path to the application configuration files. * * @param string $path Optionally, a path to append to the config path * @return string */ public function configPath($path = '') { return $this->basePath.DIRECTORY_SEPARATOR.'config'.($path ? DIRECTORY_SEPARATOR.$path : $path); } /* ========== 略 ========== */コンストラクタではまず、パス情報の初期化を行っています。アプリケーションコンテナが生成された時に渡された引数は
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
でした。それを引数としてsetBasePath()
メソッドを呼び出しています。setBasePath()
メソッドは渡されたパス情報を$this->basePath
に格納し、bindPathsInContainer()
メソッドを呼び出しています。このメソッドではパスの情報をバインドしているようです。$this->instance()
が何をやっているのか、現段階ではまだ良くわかりません。おそらく後々追うことになるでしょう。今追っているconfig
はpath.config
としてPROJECT_ROOT/config
が設定されているようです。次に
registerBaseBindings()
メソッドが呼び出されています。registerBaseBindings()
メソッドでは以下の処理が行われています。Illuminate/Foundation/Application$this->instance(PackageManifest::class, new PackageManifest( new Filesystem, $this->basePath(), $this->getCachedPackagesPath() ));
PackageManifest
という名前で第二引数で生成したPackageManifest
クラスをアプリケーションコンテナにバインドしているようです。PackageManifest
クラスに渡している三つの引数を追ってみましょう。まずFilesystem
です。実体はPROJECT_ROOT/vendor/laravel/framework/src/Illuminate/Filesystem/Filesystem.php
です。ファイルを管理するクラスのようです。次に上で初期化された$basePath
が渡されます。そして最後にキャッシュパスが渡されます。キャッシュパスについては後ほど追ってみます。PackageManifest
クラスを見てみましょう。コンストラクタに以下のような定義がされています。Illuminate/Foundation/PackageManifest/** * Create a new package manifest instance. * * @param \Illuminate\Filesystem\Filesystem $files * @param string $basePath * @param string $manifestPath * @return void */ public function __construct(Filesystem $files, $basePath, $manifestPath) { $this->files = $files; $this->basePath = $basePath; $this->manifestPath = $manifestPath; $this->vendorPath = $basePath.'/vendor'; }引数として渡された情報を変数に格納し、追加で
vendorPath
もハードコーディングで定義しています。このへんで
Application
クラスはひとまず置いておいて、一番最初に叩かれるartisan
ファイルを見てみましょう。 次の処理に$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class)
とあります。これは、$app = require_once __DIR__.'/bootstrap/app.php'
の中で以下のようにアプリケーションコンテナにシングルトンとしてバインドしているようです。生成の流れの詳細はまだわかりません。PROJECT_ROOT/bootstrap/app.php抜粋$app->singleton( Illuminate\Contracts\Console\Kernel::class, App\Console\Kernel::class );
App\Console\Kernel
はcomposer/autoload_classmap.php
で'App\\Console\\Kernel' => $baseDir . '/app/Console/Kernel.php'
と定義されています。
つまり実体はPROJECT_ROOT/app/Console/Kernel.php
のようです。このクラスはIlluminate\Foundation\Console\Kernel
を継承しています。Illuminate\Foundation\Console\Kernel
に以下のような定義がされています。Illuminate\Foundation\Console\Kernel/** * The bootstrap classes for the application. * * @var array */ protected $bootstrappers = [ \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class, \Illuminate\Foundation\Bootstrap\LoadConfiguration::class, \Illuminate\Foundation\Bootstrap\HandleExceptions::class, \Illuminate\Foundation\Bootstrap\RegisterFacades::class, \Illuminate\Foundation\Bootstrap\SetRequestForConsole::class, \Illuminate\Foundation\Bootstrap\RegisterProviders::class, \Illuminate\Foundation\Bootstrap\BootProviders::class, ]; /** * Run the console application. * * @param \Symfony\Component\Console\Input\InputInterface $input * @param \Symfony\Component\Console\Output\OutputInterface|null $output * @return int */ public function handle($input, $output = null) { try { $this->bootstrap(); return $this->getArtisan()->run($input, $output); } catch (Throwable $e) { $this->reportException($e); $this->renderException($output, $e); return 1; } } /** * Bootstrap the application for artisan commands. * * @return void */ public function bootstrap() { if (! $this->app->hasBeenBootstrapped()) { $this->app->bootstrapWith($this->bootstrappers()); } $this->app->loadDeferredProviders(); if (! $this->commandsLoaded) { $this->commands(); $this->commandsLoaded = true; } }
$bootstrappers
に初期設定用のクラスが配列としてセットされています。LoadConfiguration
とそれっぽい記述がありますね。
そして handle() メソッドですが、これは artisan ファイルに make() メソッドの直後に以下のように書かれています。PROJECT_ROOT/artisan抜粋$status = $kernel->handle( $input = new Symfony\Component\Console\Input\ArgvInput, new Symfony\Component\Console\Output\ConsoleOutput );コマンド処理開始のトリガのようです。ここから先程の
handle()
メソッドが呼ばれます。引数はおそらくコマンドラインから渡されたものと標準出力でしょう。そこは後で追うことになるので今は飛ばします。メイン処理のtry
の中に$this->bootstrap()
が記述されています。bootstrap()
メソッドのはじめで、初期化がされていない場合はアプリケーションコンテナのbootstrapWith()
に引数として 先程定義されているのを確認した初期設定用クラスの配列$bootstrappers
を渡しています。アプリケーションコンテナのbootstrapWith()
メソッドを見てみましょう。Illuminate/Foundation/Application::bootstrapWith()/** * Run the given array of bootstrap classes. * * @param string[] $bootstrappers * @return void */ public function bootstrapWith(array $bootstrappers) { $this->hasBeenBootstrapped = true; foreach ($bootstrappers as $bootstrapper) { $this['events']->dispatch('bootstrapping: '.$bootstrapper, [$this]); $this->make($bootstrapper)->bootstrap($this); $this['events']->dispatch('bootstrapped: '.$bootstrapper, [$this]); } }まず、初期化フラグを立てています。先程、初期化がされているか確認するロジックがありましたがそれの判定はここを通過したか否かということのようです。その後に引数で渡された
$bootstrappers
配列をforeach
で回します。配列の中身をevents
サービスのdispatch()
メソッドに内容を渡しているようです。events
サービスはApplication
クラスのregisterCoreContainerAliases()
メソッドでエイリアス登録されていましたね。実体はPROJECT_ROOT/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php
です。呼び出されているdispatch
と関係するメソッドを見てみましょう。Illuminate/Events/Dispatcher::dispatch|関連メソッド/** * Fire an event and call the listeners. * * @param string|object $event * @param mixed $payload * @param bool $halt * @return array|null */ public function dispatch($event, $payload = [], $halt = false) { // When the given "event" is actually an object we will assume it is an event // object and use the class as the event name and this event itself as the // payload to the handler, which makes object based events quite simple. [$event, $payload] = $this->parseEventAndPayload( $event, $payload ); if ($this->shouldBroadcast($payload)) { $this->broadcastEvent($payload[0]); } $responses = []; foreach ($this->getListeners($event) as $listener) { $response = $listener($event, $payload); // If a response is returned from the listener and event halting is enabled // we will just return this response, and not call the rest of the event // listeners. Otherwise we will add the response on the response list. if ($halt && ! is_null($response)) { return $response; } // If a boolean false is returned from a listener, we will stop propagating // the event to any further listeners down in the chain, else we keep on // looping through the listeners and firing every one in our sequence. if ($response === false) { break; } $responses[] = $response; } return $halt ? null : $responses; } /** * Parse the given event and payload and prepare them for dispatching. * * @param mixed $event * @param mixed $payload * @return array */ protected function parseEventAndPayload($event, $payload) { if (is_object($event)) { [$payload, $event] = [[$event], get_class($event)]; } return [$event, Arr::wrap($payload)]; } /** * Determine if the payload has a broadcastable event. * * @param array $payload * @return bool */ protected function shouldBroadcast(array $payload) { return isset($payload[0]) && $payload[0] instanceof ShouldBroadcast && $this->broadcastWhen($payload[0]); } /** * Check if event should be broadcasted by condition. * * @param mixed $event * @return bool */ protected function broadcastWhen($event) { return method_exists($event, 'broadcastWhen') ? $event->broadcastWhen() : true; } /** * Get all of the listeners for a given event name. * * @param string $eventName * @return array */ public function getListeners($eventName) { $listeners = $this->listeners[$eventName] ?? []; $listeners = array_merge( $listeners, $this->wildcardsCache[$eventName] ?? $this->getWildcardListeners($eventName) ); return class_exists($eventName, false) ? $this->addInterfaceListeners($eventName, $listeners) : $listeners; }まず、受け取った第一引数の初期化クラス名とイベント名(まずは「
bootstrapping:
」)を連結した文字列と第二引数のアプリケーションコンテナをparseEventAndPayload()
メソッドに通します。parseEventAndPayload()
メソッドは、渡されたものがオブジェクトだった場合の処理や配列でない場合の処理をしています。Arr::wrap()
は渡された引数が配列でない場合やnull
の場合配列にして返すstatic
メソッドです。次に
shouldBroadcast()
メソッドでdispatch()
メソッドに渡された第二引数の判定をしています。今回の場合はアプリケーションコンテナが対象になります。shouldBroadcast()
メソッドは引数として受け取ったものを配列アクセスし、0番目がセットされていてそれがShouldBroadcast
クラスのインスタンスで且つ、broadcastWhen()
メソッドが存在しない、若しくは戻り値がtrue
の場合true
に、そうでない場合false
になります。
判定がtrue
の場合、broadcastEvent()
メソッドに渡します。 今回処理するのはアプリケーションコンテナなのでこの処理は通りません。次のforeach ($this->getListeners($event) as $listener)
の処理はイベントリスナとして登録されているかどうかを実装されたインターフェイスも含め調べそれらをforeach
で回して処理をしているようです。イベント登録されている場合はトリガが叩かれる感じでしょうか。今回は主題からそれるので別の機会に追いたいと思います。コール元で戻り値を受け取っていないので次に行きましょう。アプリケーションコンテナの
$this->make($bootstrapper)->bootstrap($this)
です。$bootstrappers
配列を回して一つずつインスタンスを生成し、bootstrap($this)
しているようです。サービスを生成し必ずbootstrap()
メソッドが実行されるという仕様はこの部分で実現されているのでしょう。その後にbootstrapped
イベントトリガを叩く手順ですね。LoadConfiguration
Kernel
クラスの初期化で$bootstrappers
配列に含まれていたIlluminate\Foundation\Bootstrap\LoadConfiguration
がサービスとして初期化されました。このクラスのbootstrap()
メソッドが叩かれているはずです。見てみましょう。Illuminate/Foundation/Bootstrap/LoadConfiguration::bootstrap()/** * Bootstrap the given application. * * @param \Illuminate\Contracts\Foundation\Application $app * @return void */ public function bootstrap(Application $app) { $items = []; // First we will see if we have a cache configuration file. If we do, we'll load // the configuration items from that file so that it is very quick. Otherwise // we will need to spin through every configuration file and load them all. if (file_exists($cached = $app->getCachedConfigPath())) { $items = require $cached; $loadedFromCache = true; } // Next we will spin through all of the configuration files in the configuration // directory and load each one into the repository. This will make all of the // options available to the developer for use in various parts of this app. $app->instance('config', $config = new Repository($items)); if (! isset($loadedFromCache)) { $this->loadConfigurationFiles($app, $config); } // Finally, we will set the application's environment based on the configuration // values that were loaded. We will pass a callback which will be used to get // the environment in a web context where an "--env" switch is not present. $app->detectEnvironment(function () use ($config) { return $config->get('app.env', 'production'); }); date_default_timezone_set($config->get('app.timezone', 'UTC')); mb_internal_encoding('UTF-8'); }
bootstrap()
メソッドはアプリケーションコンテナを引数として受け取ります。まず、アプリケーションコンテナのgetCachedConfigPath()
メソッドを叩き戻り値のファイルが存在確認をしています。Application::getCachedConfigPath()
と関連するメソッドは以下のようになっています。Illuminate\Foundation\Application::Application/** * Get the path to the configuration cache file. * * @return string */ public function getCachedConfigPath() { return $this->normalizeCachePath('APP_CONFIG_CACHE', 'cache/config.php'); } /** * Normalize a relative or absolute path to a cache file. * * @param string $key * @param string $default * @return string */ protected function normalizeCachePath($key, $default) { if (is_null($env = Env::get($key))) { return $this->bootstrapPath($default); } return Str::startsWith($env, '/') ? $env : $this->basePath($env); }
Application::getCachedConfigPath()
はAPP_CONFIG_CACHE
をキーに、デフォルト値にcache/config.php
を引数指定してnormalizeCachePath()
メソッドをコールしています。normalizeCachePath()
はEnv::get($key)
がnull
であるか確認しています。
Env
クラスはPROJECT_ROOT/vendor/laravel/framework/src/Illuminate/Support/Env.php
です。get()
メソッドを見てみましょう。Illuminate\Support\Env::get()|getRepository()/** * Get the environment repository instance. * * @return \Dotenv\Repository\RepositoryInterface */ public static function getRepository() { if (static::$repository === null) { $adapters = array_merge( [new EnvConstAdapter, new ServerConstAdapter], static::$putenv ? [new PutenvAdapter] : [] ); static::$repository = RepositoryBuilder::create() ->withReaders($adapters) ->withWriters($adapters) ->immutable() ->make(); } return static::$repository; } /** * Gets the value of an environment variable. * * @param string $key * @param mixed $default * @return mixed */ public static function get($key, $default = null) { return Option::fromValue(static::getRepository()->get($key)) ->map(function ($value) { switch (strtolower($value)) { case 'true': case '(true)': return true; case 'false': case '(false)': return false; case 'empty': case '(empty)': return ''; case 'null': case '(null)': return; } if (preg_match('/\A([\'"])(.*)\1\z/', $value, $matches)) { return $matches[2]; } return $value; }) ->getOrCall(function () use ($default) { return value($default); }); }
Option::fromValue()
に引数として渡しているstatic::getRepository()->get($key)
から見てみます。getRepository()
メソッドではstatic::$repository
がnull
でない場合はそれを返し、null
だった場合はstatic::$repository
を構築する流れのようです。static::$repository
自体はDotenv/Repository/RepositoryInterface
インターフェイスを実装したインスタンスのようです。処理の中で以下のようなクラスが書かれています。
- EnvConstAdapter
- ServerConstAdapter
- PutenvAdapter
- RepositoryBuilder
これらは、vlucas/phpdotenvを利用したもののようです。Laravelの環境設定を
.env
に記述するのはこの仕組を利用するためのようです。Illuminate\Foundation\Console\Kernel
で定義した$bootstrappers
の一番最初にIlluminate\Foundation\Bootstrap\LoadEnvironmentVariables
がありました。これをKernel::handle()
実行時に読み込んでいます。このLoadEnvironmentVariables::bootstrap()
メソッドからcreateDotenv()
メソッドが呼ばれ、Dotenv::create
が実行されています。このメソッドに引数で渡される、環境設定ファイル名 「.env
」 とパス情報は アプリケーションコンテナでprotected $environmentFile = '.env'
と設定されています。変更したい場合はloadEnvironmentFrom()
メソッドで変えられるようです。このphpdotenv
でリポジトリを構築することで、Laravel設定ファイル、Apache設定、サーバ環境設定等をにアクセスするインターフェイスを整えてくれるようです。
getRepository()
メソッドからRepositoryInterface
インタフェースを実装した環境設定情報が返されます。そこからget()
メソッドが呼ばれ、PhpOption\Option
インターフェイスが実装されたインスタンスが返されます。PhpOption\Option
PhpOption\Option
とは何でしょうか。これはschmittjoh/php-optionのようです。autoload_classmap.php
で'PhpOption\\Option' => $vendorDir . '/phpoption/phpoption/src/PhpOption/Option.php'
と定義されています。Option::fromValue()
と関連するクラスは以下のように定義されています。PhpOption\Option::fromValue()abstract class Option implements IteratorAggregate { /** * Creates an option given a return value. * * This is intended for consuming existing APIs and allows you to easily * convert them to an option. By default, we treat ``null`` as the None * case, and everything else as Some. * * @template S * * @param S $value The actual return value. * @param S $noneValue The value which should be considered "None"; null by * default. * * @return Option<S> */ public static function fromValue($value, $noneValue = null) { if ($value === $noneValue) { return None::create(); } return new Some($value); } /* ========== 略 ========== */ }PhpOption\None::create()final class None extends Option { /** * @return None */ public static function create() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } /* ========== 略 ========== */ }PhpOption\Some::__construct()final class Some extends Option { /** @var T */ private $value; /** * @param T $value */ public function __construct($value) { $this->value = $value; } public function map($callable) { return new self($callable($this->value)); } /* ========== 略 ========== */ }
Option
クラスは抽象クラスで、これを静的メソッド呼び出しをしています。Option
クラス自体はIteratorAggregate
インターフェイスを実装しています。イテレーターとしてアクセスが可能のようですね。渡された第一引数が第二引数と同じ場合は空のオブジェクトであるNone
インスタンスを、同じでなければ第一引数を内部に持ったSome
インスタンスを返すようです。値はSome->value
の形で格納され、この値に各種コールできるメソッドを実装しています。今回アクセスしたいのはAPP_CONFIG_CACHE
なので、$value
にAPP_CONFIG_CACHE
がセットされたSome
インスタンスが返ってくるはずです。Some::map()
メソッドの内容はreturn new self($callable($this->value))
とあります。map()
に渡された引数にはクロージャーが入っていました。Some::$value
を引数にしたクロージャーの結果を$value
に格納したSome
インスタンスが返ってくる流れです。そして返されたSome
インスタンスのgetOrCall()
メソッドを引数としてクロージャーを入れて呼び出します。ただ、Some::getOrCall()
はreturn $this->value
しているだけですので、$value
がそのまま返されます。つまり、アプリケーションコンテナのnormalizeCachePath
にあるif (is_null($env = Env::get($key)))
は各環境設定をまとめたリポジトリからAPP_CONFIG_CACHE
が存在するかを判定しています。もし、存在していな場合は第二引数で指定された$default
つまりcache/config.php
を引数にbootstrapPath()
が呼び出されます。このメソッドはbasePath
にbootstrap
ディレクトリを追加して引数の文字列を連結したものを返します。結果、PROJECT_ROOT/bootstrap/cache/config.php
という文字列が返されます。APP_CONFIG_CACHE
が存在していた場合は、Str::startsWith($env, '/')
が判定され文字列加工をします。startsWith
はJava
などで使われる関数でPHP
関数にないものを独自に定義したもののようです。文字列が引数で指定された文字列で始まるかを判定してtrue
かfalse
を返します。LoadConfiguration::bootstrap()
にそのパスが返され、file_exists
でそのファイルが存在するか判定され、存在した場合はそのキャッシュファイルを読み込み、読み込みフラグ$loadedFromCache
にtrue
がセットされます。存在していた場合は読み込んだキャッシュを、存在していない場合は空の配列をアプリケーションコンテナにconfig
の名前でバインドします。もし、存在しなかった場合はloadConfigurationFiles()
メソッドを第一引数にアプリケーションコンテナ、第二引数に空の配列で初期化したRepository
インスタンスを渡してコールします。Illuminate/Foundation/Bootstrap/LoadConfiguration::loadConfigurationFiles()|関連メソッド/** * Load the configuration items from all of the files. * * @param \Illuminate\Contracts\Foundation\Application $app * @param \Illuminate\Contracts\Config\Repository $repository * @return void * * @throws \Exception */ protected function loadConfigurationFiles(Application $app, RepositoryContract $repository) { $files = $this->getConfigurationFiles($app); if (! isset($files['app'])) { throw new Exception('Unable to load the "app" configuration file.'); } foreach ($files as $key => $path) { $repository->set($key, require $path); } } /** * Get all of the configuration files for the application. * * @param \Illuminate\Contracts\Foundation\Application $app * @return array */ protected function getConfigurationFiles(Application $app) { $files = []; $configPath = realpath($app->configPath()); foreach (Finder::create()->files()->name('*.php')->in($configPath) as $file) { $directory = $this->getNestedDirectory($file, $configPath); $files[$directory.basename($file->getRealPath(), '.php')] = $file->getRealPath(); } ksort($files, SORT_NATURAL); return $files; } /** * Get the configuration file nesting path. * * @param \SplFileInfo $file * @param string $configPath * @return string */ protected function getNestedDirectory(SplFileInfo $file, $configPath) { $directory = $file->getPath(); if ($nested = trim(str_replace($configPath, '', $directory), DIRECTORY_SEPARATOR)) { $nested = str_replace(DIRECTORY_SEPARATOR, '.', $nested).'.'; } return $nested; }
loadConfigurationFiles()
メソッドのはじめでgetConfigurationFiles()
がコールされます。$configPath = realpath($app->configPath())
で設定ファイルのパスをセットしています。これはApplication::configPath()
でreturn $this->basePath.DIRECTORY_SEPARATOR.'config'.($path ? DIRECTORY_SEPARATOR.$path : $path)
と定義されていますので、PROJECT_ROOT/config
が代入されます。次にforeach
でFinder
クラスで色々したものを回しています。Finder
クラスはautoload_classmap で /symfony/finder/Finder.php
と定義されています。symfony/finder
symfony/finder/Finder
を見てみましょう。
Symfony
のディレクトリやファイルの一覧を取得する便利機能が詰め込まれたコンポーネントのようです。見てみましょう。symfony/finder/Finder抜粋/** * Finder allows to build rules to find files and directories. * * It is a thin wrapper around several specialized iterator classes. * * All rules may be invoked several times. * * All methods return the current Finder object to allow chaining: * * $finder = Finder::create()->files()->name('*.php')->in(__DIR__); * * @author Fabien Potencier <fabien@symfony.com> */ class Finder implements \IteratorAggregate, \Countable { /* ========== 中略 ========== */ private $names = []; /* ========== 中略 ========== */ /** * Creates a new Finder. * * @return static */ public static function create() { return new static(); } /** * Restricts the matching to files only. * * @return $this */ public function files() { $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES; return $this; } /** * Adds rules that files must match. * * You can use patterns (delimited with / sign), globs or simple strings. * * $finder->name('*.php') * $finder->name('/\.php$/') // same as above * $finder->name('test.php') * $finder->name(['test.py', 'test.php']) * * @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns * * @return $this * * @see FilenameFilterIterator */ public function name($patterns) { $this->names = array_merge($this->names, (array) $patterns); return $this; } /** * Searches files and directories which match defined rules. * * @param string|string[] $dirs A directory path or an array of directories * * @return $this * * @throws DirectoryNotFoundException if one of the directories does not exist */ public function in($dirs) { $resolvedDirs = []; foreach ((array) $dirs as $dir) { if (is_dir($dir)) { $resolvedDirs[] = $this->normalizeDir($dir); } elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? GLOB_BRACE : 0) | GLOB_ONLYDIR | GLOB_NOSORT)) { sort($glob); $resolvedDirs = array_merge($resolvedDirs, array_map([$this, 'normalizeDir'], $glob)); } else { throw new DirectoryNotFoundException(sprintf('The "%s" directory does not exist.', $dir)); } } $this->dirs = array_merge($this->dirs, $resolvedDirs); return $this; } }symfony/finder/Iterator/FileTypeFilterIterator抜粋/** * FileTypeFilterIterator only keeps files, directories, or both. * * @author Fabien Potencier <fabien@symfony.com> */ class FileTypeFilterIterator extends \FilterIterator { const ONLY_FILES = 1; const ONLY_DIRECTORIES = 2; private $mode; /** * @param \Iterator $iterator The Iterator to filter * @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES) */ public function __construct(\Iterator $iterator, int $mode) { $this->mode = $mode; parent::__construct($iterator); } /* ========== 略 ========== */ }クラスドキュメントを見ると、「ファイルとディレクトリを検索するルールを構築できます」とあります。使用例に
$finder = Finder::create()->files()->name('*.php')->in(__DIR__);とあります。
LoadConfiguration:: getConfigurationFiles()
の記述とほぼ同じなので、典型的な利用方法のようです。
まず、create()
でstatic
として自分自身を生成して返します。その後使われるメソッドは基本的に戻り値が$this
になっていて、メソッドチェーンで処理をすすめる前提になっているようです。次にfiles()
メソッドがコールされます。$this->mode
にIterator\FileTypeFilterIterator::ONLY_FILES
を代入しています。ONLY_FILES
は 1 が定義されています。ファイルだけ一覧にするモード定数なのでしょう。次にname()
メソッドに'*.php'
が引数として渡されています。name()
メソッドではFinder::namesに
渡された引数をarray_marge
しています。検索条件を配列で蓄える機能と思われます。最後にin()
メソッドが設定ファイルのパスを引数としてコールされます。渡された引数をforeach
で回し、正規化したパスをFinder::dirs
にセットして自身を返します。Finder
インスタンスはイテレーターインターフェイスを持っていますので、foreach
で回すことができます。つまり、create
で生成した後、モードやディレクトリ、その他条件をセットし結果をイテレーターとして提供するコンポーネントですね。ではloadConfigurationFiles
に戻りましょう。PROJECT_ROOT/config/*.php 設定ファイルの読み込み
symfony/finder
から受け取るものはPROJECT_ROOT/config/
の配下にある.php
拡張子のついたファイル一覧だということが先程わかりました。一覧を受け取った後に以下の処理をしています。Illuminate/Foundation/Bootstrap/LoadConfiguration::getConfigurationFiles()抜粋$directory = $this->getNestedDirectory($file, $configPath); $files[$directory.basename($file->getRealPath(), '.php')] = $file->getRealPath();パスの表記を整えてソートした配列を作り変えしています。
loadConfigurationFiles
の続きに戻りましょう。Illuminate/Foundation/Bootstrap/LoadConfiguration::loadConfigurationFiles()抜粋if (! isset($files['app'])) { throw new Exception('Unable to load the "app" configuration file.'); } foreach ($files as $key => $path) { $repository->set($key, require $path); }受け取った配列に
app
をキーとした情報が存在しなければ 「Unable to load the “app” configuration file.
」というメッセージを添えた例外を投げています。存在すれば第二引数でうけとったリポジトリインスタンスに設定ファイル名をキーとして、設定ファイルを読み込み、その戻り値の配列をセットしてloadConfigurationFiles()
メソッドの処理は終了です。LoadConfiguration::bootstrap()
メソッドの続きに戻りましょう。Illuminate/Foundation/Bootstrap/LoadConfiguration::bootstrap()抜粋// Finally, we will set the application's environment based on the configuration // values that were loaded. We will pass a callback which will be used to get // the environment in a web context where an "--env" switch is not present. $app->detectEnvironment(function () use ($config) { return $config->get('app.env', 'production'); });Illuminate/Foundation/Application::detectEnvironment()/** * Detect the application's current environment. * * @param \Closure $callback * @return string */ public function detectEnvironment(Closure $callback) { $args = $_SERVER['argv'] ?? null; return $this['env'] = (new EnvironmentDetector)->detect($callback, $args); }
detectEnvironment()
メソッドにクロージャーを引数として渡しています。クロージャー自体の引数は設定リポジトリがuse
で指定されています。クロージャーの戻り値は$config->get('app.env', 'production')
とあります。ちょうど先程読んだところですね。PROJECT_ROOT/config/app.php
に記述されている配列の 「env
」 キーを探し、なければproduction
を返します。PRODUCT_ROOT/config/app.php
には'env' => env('APP_ENV', 'production')
とあるので、.env
ファイルなどでAPP_ENV
が設定されていればそれを、なければproduction
を返します。
detectEnvironment()
メソッドを読んでみましょう。EnvironmentDetector
インスタンスを生成して、先程のコールバックとスクリプトへ渡された引数の配列をEnvironmentDetector::detect()
メソッドに渡しています。EnvironmentDetector
クラスを読んでみましょう。実体はIlluminate/Foundation/EnvironmentDetector
です。Illuminate/Foundation/EnvironmentDetector::detect()/** * Detect the application's current environment. * * @param \Closure $callback * @param array|null $consoleArgs * @return string */ public function detect(Closure $callback, $consoleArgs = null) { if ($consoleArgs) { return $this->detectConsoleEnvironment($callback, $consoleArgs); } return $this->detectWebEnvironment($callback); } /** * Set the application environment for a web request. * * @param \Closure $callback * @return string */ protected function detectWebEnvironment(Closure $callback) { return $callback(); } /** * Set the application environment from command-line arguments. * * @param \Closure $callback * @param array $args * @return string */ protected function detectConsoleEnvironment(Closure $callback, array $args) { // First we will check if an environment argument was passed via console arguments // and if it was that automatically overrides as the environment. Otherwise, we // will check the environment as a "web" request like a typical HTTP request. if (! is_null($value = $this->getEnvironmentArgument($args))) { return $value; } return $this->detectWebEnvironment($callback); } /** * Get the environment argument from the console. * * @param array $args * @return string|null */ protected function getEnvironmentArgument(array $args) { foreach ($args as $i => $value) { if ($value === '--env') { return $args[$i + 1] ?? null; } if (Str::startsWith($value, '--env')) { return head(array_slice(explode('=', $value), 1)); } } }
detect()
は受け取った第二引数、スクリプトへ渡された引数、つまりコマンドラインからartisan
を実行した時の引数の配列の存在を判定し、あった場合は、detectConsoleEnvironment()
メソッドの、なかった場合はdetectWebEnvironment()
メソッドの戻り値を返します。detectConsoleEnvironment()
メソッドはgetEnvironmentArgument()
メソッドにコマンド引数の配列を引数として渡します。getEnvironmentArgument()
メソッドはコマンド引数をforeach
で回し、--env
で指定した値が存在する場合はそれを返します。つまりdetect()
はartisan
コマンドの引数の中に--env
があった場合はenv
を上書します。その結果をアプリケーションコンテナのenv
に代入します。LoadConfiguration::bootstrap()
の残りはタイムゾーンをセットしてエンコードをUTF-8
に設定して完了です。
最初の目的より少し読みすぎましたが、LoadConfiguration::loadConfigurationFiles()
でリポジトリに設定ファイルを読み込みセットしているところを確認しました。Illuminate/Config/Repository::get()
で返されるArr::get($this->items, $key, $default)
の$this->items
の正体が明確になりました。次回
初期化の流れを読んでみてなんとなく、ふんわりやってることがわかってきましたが、まだオブジェクトの生成やイベント周りなどはっきりとつかめていない部分がありますね。徐々に理解していけると良いですが、どうなるでしょうか。次回はこの流れでリポジトリの読込を探っていきたいと思います。
続く☆
- 投稿日:2020-04-21T20:28:56+09:00
laravel基礎に入門したメモ
view周り
ユーザー認証周りのコード
php artisan make:auth
laravel6,7系だと上記のコードは使用できない$ composer require laravel/ui $ php artisan ui vue --auth代わりに上記のコードを使用する
JavaScriptのパッケージをインストールしてコンパイルするために以下のコードを実行する
$ npm install && npm run devテーブルの作成
$ php artisan make:migration create_tablename_tableテーブルの作成してup関数に追加したいカラムなどを記載
down関数に削除したいカラムなどを記載する
作成した後はmodelと関連づけないといけないモデルの作成
$ php artisan make:model モデル名 -m $ php artisan migrateモデルには命名規約がありテーブル名は複数形で命名しないといけなくモデル名にはテーブル名の単数形でないといけない
1対多 hasMany('App/Post'); 逆 belongsTo('App/User'); 1対1 hasOne それぞれApp/モデル名っぽい呼び出された時の処理
app/Http/Middleware/Authenticate.phpuse Illuminate\Support\Facades\Auth; class Authenticate { public function handle($request, Closure $next) { if (!Auth::check()) { // 非ログインはログインページに飛ばす return redirect('/login'); } return $next($request); } }を作成したapp/Http/Middleware/Authenticate.phpに記述することでログインしていなかったらログイン画面に遷移する
ルートの設定
/routes/web.phpRoute::resource('表示したいURL', 'コントローラー名'); php artisan route:listで表示できるコントローラーの一覧が出てくるコントローラーの作成
$ php artisan make:controller コントローラー名 もしrouteをresourceで書いているなら $ php artisan make:controller --resource コントローラー名/app/Http/Controllers/作成したControllerファイルviewを表示したい時 return view('viewのフォルダ名.bladeより上のファイル名')view
親となるviewファイルを1つ作成する
私はviews/layouts/index.blade.phpにしました。
子となるフォルダを作成します。これはコントローラーに指定されているviewファイル、子となるファイルにはこのように記述することで、親のファイルの記述をそのまま子にも反映されるようになりますlayouts/index.blade.php<!doctype html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Example</title> </head> <body> <div class="container"> <h1>親のページです</h1> @yield('content') </div> @yield('footer') </body>users/index.blade.php(呼び出し側)@extends('layouts.index')?layoutsフォルダの中にあるindexフォルダ呼び出し @section('content')?indexフォルダのなかのcontentをのところに記述されるようになる <h1>子のページです</h1> @endsection?終了コードをスッキリさせることが可能となる
viewにデータベースの内容を表示したい
controller側の処理
UsersController// 使用するテーブルの記載 use App\User; public function index() { // $usersにUserテーブルにある全レコードを格納 $users = User::all(); return view('users.index', compact('users')) } //compactは変数名と名前が一致すると自動的に変数として扱ってくれるview側の処理
index.blade.php@if($users) @foreach($users as $user) <p>{{ $user->id }}</p> <p>{{ $user->name }}</p> <p>{{ $user->email }}</p> @endforeach
- 投稿日:2020-04-21T19:48:40+09:00
laravel-session 使い方メモ
- 投稿日:2020-04-21T19:08:20+09:00
【Laravel】Formファサードのselectタグでvalueなしの初期値を入れる方法【Bladeテンプレート】
個人的に調べ方が分からず微妙に詰まった
調べるとコントローラーで取得した配列に初期値をprependして無理やり初期値を持った配列を指定する方法が多く出てくるが、何かイケてない気がする。
以下のように第4引数にplaceholderを追加するだけでした
第三引数にあたる初期値を
null
にして、第四引数に表示したいテキストをplaceholderとして指定するだけです。{!! Form::select('hoge_id', $arrayHoges, null, ['class' => 'form-hoge', 'placeholder' => '選択してください']) !!}しっかりvalueなし&デフォルト値でoptionタグの生成ができました。
さいごに
めちゃくちゃシンプルで単純でした。
シンプルすぎて逆に記事がほとんどなかったのかもしれませんね泣
他にもっとシンプルな方法ございましたら教えて頂けると助かります!
- 投稿日:2020-04-21T17:30:28+09:00
俺のLaravelがこんなに遅いわけがない
環境(ベース)
- PHP 7.4.5
- Laravel 7.5.1
Docker for Macでnginxとphp-fpmコンテナをunixソケットで繋いだ環境で試してます。
環境の差異
- OPcache なし
- OPcache あり、プリロードなし
- OPcache あり、プリロードあり
比較方法
- Laravelのwelcome画面をabコマンドの結果で比較します。
Requests per second(1秒間に捌けるリクエスト数)、Time per request(1リクエストあたりの処理時間)に着目します。
以前こんな記事も書いてます。
Apache Bench を初めて使ってみたOPcacheなし
php.ini(設定例)[opcache] opcache.enable = 0$ ab -n 1000 -c 100 http://127.0.0.1/ This is ApacheBench, Version 2.3 <$Revision: 1843412 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking 127.0.0.1 (be patient) Completed 100 requests Completed 200 requests Completed 300 requests Completed 400 requests Completed 500 requests Completed 600 requests Completed 700 requests Completed 800 requests Completed 900 requests Completed 1000 requests Finished 1000 requests Server Software: nginx/1.17.8 Server Hostname: 127.0.0.1 Server Port: 80 Document Path: / Document Length: 2426 bytes Concurrency Level: 100 Time taken for tests: 18.432 seconds Complete requests: 1000 Failed requests: 0 Total transferred: 3446000 bytes HTML transferred: 2426000 bytes Requests per second: 54.25 [#/sec] (mean) Time per request: 1843.175 [ms] (mean) Time per request: 18.432 [ms] (mean, across all concurrent requests) Transfer rate: 182.58 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 1 2.5 0 22 Processing: 79 1744 326.5 1836 2038 Waiting: 69 1743 326.6 1834 2038 Total: 83 1745 324.6 1836 2039 Percentage of the requests served within a certain time (ms) 50% 1836 66% 1865 75% 1883 80% 1894 90% 1926 95% 1948 98% 1988 99% 2009 100% 2039 (longest request)
- 1秒間に捌けるリクエスト数:
54.25
- 1リクエストあたりの処理時間:
18.432 (ms)
俺のLaravelがこんなに遅いわけがない...
OPcacheあり、プリロードなし
php.ini(設定例)[opcache] opcache.enable = 1 opcache.memory_consumption = 128 opcache.interned_strings_buffer = 8 opcache.max_accelerated_files = 4000 opcache.validate_timestamps = 0 opcache.huge_code_pages = 0$ ab -n 1000 -c 100 http://127.0.0.1/ This is ApacheBench, Version 2.3 <$Revision: 1843412 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking 127.0.0.1 (be patient) Completed 100 requests Completed 200 requests Completed 300 requests Completed 400 requests Completed 500 requests Completed 600 requests Completed 700 requests Completed 800 requests Completed 900 requests Completed 1000 requests Finished 1000 requests Server Software: nginx/1.17.8 Server Hostname: 127.0.0.1 Server Port: 80 Document Path: / Document Length: 2426 bytes Concurrency Level: 100 Time taken for tests: 3.318 seconds Complete requests: 1000 Failed requests: 0 Total transferred: 3446000 bytes HTML transferred: 2426000 bytes Requests per second: 301.41 [#/sec] (mean) Time per request: 331.772 [ms] (mean) Time per request: 3.318 [ms] (mean, across all concurrent requests) Transfer rate: 1014.32 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 2 4.4 1 24 Processing: 31 316 60.1 319 481 Waiting: 9 311 59.3 316 461 Total: 34 318 57.9 320 483 Percentage of the requests served within a certain time (ms) 50% 320 66% 335 75% 346 80% 354 90% 381 95% 404 98% 413 99% 421 100% 483 (longest request)
- 1秒間に捌けるリクエスト数:
301.41
- 1リクエストあたりの処理時間:
3.318 (ms)
54.25
から301.41
へおよそ5.55倍の高速化されました!!
圧倒的すぎる速さ!!キャッシュ効果恐るべし!!OPcacheあり、プリロードあり
php.ini(設定例)[opcache] opcache.enable = 1 opcache.memory_consumption = 128 opcache.interned_strings_buffer = 8 opcache.max_accelerated_files = 4000 opcache.validate_timestamps = 0 opcache.huge_code_pages = 0 opcache.preload = /var/www/preload.php opcache.preload_user = www-datahttps://github.com/brendt/laravel-preload/blob/master/preload.php
このpreload.phpを参考にしてます。ignoreにいくつか追加してます。
ただお試し的に使ってるので、また内容まとまったら別記事にしたいと思います。$ ab -n 1000 -c 100 http://127.0.0.1/ This is ApacheBench, Version 2.3 <$Revision: 1843412 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking 127.0.0.1 (be patient) Completed 100 requests Completed 200 requests Completed 300 requests Completed 400 requests Completed 500 requests Completed 600 requests Completed 700 requests Completed 800 requests Completed 900 requests Completed 1000 requests Finished 1000 requests Server Software: nginx/1.17.8 Server Hostname: 127.0.0.1 Server Port: 80 Document Path: / Document Length: 2426 bytes Concurrency Level: 100 Time taken for tests: 2.878 seconds Complete requests: 1000 Failed requests: 0 Total transferred: 3446000 bytes HTML transferred: 2426000 bytes Requests per second: 347.40 [#/sec] (mean) Time per request: 287.850 [ms] (mean) Time per request: 2.878 [ms] (mean, across all concurrent requests) Transfer rate: 1169.09 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 1 1.3 0 7 Processing: 49 268 46.5 275 344 Waiting: 35 267 46.5 274 342 Total: 54 269 45.5 276 344 Percentage of the requests served within a certain time (ms) 50% 276 66% 287 75% 294 80% 298 90% 312 95% 322 98% 331 99% 335 100% 344 (longest request)
- 1秒間に捌けるリクエスト数:
347.40
- 1リクエストあたりの処理時間:
2.878 (ms)
301.41
から347.40
へおよそ1.15倍の高速化されました!!もう俺のLaravelが遅いなんて言わせない...!!
まとめ
# OPcacheなし Requests per second: 54.25 [#/sec] (mean) Time per request: 18.432 [ms] (mean, across all concurrent requests) # OPcacheあり、プリロードなし Requests per second: 301.41 [#/sec] (mean) Time per request: 3.318 [ms] (mean, across all concurrent requests) # OPcacheあり、プリロードあり Requests per second: 347.40 [#/sec] (mean) Time per request: 2.878 [ms] (mean, across all concurrent requests)何も設定してない
54.25
の状態から347.40
へおよそ6.4倍と劇的な高速化を遂げました!!!
PHPのポテンシャル半端ないですね!!!プリロード自体は初めてなので諸々問題出てくるかもしれませんが、何か問題あったらまた記事にしていきたいと思います?
- 投稿日:2020-04-21T14:39:12+09:00
laravel 【ページの表示フロー】
どのようにlaravelのページが表示されているのかを見ていきたいと思います。
まずは新規のプロジェクトを作成します。プロジェクト名を blog とします。
laravel new blog
cd blog
php artisan serveまずはこの図の
Access〜show
を頭に入れておくと理解しやすいと思います。
ルーティング
routes/web.phpRoute::get('/', function () { return view('welcome'); });
HTTPメソッド パス アクション GET / クロージャー 関数の中でwelcomeビューを表示するようにしています。
HTTPメソッドやパスの組合せに応じたアクションを実行するのが、Routing の役割です。ビュー
resouces/views/welcome.blade.php<!doctype html> <html lang="{{ app()->getLocale() }}"> <head> ... <title>Laravel</title> ... </head> <body> <div class="flex-center position-ref full-height"> ... <div class="content"> <div class="title m-b-md"> Laravel </div> ... </div> </div> </body> </html>http://localhost:8000/ にアクセスした時に表示されるHTMLです
Laravel では bladeというテンプレートエンジンを使って、ビューファイル(HTMLテンプレート)からHTMLを生成しています。コントローラ
php artisan make:controller WelcomeController
としてコントローラを作りindexメソッドを追加しましょうapp/Http/Controllers/WelcomeController.phpnamespace App\Http\Controllers; use Illuminate\Http\Request; class WelcomeController extends Controller { public function index() { return view('welcome'); } }indexメソッドはview関数を実行し、その戻り値を返しています。
viewの戻り値がブラウザへのレスポンスになりますRouting 設定のアクションをクロージャーからコントローラに変更します。
routes/web.php//Route::get('/', function () { // return view('welcome'); //}); Route::get('/', 'WelcomeController@index');
HTTPメソッド パス アクション GET / WelcomeControllerクラスのindex メソッドを実行する まとめ
このことから
Route::get('/', 'WelcomeController@index');
ルーティングでアクセスするコントローラを決め、実行されると
コントローラのindexメソッドからwelcome.blade.phpを表示命令し
welcome.blade.phpが表示されるのが分かります。簡単にですが一連の流れを覚えておくとイメージしやすいかと思います。
- 投稿日:2020-04-21T12:55:22+09:00
【Laravel】リレーションの書き方
はじめに
この記事は初学者の僕が私的メモとして書いているものです。
リレーションの書き方で詰まってる人の役にたてばと思います!モデルにリレーションを書く
UserモデルとCommentモデルのリレーションは1対多となっています。
hasManyメソッド
class User extends Model { // 略 public function comments() //関数名は複数形がベスト { return $this->hasMany('App\Comment'); } }Userモデルが自動的にCommentモデルの主キー、つまり各コメントのidを外部キーとして持ってくれます。
つまり、hasManyメソッドが定義されているクラス名(User)を小文字にして、語尾に_id
をつけたカラム(user_id)をhasManyメソッドの引数のモデル(Comment)の外部キーとして自動で判断してくれます。belongsToメソッド
class Comment extends Model { // 略 public function user() //関数名は単数形がベスト { return $this->belongsTo('App\User'); } }hasManyを書いたらそれに対するモデルにもbelongsToメソッドを定義してあげましょう。
この場合、belongs_toメソッドの引数のUserモデルの主キーが自動的にCommentテーブルの外部キーと判断してくれます。
つまり、belongsToメソッドの引数名を小文字にして、語尾に_id
をつけたものが親モデルに対する外部キーとしてくれます。使い方
リレーションを組んだことで親テーブルの主キーに対応する子テーブルの外部キーを使って、子テーブル内のカラムのデータを取得できるようになります。
例えば一人のユーザーに対するコメント数などを表示させたい場合以下のように書きます。//略 $user->comments()->count('user_id')おわりに
リレーションを記述する時、頭の中で関係性がわかってても実際のコードに落とし込むのが難しかったです。
しかも、自動的にlaravelが外部キーを判断してくれるのでどう動いているのか理解するのに苦労しました。ざっくりとなぐり書きになってしまいましたが、少しでもリレーションの理解が深まればと思います。
内容についても修正点等、ご指摘頂けると幸いです!参考サイト
- 投稿日:2020-04-21T09:00:20+09:00
PHP7.4 ぼくのかんがえたさいきょうのphp.ini
ストーリー
PHPをインストールしたら必ず行う
php.ini
の設定ですが、
ネット上ではPHP5系の情報がたくさん出回っており、非推奨または削除された設定例が数多く困り果てていました。良い感じにまとめてくれてるサイトが見つからなかったので、最強でベストプラクティスな
php.ini
推奨設定を考えました。
異論は受け付けますので、ぜひコメントください。参考設定
PHPでは、開発用と本番用の設定例を用意してくれています。
なんと素晴らしいことなんでしょうか。これをベースに設定します。
- https://github.com/php/php-src/blob/master/php.ini-development
- https://github.com/php/php-src/blob/master/php.ini-production
予め以前の記事で設定の差分を調べておきましたので、よかったらご覧ください。
環境
- PHP 7.4.5 (執筆時のバージョンです。)
※バージョンが異なる場合は公式サイトで有効な設定か確認してください。
開発用 php.ini
php.inizend.exception_ignore_args = off expose_php = on max_execution_time = 30 max_input_vars = 1000 upload_max_filesize = 64M post_max_size = 128M memory_limit = 256M error_reporting = E_ALL display_errors = on display_startup_errors = on log_errors = on error_log = /var/log/php/php-error.log default_charset = UTF-8 [Date] date.timezone = Asia/Tokyo [mysqlnd] mysqlnd.collect_memory_statistics = on [Assertion] zend.assertions = 1 [mbstring] mbstring.language = Japanese本番用 php.ini
php.inizend.exception_ignore_args = on expose_php = off max_execution_time = 30 max_input_vars = 1000 upload_max_filesize = 64M post_max_size = 128M memory_limit = 256M error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT display_errors = off display_startup_errors = off log_errors = on error_log = /var/log/php/php-error.log default_charset = UTF-8 [Date] date.timezone = Asia/Tokyo [mysqlnd] mysqlnd.collect_memory_statistics = off [Assertion] zend.assertions = -1 [mbstring] mbstring.language = Japanese [opcache] opcache.enable = 1 opcache.memory_consumption = 128 opcache.interned_strings_buffer = 8 opcache.max_accelerated_files = 4000 opcache.validate_timestamps = 0 opcache.huge_code_pages = 0 opcache.preload = /var/www/preload.php opcache.preload_user = www-dataオプションの補足
設定値だけだと何を設定しているかわからないので、各項目の補足を付け加えました。
zend.exception_ignore_args
https://www.php.net/manual/ja/migration74.other-changes.php
- 開発 は
off
、本番 はon
- 有効にすると例外のスタックトレースに引数情報が出なくなる
- PHP7.4以降の設定
expose_php
https://www.php.net/manual/ja/ini.core.php#ini.expose-php
- 開発 は
on
、本番 はoff
- 有効にするとHTTPヘッダに
X-Powered-By: PHP/7.4.5
とPHPのバージョン情報が表示されます。max_execution_time
https://www.php.net/manual/ja/info.configuration.php#ini.max-execution-time
- 設定値: 30(秒) デフォルト: 30(秒)
- 1リクエストあたりの最大実行時間(秒)
- コマンドラインから実行する場合のデフォルト設定は 0 です。
- サーバーの負荷を上げることを防止するのに役立ちます。
重たい処理を実行するとこの設定で引っかかるので、その場合はそのコードだけ特別にset_time_limitを呼んであげると良いかもです。
max_input_vars
https://www.php.net/manual/ja/info.configuration.php#ini.max-input-vars
- 設定値: 1000(個) デフォルト: 1000(個)
- 1リクエストで受け付ける最大の入力変数の数
$_GET
,$_POST
,$_COOKIE
それぞれ個別に適用されます。- 設定値を超える場合は
E_WARNING
が発生し、以降の入力変数はリクエストから削除されます。入力フォームが気が狂ったように多い画面とかは1000超えるかも?
upload_max_filesize
https://www.php.net/manual/ja/ini.core.php#ini.upload-max-filesize
- 設定値: 20M デフォルト: 2M
- 設定する単位に短縮表記を使えます
- アップロードされるファイルの最大サイズ。
スマホの写真サイズも大きいので多めにした方が良き
post_max_size
https://www.php.net/manual/ja/ini.core.php#ini.post-max-size
- 設定値: 128M デフォルト: 8M
- upload_max_filesize の設定値より大きくする必要がある。
- POSTデータに許可される最大サイズを設定します。
- ファイルアップロードにも影響します。
memory_limit
https://www.php.net/manual/ja/ini.core.php#ini.memory-limit
- 設定値: 256M デフォルト: 128M
- post_max_size の設定値より大きくする必要がある。
- memory_limit > post_max_size > upload_max_filesize
- 1リクエストあたりの最大メモリ使用量
メモリ設定はサーバーやプロジェクトによるかと思います。
最初から大量に確保するのではなく、必要に応じて上げていくのが良いのかなと思います。error_reporting
https://www.php.net/manual/ja/errorfunc.configuration.php#ini.error-reporting
- 開発 は
E_ALL
、本番 はE_ALL & ~E_DEPRECATED & ~E_STRICT
E_ALL
は 全ての PHP エラーを表示するE_ALL & ~E_DEPRECATED & ~E_STRICT
は 非推奨の警告エラーを除く PHP エラーを表示する。
E_DEPRECATED
は コードの相互運用性や互換性を維持するために PHP がコードの変更を提案する。E_STRICT
は 実行時の注意、将来のバージョンで動作しなくなるコードについて警告する。display_errors
- 開発 は
on
、本番 はoff
- エラーをHTML出力の一部として画面に出力するかどうかを定義します。
- セキュリティ上、本番では
off
推奨display_startup_errors
http://php.net/display-startup-errors
- 開発 は
on
、本番 はoff
display_errors
をon
にした場合でも、PHPの起動シーケンスにおいて発生したエラーは表示されません。- セキュリティ上、本番では
off
推奨log_errors
https://www.php.net/manual/ja/errorfunc.configuration.php#ini.log-errors
- エラーメッセージを、サーバーのエラーログまたはerror_logに記録するかどうかを指します。
- このオプションはサーバーに依存します。
error_log
https://www.php.net/manual/ja/errorfunc.configuration.php#ini.error-log
- スクリプトエラーが記録されるファイル名です。
default_charset = UTF-8
https://www.php.net/manual/ja/ini.core.php#ini.default-charset
- 設定値: UTF-8 デフォルト: UTF-8
- デフォルト文字コード設定
PHP 5.6.0 以降は "UTF-8" がデフォルトになりますが、念のため明示的に指定します。
date.timezone
https://www.php.net/manual/ja/datetime.configuration.php#ini.date.timezone
- 設定値: Asia/Tokyo デフォルト: GMT
- 全ての日付/時刻関数で使用されるデフォルトのタイムゾーン。
mysqlnd.collect_memory_statistics
https://www.php.net/manual/ja/mysqlnd.config.php#ini.mysqlnd.collect-memory-statistics
- 開発 は
on
、本番 はoff
- さまざまなメモリ統計情報の収集を有効にします。
phpinfo()
でmysqli
の統計情報を出力するかどうかzend.assertions
https://www.php.net/manual/ja/ini.core.php#ini.zend.assertions
- 開発 は
1
、本番 は-1
- アサーションのコードを生成して実行します
- 1 アサーションのコードを生成して実行します (開発モード)
- 0 アサーションのコードは生成しますが実行時にはスキップします (実行しません)
- -1 アサーションのコードを生成せず、アサーションのコストがゼロになります (実運用モード)
mbstring.language
https://www.php.net/manual/ja/mbstring.configuration.php#ini.mbstring.language
- 設定値: Japanese デフォルト: neutral
- mbstring で使用される言語設定のデフォルト値。
opcache の設定
https://www.php.net/manual/ja/opcache.configuration.php
本番のみ有効にします。
opcacheするとソースコードのキャッシュ、最適化して高速化が見込めます。ソースコードを変更してもサーバーを再起動しないと変更が反映されなくなるため開発時は使用しません。
opcache.enable
- オペコード・キャッシュを有効にします。
opcache.memory_consumption
- OPcache によって使用される共有メモリ・ストレージのサイズ(MB単位)
opcache.interned_strings_buffer
- インターン (intern) された文字列を格納するために使用されるメモリ量。(MB単位)
opcache.max_accelerated_files
- OPcache ハッシュテーブルのキー(すなわちスクリプト)の最大数
opcache.validate_timestamps
- 有効にすると、OPcache は、スクリプトが更新されたか opcache.revalidate_freq 秒ごとにチェックします。
- 無効にすると、スクリプトの更新をチェックしません。
opcache.huge_code_pages
- PHPコード(textセグメント)を HUGE PAGE にコピーする機能を有効にしたり、無効にしたりできます。
- これにより、パフォーマンスは向上するはずですが、適切なOSの設定が必要です。
※適切なOS設定がいまいちわからなかったので、この設定は無効化しています。
opcache.preload
- サーバが起動した際にコンパイルされ、実行されるPHPスクリプトを指定します。
- PHP7.4以降の設定
※ここはプロジェクトに合わせて自前で用意する必要があります。これは無理に設定しなくてもokと思います。
※Laravelの場合はこちらのコードを参考にしています。 https://github.com/brendt/laravel-preload/blob/master/preload.phpopcache.preload_user
- root ユーザでコードをあらかじめロードすることは、セキュリティ上の理由から禁止されています。
- PHPの実行ユーザーを指定します。
- PHP7.4以降の設定
- Fatal Error "opcache.preload_user" has not been defined
その他
論理値
設定で使用される論理値(true, false, on, off, yes, no)は大文字・小文字は区別しないようなので、True, On等でも認識されます。
とても柔軟で素敵だと思いました???私のphp.iniはどこ?
ここです。
$ php -i | grep php.ini環境変数を使いたい
普通に環境変数読み込めます。
php.inidate.timezone = $TZさいきょうのツール爆誕!
記事と照らし合わせて手元の環境のphp.iniをチェックできるツールができました。
— suin❄️TypeScript入門書執筆中 (@suin) April 21, 2020
## 本番環境としてチェック<br>curl -sS <a href="https://t.co/264zxsg83a">https://t.co/264zxsg83a</a> | php<br>
## 開発環境としてチェック<br>curl -sS <a href="https://t.co/264zxsg83a">https://t.co/264zxsg83a</a> | INIENV=dev php<br>
pic.twitter.com/MigmzN3W1Wお手元の環境と本記事の推奨設定を照らし合わせて、差分を表示する神ツールを @suin 氏が作成してくれました。
開発用の差分チェック
$ curl -sS https://raw.githubusercontent.com/suin/php-playground/master/UcanIniAdvisory/ucan-ini-advisory.php | INIENV=dev php本番用の差分チェック
$ curl -sS https://raw.githubusercontent.com/suin/php-playground/master/UcanIniAdvisory/ucan-ini-advisory.php | php参考
- 投稿日:2020-04-21T00:26:07+09:00
LaravelでFatal error: require(): Failed opening required '/〇〇/public/../vendor/autoload.php' (include_path='.:') in 〇〇/public/index.php on line 24というエラー
問題
laravelプロジェクトで
php artisan serveをしてブラウザをアクセスすると
Warning: require(/〇〇/public/../vendor/autoload.php): failed to open stream: No such file or directory in /〇〇/public/index.php on line 24 Fatal error: require(): Failed opening required '/〇〇/public/../vendor/autoload.php' (include_path='.:') in 〇〇/public/index.php on line 24と出てきた。
解決法
プロジェクト直下で
composer installをする!