roots / acorn

Laravel components for WordPress plugins and themes
https://roots.io/acorn/
MIT License
814 stars 94 forks source link

Acorn scheduler is not loading wordpress correctly #383

Open tgeorgel opened 3 months ago

tgeorgel commented 3 months ago

Version

v4.2.2

What did you expect to happen?

I'm using Acorn in combinaison with Radicle.

I did register a Kernel instance :

\Roots\bootloader()->boot(function ($app) {
    $app->singleton(
        \Illuminate\Contracts\Console\Kernel::class,
        \App\Console\Kernel::class
    );
});

Which provides schedules :

namespace App\Console;

use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Contracts\Foundation\Application;
use Roots\Acorn\Console\Kernel as RootsKernel;
use Illuminate\Console\Scheduling\Schedule;

class Kernel extends RootsKernel
{
    protected function schedule(Schedule $schedule)
    {
        $schedule->command('debug:dump')->everySecond();
    }

    public function __construct(Application $app, Dispatcher $events)
    {
        if (! defined('ARTISAN_BINARY')) {
            define('ARTISAN_BINARY', dirname(__DIR__, 2) . '/vendor/roots/acorn/bin/acorn');
        }

        $this->app = $app;
        $this->events = $events;

        // Make sure our schedules are getting registered
        $this->app->extend(Schedule::class, function ($app) {
            return tap(new Schedule($this->scheduleTimezone()), function ($schedule) {
                $this->schedule($schedule->useCache($this->scheduleCache()));
            });
        });

        $this->app->booted(function () {
            $this->defineConsoleSchedule();
        });
    }
}

Schedules are working nicely, but the load of Wordpress is not complete.
For example, If I would want to get the permalink of a page inside my command :

// App\Console\Commands\DebugDump

public function handle()
{
    ray(get_permalink(2));
}

When using WP-CLI, this works perfectly :

wp acorn debug:dump 

// http://site.test/my-page-name

But in the scheduler context, I get :

 wp acorn schedule:test

Running ['vendor/roots/acorn/bin/acorn' debug:dump] ................................................................................... 344ms DONE
  ⇂ '/opt/homebrew/Cellar/php@8.2/8.2.18/bin/php' 'vendor/roots/acorn/bin/acorn' debug:dump > '/dev/null' 2>&1

 // http://site.test/?page_id=2

What actually happens?

When running the scheduler with acorn, the acorn binary is called.

This binary loads wordpress and then expect acorn to boot on top :

require_once "{$rootPath}/{$composer['extra']['wordpress-install-dir']}/wp-blog-header.php";

Roots\Acorn\Bootloader::getInstance()->boot();

The seconds line nevers gets called.

What actually happens is that the wp-settings.php file is going to load the mu-plugins, which loads Acorn (00-acorn-boot.php), and the load of Acorn "captures" the "request", and so any code that should have run after the load of mu-plugins (inside wp-settings.php) won't run, including settings up default post_types and so on :

// file: wp-settings.php

// Load must-use plugins.
foreach ( wp_get_mu_plugins() as $mu_plugin ) {
    $_wp_plugin_file = $mu_plugin;
    include_once $mu_plugin; // <-- loading acorn will stop here.
    $mu_plugin = $_wp_plugin_file;
    do_action( 'mu_plugin_loaded', $mu_plugin );
}

/** Never reach this part */

/** ... */

create_initial_taxonomies();
create_initial_post_types();

/** ... */

This means that if I run a command with the scheduler :

 wp acorn schedule:test
  Running ['vendor/roots/acorn/bin/acorn' debug:dump] ................................................................................... 344ms DONE
  ⇂ '/opt/homebrew/Cellar/php@8.2/8.2.18/bin/php' 'vendor/roots/acorn/bin/acorn' debug:dump > '/dev/null' 2>&1

The debug:dump command won't be able to get a permalink (eg: get_permalink(1)) as the internal get_post_status_object() function could not return values because the core is not fully loaded.

Steps to reproduce

Create a Kernel class :

namespace App\Console;

use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Contracts\Foundation\Application;
use Roots\Acorn\Console\Kernel as RootsKernel;
use Illuminate\Console\Scheduling\Schedule;

class Kernel extends RootsKernel
{
    protected function schedule(Schedule $schedule)
    {
        $schedule->command('debug:dump')->everySecond();
    }

    public function __construct(Application $app, Dispatcher $events)
    {
        if (! defined('ARTISAN_BINARY')) {
            define('ARTISAN_BINARY', dirname(__DIR__, 2) . '/vendor/roots/acorn/bin/acorn');
        }

        $this->app = $app;
        $this->events = $events;

        // Make sure our schedules are getting registered
        $this->app->extend(Schedule::class, function ($app) {
            return tap(new Schedule($this->scheduleTimezone()), function ($schedule) {
                $this->schedule($schedule->useCache($this->scheduleCache()));
            });
        });

        $this->app->booted(function () {
            $this->defineConsoleSchedule();
        });
    }
}

Make sure to register the kernel class when booting acorn :

\Roots\bootloader()->boot(function ($app) {
    $app->singleton(
        \Illuminate\Contracts\Console\Kernel::class,
        \App\Console\Kernel::class
    );
});

Then, after creating a command which tries to display a permalink, test it thru the scheduler using wp acorn schedule:test.

System info

MacBook Air M1 MacOS Sonoma 14.5 PHP 8.2.18

Log output

No response

Please confirm this isn't a support request.

Yes

mfr75 commented 1 month ago

up

RafaelKr commented 4 weeks ago

I also had a similar setup and noticed it would boot the Acorn Kernel AND my custom Kernel.

Maybe you can try to overwrite the implementation for \Roots\Acorn\Console\Kernel instead:

\Roots\bootloader()->boot(function ($app) {
    $app->singleton(
        \Roots\Acorn\Console\Kernel::class,
        \App\Console\Kernel::class
    );
});

// see comment below
// }, 0);

Also the Acorn boot priority was updated recently: https://github.com/roots/radicle/commit/5af169578560f6b1b7ec0879b4d4ce2d7842f25c

This is also something you could try.

Recently I got rid of overwriting the Kernel and moved my logic to a ServiceProvider instead. But I don't know if that's also possible for scheduling.

Details ```php extend(Kernel::class, function (Kernel $kernel) { // We need to add this first to make sure we're able to set headers return $kernel->prependMiddlewareToGroup('web', ServerPush::class); }); } // [...] } ```