acorn
acorn copied to clipboard
Acorn scheduler is not loading wordpress correctly
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/[email protected]/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/[email protected]/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
up
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
declare(strict_types=1);
namespace App\Providers;
// [...]
use BabDev\ServerPushManager\Http\Middleware\ServerPush;
use Illuminate\Support\ServiceProvider;
use Roots\Acorn\Http\Kernel;
class WordPressOptimizationProvider extends ServiceProvider
{
// [...]
public function __construct($app)
{
parent::__construct($app);
$app->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);
});
}
// [...]
}
still need help on this one
@mfr75 The first bump wasn't necessary, nor was the second one. Post on Roots Discourse if you want to hire someone from the community if you're looking for immediate help.
@mfr75 Try the following:
- Install WP-CLI via composer (
wp-cli/wp-cli-bundle) - Creating the file below in
bin/artisan - Add
define('ARTISAN_BINARY', realpath(__DIR__ . '/../bin/artisan'));towp-config.php(adjusting the path appropriately).
This completely bypasses vendor/bin/acorn and instead uses the WP-CLI integration which seems to work.
This probably works with a globally installed WP-CLI as well if you adjust the script but YMMV https://github.com/wp-cli/wp-cli/issues/4632.
#!/usr/bin/env php
<?php
$basePath = dirname(__DIR__);
$wpBin = realpath($basePath . '/vendor/bin/wp');
$args = array_slice($argv, 1);
pcntl_exec($wpBin, ['acorn',... $args]);
I ran into the similar issue and figured out a workaround. Sharing it here in case it helps someone 😇
Instead of defining a custom ARTISAN_BINARY and using wp acorn schedule:run, I made a ServiceProvider that uses wp-cron to trigger Acorn’s scheduled tasks:
namespace App\Providers;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Support\ServiceProvider;
class WpCronScheduleProvider extends ServiceProvider {
public const string SCHEDULE_RUN_ACTION = 'acorn_schedule_run';
public function boot(Schedule $schedule): void {
add_action(self::SCHEDULE_RUN_ACTION, function () use ($schedule) {
require_once app_path('schedule.php');
foreach ($schedule->events() as $event) {
if ($event->isDue(app()) && $event->filtersPass(app())) {
$event->run(app());
}
}
});
add_filter('cron_schedules', static function (array $schedules): array {
if (! isset($schedules['minutely'])) {
$schedules['minutely'] = [
'interval' => 60,
'display' => __('Every Minute'),
];
}
return $schedules;
});
if (wp_doing_cron() && ! wp_next_scheduled(self::SCHEDULE_RUN_ACTION)) {
wp_schedule_event(time(), 'minutely', self::SCHEDULE_RUN_ACTION);
}
}
}
And in schedule.php:
use App\Console\Commands\PublishMissedScheduledPosts;
use Illuminate\Support\Facades\{Artisan, Schedule};
Schedule::call(fn () => Artisan::call(PublishMissedScheduledPosts::class))
->name('Publish missed scheduled WordPress posts')
->everyTwoMinutes()
->withoutOverlapping();
I’m using Schedule::call instead of Schedule::command, so commands run in the same PHP process instead of spawning new CLI processes.
This also works on shared hostings where proc_open is blocked.