Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 67 additions & 18 deletions src/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,57 @@ final class Plugin {
* @codeCoverageIgnore
*/
public function load_integrations() {
new Integrations();
$this->load_service( Integrations::class );
}

/**
* Instantiates a service class without taking down the site when its file can't be loaded.
*
* WordPress' updater deletes the plugin directory before copying the new version into place, so a
* request landing in that window finds part of our source tree missing. Because
* @see register_services() runs on plugins_loaded for every request, an unguarded `new` turns that
* into a fatal error on the front end instead of a single disabled feature.
*
* Note that `::class` is resolved at compile time and doesn't trigger the autoloader, so passing a
* missing class here is safe.
*
* @since 2.6.1
*
* @param string $class_name Fully qualified class name.
*
* @return object|null Null when the class couldn't be loaded.
*/
private function load_service( $class_name ) {
if ( ! class_exists( $class_name ) ) {
$this->log_missing_service( $class_name );

return null;
}

return new $class_name();
}

/**
* Logs a service that couldn't be loaded by @see load_service().
*
* Only logged when WP_DEBUG is enabled: during a plugin update this is expected and transient, and
* we don't want to fill production logs with it.
*
* @since 2.6.1
*
* @param string $class_name Fully qualified class name.
*
* @return void
*
* @codeCoverageIgnore
*/
private function log_missing_service( $class_name ) {
if ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) {
return;
}

// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
error_log( sprintf( 'Plausible Analytics: %s could not be loaded and was skipped.', $class_name ) );
}

/**
Expand Down Expand Up @@ -44,8 +94,8 @@ public function load_plugin_textdomain() {
* @codeCoverageIgnore
*/
public function load_provisioning() {
new Admin\Provisioning();
new Admin\Provisioning\Integrations();
$this->load_service( Admin\Provisioning::class );
$this->load_service( Admin\Provisioning\Integrations::class );
}

/**
Expand All @@ -56,7 +106,7 @@ public function load_provisioning() {
* @codeCoverageIgnore
*/
public function load_settings() {
new Admin\Settings\Page();
$this->load_service( Admin\Settings\Page::class );
}

/**
Expand All @@ -82,7 +132,7 @@ public function register() {
* @return void
*/
public function setup() {
new Setup();
$this->load_service( Setup::class );
}

/**
Expand All @@ -93,26 +143,25 @@ public function setup() {
* @return void
*/
public function register_services() {

if ( is_admin() ) {
add_action( 'init', [ $this, 'load_settings' ] );
add_action( 'init', [ $this, 'load_provisioning' ] );

new Admin\Upgrades();
new Admin\Filters();
new Admin\Actions();
new Admin\Module();
new Admin\PrivacyPolicy();
$this->load_service( Admin\Upgrades::class );
$this->load_service( Admin\Filters::class );
$this->load_service( Admin\Actions::class );
$this->load_service( Admin\Module::class );
$this->load_service( Admin\PrivacyPolicy::class );
}

add_action( 'init', [ $this, 'load_integrations' ] );

new AdminBar();
new Assets();
new Ajax();
new Compatibility();
new InitOptions();
new Proxy();
new Verification();
$this->load_service( AdminBar::class );
$this->load_service( Assets::class );
$this->load_service( Ajax::class );
$this->load_service( Compatibility::class );
$this->load_service( InitOptions::class );
$this->load_service( Proxy::class );
$this->load_service( Verification::class );
}
}
16 changes: 16 additions & 0 deletions tests/integration/PluginTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,25 @@
namespace Plausible\Analytics\Tests\Integration;

use Plausible\Analytics\Tests\TestCase;
use Plausible\Analytics\WP\AdminBar;
use Plausible\Analytics\WP\Plugin;

class PluginTest extends TestCase {
/**
* A service whose file is missing (e.g. because WordPress is mid-update) should be skipped
* instead of failing the request.
*
* @see Plugin::load_service()
*/
public function testLoadServiceSkipsMissingClass() {
$class = new Plugin();
$method = new \ReflectionMethod( $class, 'load_service' );
$method->setAccessible( true );

$this->assertNull( $method->invoke( $class, '\Plausible\Analytics\WP\ThisClassDoesNotExist' ) );
$this->assertInstanceOf( AdminBar::class, $method->invoke( $class, AdminBar::class ) );
}

/**
* @see Plugin::register()
*/
Expand Down
Loading