20200421のlaravelに関する記事は10件です。

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']));
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelを知らない中級(中年)プログラマーがマイグレーションファイルの仕組みを調べてみたけど全くわからない!その4:「Larabelアプリケーションの初期化の流れ」

INDEX

Laravelを知らない中級(中年)プログラマーがマイグレーションファイルの仕組みを調べてみたけど全くわからない!

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()が何をやっているのか、現段階ではまだ良くわかりません。おそらく後々追うことになるでしょう。今追っているconfigpath.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\Kernelcomposer/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::$repositorynullでない場合はそれを返し、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なので、$valueAPP_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()が呼び出されます。このメソッドはbasePathbootstrapディレクトリを追加して引数の文字列を連結したものを返します。結果、PROJECT_ROOT/bootstrap/cache/config.phpという文字列が返されます。APP_CONFIG_CACHEが存在していた場合は、Str::startsWith($env, '/')が判定され文字列加工をします。startsWithJavaなどで使われる関数でPHP関数にないものを独自に定義したもののようです。文字列が引数で指定された文字列で始まるかを判定してtruefalseを返します。LoadConfiguration::bootstrap()にそのパスが返され、file_existsでそのファイルが存在するか判定され、存在した場合はそのキャッシュファイルを読み込み、読み込みフラグ$loadedFromCachetrueがセットされます。存在していた場合は読み込んだキャッシュを、存在していない場合は空の配列をアプリケーションコンテナに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が代入されます。次にforeachFinderクラスで色々したものを回しています。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->modeIterator\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の正体が明確になりました。

次回

初期化の流れを読んでみてなんとなく、ふんわりやってることがわかってきましたが、まだオブジェクトの生成やイベント周りなどはっきりとつかめていない部分がありますね。徐々に理解していけると良いですが、どうなるでしょうか。次回はこの流れでリポジトリの読込を探っていきたいと思います。

続く☆

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.php
use 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.php
Route::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?終了

上記の実行結果
スクリーンショット 2020-04-17 15.06.54.png

コードをスッキリさせることが可能となる

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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

laravel-session 使い方メモ

laravel-session

  • set
$request->session()->put('loginInfo', $loginInfo);
  • get
$sess = $request->session()->get('loginInfo');
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】Formファサードのselectタグでvalueなしの初期値を入れる方法【Bladeテンプレート】

個人的に調べ方が分からず微妙に詰まった

↓↓これがやりたい
スクリーンショット 2020-04-21 19.08.47.png

調べるとコントローラーで取得した配列に初期値をprependして無理やり初期値を持った配列を指定する方法が多く出てくるが、何かイケてない気がする。

以下のように第4引数にplaceholderを追加するだけでした

第三引数にあたる初期値をnullにして、第四引数に表示したいテキストをplaceholderとして指定するだけです。

{!! Form::select('hoge_id', $arrayHoges, null, ['class' => 'form-hoge',  'placeholder' => '選択してください']) !!}

しっかりvalueなし&デフォルト値でoptionタグの生成ができました。
スクリーンショット 2020-04-21 19.06.45.png

さいごに

めちゃくちゃシンプルで単純でした。
シンプルすぎて逆に記事がほとんどなかったのかもしれませんね泣
他にもっとシンプルな方法ございましたら教えて頂けると助かります!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

俺のLaravelがこんなに遅いわけがない

Laravel

環境(ベース)

  • PHP 7.4.5
  • Laravel 7.5.1

Docker for Macでnginxとphp-fpmコンテナをunixソケットで繋いだ環境で試してます。

環境の差異

  1. OPcache なし
  2. OPcache あり、プリロードなし
  3. 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-data

https://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のポテンシャル半端ないですね!!!

プリロード自体は初めてなので諸々問題出てくるかもしれませんが、何か問題あったらまた記事にしていきたいと思います?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

laravel 【ページの表示フロー】

どのようにlaravelのページが表示されているのかを見ていきたいと思います。

まずは新規のプロジェクトを作成します。プロジェクト名を blog とします。
laravel new blog
cd blog
php artisan serve

まずはこの図のAccess〜showを頭に入れておくと理解しやすいと思います。
3AE1D46A-51B1-4461-B705-DB36A7032905.png

ルーティング

routes/web.php
Route::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.php
namespace 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が表示されるのが分かります。

簡単にですが一連の流れを覚えておくとイメージしやすいかと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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が外部キーを判断してくれるのでどう動いているのか理解するのに苦労しました。

ざっくりとなぐり書きになってしまいましたが、少しでもリレーションの理解が深まればと思います。
内容についても修正点等、ご指摘頂けると幸いです!

参考サイト

Laravel 5.5 Eloquent:リレーション

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP7.4 ぼくのかんがえたさいきょうのphp.ini

ストーリー

PHPをインストールしたら必ず行う php.ini の設定ですが、
ネット上ではPHP5系の情報がたくさん出回っており、非推奨または削除された設定例が数多く困り果てていました。

良い感じにまとめてくれてるサイトが見つからなかったので、最強でベストプラクティスな php.ini 推奨設定を考えました。
異論は受け付けますので、ぜひコメントください。

参考設定

PHPでは、開発用と本番用の設定例を用意してくれています。
なんと素晴らしいことなんでしょうか。これをベースに設定します。

予め以前の記事で設定の差分を調べておきましたので、よかったらご覧ください。

環境

  • PHP 7.4.5 (執筆時のバージョンです。)

※バージョンが異なる場合は公式サイトで有効な設定か確認してください。

開発用 php.ini

php.ini
zend.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.ini
zend.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

http://php.net/display-errors

  • 開発 は on、本番 は off
  • エラーをHTML出力の一部として画面に出力するかどうかを定義します。
  • セキュリティ上、本番では off 推奨

display_startup_errors

http://php.net/display-startup-errors

  • 開発 は on、本番 は off
  • display_errorson にした場合でも、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.php

opcache.preload_user

その他

論理値

設定で使用される論理値(true, false, on, off, yes, no)は大文字・小文字は区別しないようなので、True, On等でも認識されます。
とても柔軟で素敵だと思いました???

私のphp.iniはどこ?

ここです。

$ php -i | grep php.ini

環境変数を使いたい

普通に環境変数読み込めます。

php.ini
date.timezone = $TZ

さいきょうのツール爆誕!

お手元の環境と本記事の推奨設定を照らし合わせて、差分を表示する神ツールを @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

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

をする!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む