Skip to content

Commit f0ebe33

Browse files
authored
Use WP Autoloader class (#1051)
* Use WP Autoloader class * Load Autoloader first thing so it can be used at any time after that. * Add unit tests * Revert failed merge conflict resolution. * Be less class name agnostic * Denote global namespace
1 parent d0540b4 commit f0ebe33

File tree

4 files changed

+209
-41
lines changed

4 files changed

+209
-41
lines changed

activitypub.php

Lines changed: 4 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,13 @@
2727
\define( 'ACTIVITYPUB_PLUGIN_FILE', ACTIVITYPUB_PLUGIN_DIR . basename( __FILE__ ) );
2828
\define( 'ACTIVITYPUB_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
2929

30+
require_once __DIR__ . '/includes/class-autoloader.php';
3031
require_once __DIR__ . '/includes/compat.php';
3132
require_once __DIR__ . '/includes/functions.php';
3233
require_once __DIR__ . '/includes/constants.php';
34+
require_once __DIR__ . '/integration/load.php';
35+
36+
Autoloader::register_path( __NAMESPACE__, __DIR__ . '/includes' );
3337

3438
/**
3539
* Initialize REST routes.
@@ -81,45 +85,6 @@ function plugin_init() {
8185
}
8286
\add_action( 'plugins_loaded', __NAMESPACE__ . '\plugin_init' );
8387

84-
85-
/**
86-
* Class Autoloader.
87-
*/
88-
\spl_autoload_register(
89-
function ( $full_class ) {
90-
$base_dir = __DIR__ . '/includes/';
91-
$base = 'Activitypub\\';
92-
93-
if ( strncmp( $full_class, $base, strlen( $base ) ) === 0 ) {
94-
$maybe_uppercase = str_replace( $base, '', $full_class );
95-
$class = strtolower( $maybe_uppercase );
96-
// All classes should be capitalized. If this is instead looking for a lowercase method, we ignore that.
97-
if ( $maybe_uppercase === $class ) {
98-
return;
99-
}
100-
101-
if ( false !== strpos( $class, '\\' ) ) {
102-
$parts = explode( '\\', $class );
103-
$class = array_pop( $parts );
104-
$sub_dir = strtr( implode( '/', $parts ), '_', '-' );
105-
$base_dir = $base_dir . $sub_dir . '/';
106-
}
107-
108-
$filename = 'class-' . strtr( $class, '_', '-' );
109-
$file = $base_dir . $filename . '.php';
110-
111-
if ( file_exists( $file ) && is_readable( $file ) ) {
112-
require_once $file;
113-
} else {
114-
// translators: %s is the class name.
115-
$message = sprintf( esc_html__( 'Required class not found or not readable: %s', 'activitypub' ), esc_html( $full_class ) );
116-
Debug::write_log( $message );
117-
\wp_die( $message ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
118-
}
119-
}
120-
}
121-
);
122-
12388
\register_activation_hook(
12489
__FILE__,
12590
array(
@@ -144,8 +109,6 @@ function ( $full_class ) {
144109
)
145110
);
146111

147-
// Load integrations.
148-
require_once __DIR__ . '/integration/load.php';
149112

150113
/**
151114
* `get_plugin_data` wrapper.

includes/class-autoloader.php

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
/**
3+
* Autoloader for Activitypub.
4+
*
5+
* @package Activitypub
6+
*/
7+
8+
namespace Activitypub;
9+
10+
/**
11+
* An Autoloader that respects WordPress's filename standards.
12+
*/
13+
class Autoloader {
14+
15+
/**
16+
* Namespace separator.
17+
*/
18+
const NS_SEPARATOR = '\\';
19+
20+
/**
21+
* The prefix to compare classes against.
22+
*
23+
* @var string
24+
* @access protected
25+
*/
26+
protected $prefix;
27+
28+
/**
29+
* Length of the prefix string.
30+
*
31+
* @var int
32+
* @access protected
33+
*/
34+
protected $prefix_length;
35+
36+
/**
37+
* Path to the file to be loaded.
38+
*
39+
* @var string
40+
* @access protected
41+
*/
42+
protected $path;
43+
44+
/**
45+
* Constructor.
46+
*
47+
* @param string $prefix Namespace prefix all classes have in common.
48+
* @param string $path Path to the files to be loaded.
49+
*/
50+
public function __construct( $prefix, $path ) {
51+
$this->prefix = $prefix;
52+
$this->prefix_length = \strlen( $prefix );
53+
$this->path = \rtrim( $path . '/' );
54+
}
55+
56+
/**
57+
* Registers Autoloader's autoload function.
58+
*
59+
* @throws \Exception When autoload_function cannot be registered.
60+
*
61+
* @param string $prefix Namespace prefix all classes have in common.
62+
* @param string $path Path to the files to be loaded.
63+
*/
64+
public static function register_path( $prefix, $path ) {
65+
$loader = new self( $prefix, $path );
66+
\spl_autoload_register( array( $loader, 'load' ) );
67+
}
68+
69+
/**
70+
* Loads a class if its namespace starts with `$this->prefix`.
71+
*
72+
* @param string $class_name The class to be loaded.
73+
*/
74+
public function load( $class_name ) {
75+
if ( \strpos( $class_name, $this->prefix . self::NS_SEPARATOR ) !== 0 ) {
76+
return;
77+
}
78+
79+
// Strip prefix from the start (ala PSR-4).
80+
$class_name = \substr( $class_name, $this->prefix_length + 1 );
81+
$class_name = \strtolower( $class_name );
82+
$dir = '';
83+
84+
$last_ns_pos = \strripos( $class_name, self::NS_SEPARATOR );
85+
if ( false !== $last_ns_pos ) {
86+
$namespace = \substr( $class_name, 0, $last_ns_pos );
87+
$namespace = \str_replace( '_', '-', $namespace );
88+
$class_name = \substr( $class_name, $last_ns_pos + 1 );
89+
$dir = \str_replace( self::NS_SEPARATOR, DIRECTORY_SEPARATOR, $namespace ) . DIRECTORY_SEPARATOR;
90+
}
91+
92+
$path = $this->path . $dir . 'class-' . \str_replace( '_', '-', $class_name ) . '.php';
93+
94+
if ( ! \file_exists( $path ) ) {
95+
$path = $this->path . $dir . 'interface-' . \str_replace( '_', '-', $class_name ) . '.php';
96+
}
97+
98+
if ( ! \file_exists( $path ) ) {
99+
$path = $this->path . $dir . 'trait-' . \str_replace( '_', '-', $class_name ) . '.php';
100+
}
101+
102+
if ( \file_exists( $path ) ) {
103+
require_once $path;
104+
}
105+
}
106+
}

tests/class-test-autoloader.php

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php
2+
/**
3+
* Tests for Autoloader class.
4+
*
5+
* @package Activitypub
6+
*/
7+
8+
namespace Activitypub\Tests;
9+
10+
use Activitypub\Autoloader;
11+
12+
/**
13+
* Class Test_Autoloader.
14+
*
15+
* @coversDefaultClass \Activitypub\Autoloader
16+
*/
17+
class Test_Autoloader extends \WP_UnitTestCase {
18+
19+
/**
20+
* Test__construct.
21+
*
22+
* @covers ::__construct
23+
*/
24+
public function test__construct() {
25+
$autoloader = new Autoloader( 'Activitypub', __DIR__ );
26+
27+
$prefix = new \ReflectionProperty( '\Activitypub\Autoloader', 'prefix' );
28+
$this->assertTrue( $prefix->isProtected() );
29+
$prefix->setAccessible( true );
30+
$this->assertSame( $prefix->getValue( $autoloader ), 'Activitypub' );
31+
32+
$prefix_length = new \ReflectionProperty( '\Activitypub\Autoloader', 'prefix_length' );
33+
$this->assertTrue( $prefix_length->isProtected() );
34+
$prefix_length->setAccessible( true );
35+
$this->assertSame( $prefix_length->getValue( $autoloader ), strlen( 'Activitypub' ) );
36+
37+
$path = new \ReflectionProperty( '\Activitypub\Autoloader', 'path' );
38+
$this->assertTrue( $path->isProtected() );
39+
$path->setAccessible( true );
40+
$this->assertSame( $path->getValue( $autoloader ), rtrim( __DIR__ . '/' ) );
41+
}
42+
43+
/**
44+
* Test_register_path.
45+
*
46+
* @covers ::register_path
47+
*/
48+
public function test_register_path() {
49+
Autoloader::register_path( 'Activitypub', __DIR__ );
50+
51+
foreach ( spl_autoload_functions() as $function ) {
52+
if ( is_array( $function ) && $function[0] instanceof Autoloader ) {
53+
$path = new \ReflectionProperty( '\Activitypub\Autoloader', 'path' );
54+
$path->setAccessible( true );
55+
56+
if ( $path->getValue( $function[0] ) === rtrim( __DIR__ . '/' ) ) {
57+
$this->assertTrue( true );
58+
return;
59+
}
60+
}
61+
}
62+
63+
$this->fail( 'Failed asserting that autoload function gets registered.' );
64+
}
65+
66+
/**
67+
* Test_load.
68+
*
69+
* @covers ::load
70+
*/
71+
public function test_load() {
72+
$autoloader = new Autoloader( __NAMESPACE__, __DIR__ );
73+
74+
// Wrong prefix.
75+
$autoloader->load( 'Activitypub\Autoload_Test_File' );
76+
$this->assertFalse( class_exists( 'Activitypub\Autoload_Test_File' ) );
77+
78+
// Right prefix but class doesn't exist.
79+
$autoloader->load( 'Activitypub\Tests\Data\Non\Existent\Class' );
80+
$this->assertFalse( class_exists( 'Activitypub\Tests\Data\Non\Existent\Class' ) );
81+
82+
// Class should load.
83+
$autoloader->load( 'Activitypub\Tests\Data\Autoload_Test_File' );
84+
$this->assertTrue( class_exists( 'Activitypub\Tests\Data\Autoload_Test_File' ) );
85+
}
86+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
/**
3+
* Autoload test file.
4+
*
5+
* @package Activitypub
6+
*/
7+
8+
namespace Activitypub\Tests\Data;
9+
10+
/**
11+
* Class Autoload_Test_File.
12+
*/
13+
class Autoload_Test_File {}

0 commit comments

Comments
 (0)