• Время чтения ~2 мин
  • 27.06.2023

В типичной настройке стека LEMP приложение Laravel загружается при каждом новом запросе, регистрируются привязки контейнеров, создаются новые экземпляры, запускается промежуточное ПО, вызываются действия маршрута, а затем генерируется ответ для отправки в браузер.

При запуске вашего приложения под Laravel Octane меняется одна важная вещь; ваше приложение загружается только один раз при запуске рабочих процессов Octane, и этот же экземпляр будет использоваться для всех запросов.

Чтобы понять, как это работает, давайте посмотрим, что происходит при запуске воркера Octane:

$app = require BASE_PATH . '/bootstrap/app.php'
$app->bootstrapWith([
    LoadEnvironmentVariables::class,
    LoadConfiguration::class,
    HandleExceptions::class,
    RegisterFacades::class,
    SetRequestForConsole::class,
    RegisterProviders::class,
    BootProviders::class,
]);
$app->loadDeferredProviders();
foreach ($app->make('config')->get('octane.warm') as $service) {
    $app->make($service);
}

Как видите, экземпляр приложения Laravel создается при запуске воркера. Затем этот экземпляр загружается путем выполнения нескольких действий, таких как загрузка файлов конфигурации, регистрация фасадов и регистрация поставщиков услуг.

На этом этапе были вызваны все методы и boot методы неотложенных поставщиков услуг, что означает, что все register службы приложений теперь привязаны к контейнеру. Другими словами, контейнер теперь знает, как разрешать все привязки службы приложения. Если мы обратимся app('config') к этому моменту, контейнер будет знать, как разрешить репозиторий конфигурации для вас.

Затем вызов loadDeferredProviders() метода гарантирует, что все ваши отложенные поставщики также зарегистрированы и загружены. Так как приложение загружается только один раз, поставщик отложений не будет иметь никаких преимуществ в производительности. Octane загрузит их все, поэтому все услуги, которые они предоставляют, будут привязаны к контейнеру и могут быть решены по любому запросу.

Привязки в контейнере могут быть зарегистрированы как синглтоны, эти специальные привязки будут разрешаться только один раз за время существования приложения. Разрешенные экземпляры будут храниться в кэше контейнера, и те же экземпляры будут повторно использоваться в течение всего жизненного цикла приложения. Это повышает производительность приложения, так как вам не нужно будет создавать эти экземпляры снова и снова каждый раз, когда вам нужно разрешить их из контейнера.

При работе на Octane следует обратить особое внимание на синглтоны. Поскольку они будут разрешены только один раз, любое состояние, хранящееся в этих экземплярах, будет сохраняться до тех пор, пока работает сервер Octane.

Экземпляры разрешаются вызовом $app->resolve('singleton') или $app->make('singleton'). Поэтому, boot если вы разрешаете какие-либо синглтоны внутри ваших поставщиков услуг или register методов. Эти синглтоны сохранятся. Синглтоны, которые разрешаются при обработке запросов, не будут сохраняться, они будут создаваться для каждого запроса при работе в Octane. Вам не нужно беспокоиться об этом.

В случае, если вы хотите разрешить некоторые синглтоны во время запуска воркера, и вы явно не разрешаете их в своем поставщике услуг, Octane позволит вам установить набор сервисов для предварительного прогрева. Это то, что делают последние строки кода в приведенном выше примере:

foreach ($app->make('config')->get('octane.warm') as $service) {
    $app->make($service);
}

Octane перебирает эти службы и разрешает их за вас. Таким образом, они будут сохраняться в памяти контейнера до тех пор, пока работает Octane.

Обработка запросов Теперь

, когда у нас есть экземпляр приложения с контейнером службы, который знает, как разрешать все наши привязки, а также имеет несколько предварительно разрешенных (разогретых) синглтонов, мы готовы обрабатывать запросы.

Вот как Octane обрабатывает входящие запросы:

$server->on('request', function($request) use ($app){
    $sandbox = clone $app;
    Container::setInstance($sandbox);
    $sandbox->make('events')->dispatch(new RequestReceived);
    $response = $sandbox->make(Kernel::class)->handle($request);
    Container::setInstance($app);
    return $response;
});

Octane клонирует исходный экземпляр приложения и использует клон для обработки входящего запроса. Наличие экземпляра песочницы для каждого запроса позволяет Octane очищать некоторые сервисы, которые мутировали во время обработки запроса, чтобы следующий запрос получал чистое состояние.

Как мы объясняли ранее, синглтоны, которые сохраняются в течение всего срока службы сервера Octane, могут быть полезны для повышения производительности. Но наши приложения не терпят мутаций этих экземпляров, просачивающихся между запросами. Например, служба может мутировать во время запроса, вызвав что-то вроде:

app('config')->set('services.aws.key', AWS_KEY_HERE);

Теперь при обработке следующего запроса ключ конфигурации по-прежнему будет содержать значение, config services.aws.key установленное предыдущим запросом. Отследить эти утечки может быть очень сложно, особенно если они были мутированы сторонними пакетами, не созданными разработчиком. По этой причине Laravel предоставляет приложению-песочнице отдельную config службу, которая обрезается после каждого запроса, сохраняя при этом исходную config службу неизменной внутри исходного экземпляра приложения.

$sandbox->instance('config', clone $sandbox['config']);

В приведенном выше коде показано, как Octane предоставляет каждой песочнице клонированный экземпляр исходного config сервиса.

Теперь вернемся к тому, как Octane обрабатывает запросы. Перед тем, как экземпляр песочницы будет использован для обработки запроса, Octane отправляет RequestReceived событие. Octane прослушивает это событие и выполняет несколько шагов, чтобы подготовить экземпляр песочницы к обработке запроса.

Вот некоторые из важных вещей, которые Octane делает при RequestReceived отправке события:

$sandbox->instance('config', clone $sandbox['config']);
$sandbox[Kernel::class]->setApplication($sandbox);
$sandbox['cache']->store('array')->flush();
$sandbox['session']->driver()->flush();
$sandbox['session']->driver()->regenerate();
$sandbox['translator']->setLocale($sandbox['config']['app.locale']);
$sandbox['translator']->setFallback($sandbox['config']['app.fallback_locale']);
$sandbox['auth']->forgetGuards();
$app->instance('request', $request);
$sandbox->instance('request', $request);

Во-первых, он клонирует чистую копию репозитория конфигурации из свежего экземпляра песочницы, который был только что клонирован. Затем он привязывает этот свежий репозиторий конфигурации к приложению песочницы. Отныне любые изменения в конфигурациях должны влиять только на песочницу.

Затем Octane передал экземпляр песочницы нескольким контейнерным сервисам. Таким образом, при $this->app вызове внутри этих служб будет возвращен экземпляр песочницы, а не исходный экземпляр приложения. Octane делает это с несколькими сервисами, но мы показываем сервис только в нашем примереKernel.

Затем Octane очищает хранилище кэша массива и состояние сеанса, чтобы они не сохранялись между запросами. По той же причине он также устанавливает языковые стандарты внутри переводчика на исходные языковые стандарты.

После этого Octane удаляет все экземпляры защитников аутентификации, поэтому для каждого запроса создаются новые экземпляры. Это очень важно, так как эти экземпляры защиты кэшируют аутентифицированного пользователя внутри себя, и нам нужно установить пользователя для каждого запроса, поскольку это может быть другой пользователь.

Наконец, Octane передает экземпляр входящего запроса исходному экземпляру приложения, а также экземпляру песочницы. Причина, по которой запрос передается исходному экземпляру приложения, заключается в том, что мы хотим, чтобы все службы, содержащие ссылку на исходный экземпляр приложения, по-прежнему могли получить доступ к запросу. Даже несмотря на то, что Octane настоятельно не рекомендует передавать ссылки на экземпляр приложения, мы по-прежнему передаем запрос на обработку случаев, когда это происходит, до тех пор, пока все приложения и пакеты не смогут адаптировать свой код.

На что следует обратить внимание Теперь

, когда мы понимаем, как Octane загружает наше приложение и использует его для обработки нескольких входящих запросов, я хочу выделить несколько подводных камней для разработчиков приложений и сопровождающих пакетов:

Передача экземпляра приложения службам

Если сервису необходимо взаимодействовать с экземпляром приложения, и вы передаете его в конструкторе, убедитесь, что вы используете экземпляр приложения, переданный обратному вызову, а не тот, который передан поставщику услуг:Причина в том,

// Instead of...
$this->app->bind(Service::class, function () {
    return new Service($this->app);
});
// Do...
$this->app->bind(Service::class, function ($app) {
    return new Service($app);
});

что он $this->app содержит ссылку на исходный экземпляр приложения, поскольку именно он использовался для регистрации поставщиков при загрузке. Пока песочница разрешает службу, вы хотите, чтобы экземпляр песочницы передавался конструктору службы, а не исходному экземпляру.

Другой вариант — не передавать экземпляр приложения конструктору и вместо этого использовать вспомогательный app() компонент внутри службы или Container::getInstance(). Эти вспомогательные средства всегда будут содержать ссылку на экземпляр песочницы.

Передача экземпляра приложения синглтонам Не передавайте экземпляр приложения синглтону

, вместо этого передайте обратный вызов, который возвращает экземпляр песочницы.

// Instead of...
$this->app->singleton(Service::class, function ($app) {
    return new Service($app);
});
// Do...
$this->app->singleton(Service::class, function ($app) {
    return new Service(fn () => Container::getInstance());
});

Синглтоны могут сохраняться между запросами, что означает, что экземпляр приложения, переданный при первом разрешении синглтона, будет использоваться при использовании службы при каждом запросе.

Передача экземпляра запроса синглтонам

Как и в случае с экземпляром приложения, вместо этого передайте обратный вызов:

// Instead of...
$this->app->singleton(Service::class, function ($app) {
    return new Service($app['request']);
});
// Do...
$this->app->singleton(Service::class, function ($app) {
    return new Service(fn () => Container::getInstance()['request']);
});

Или используйте вспомогательный request() элемент внутри службы и не вставляйте запрос в качестве зависимости.

Передача экземпляра репозитория конфигурации синглтонам Как и в двух приведенных выше случаях, вместо этого передайте обратный вызов или используйте вспомогательный config() элемент:

Сохранение синглтонов

// Instead of...
$this->app->singleton(Service::class, function ($app) {
    return new Service($app['config']);
});
// Do...
$this->app->singleton(Service::class, function ($app) {
    return new Service(fn() => Container::getInstance()['config']);
});

Между запросами будут сохраняться только синглтоны

, разрешенные во время начальной загрузки приложения. Синглтоны, разрешенные при обработке запроса, будут зарегистрированы в контейнере песочницы, этот контейнер уничтожается после обработки запроса.

Чтобы сохранить синглтоны между запросами, вы можете либо разрешить их в своих поставщиках услуг, либо добавить их в массив внутри файла конфигурации Octane:С другой стороны, если у вас есть пакет, который регистрирует и разрешает синглтон внутри поставщика услуг, и вы хотите очищать этот экземпляр перед каждым запросом, добавьте его в warm flush массив внутри файла конфигурации:

'warm' => [
    ...Octane::defaultServicesToWarm(),
    Service::class
],

'flush' => [
    Service::class
],

Octane удалит эти синглтоны из контейнера после обработки каждого запроса.

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Об авторе CrazyBoy49z
WORK EXPERIENCE
Контакты
Ukraine, Lutsk
+380979856297