Skip to content

Commit 943b0ba

Browse files
authored
Lightweight static constructor initialiser (#7)
* Implement very lightweight static constructor initialiser with custom SPL autoloader w/o reflections. * Remove unneeded dependency on * Update documentation * Covered with tests
1 parent 829ef9e commit 943b0ba

20 files changed

+286
-26
lines changed

README.md

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@ final class Action extends Enumeration
2626
public static $view;
2727
public static $edit;
2828
}
29-
// to avoid manual initialization you can setup the "vladimmi/construct-static" custom loader
3029
Action::initialize();
3130
```
31+
**Note!** You should always call the `Enumeration::initialize()` method right after Enumeration Class declaration.
32+
To avoid manual initialization you can setup the [StaticConstructorLoader](#class-static-initialization) provided in this library.
3233

3334
Declaration with Typed Properties support:
3435
```php
@@ -129,7 +130,7 @@ multiple Enumeration classes.
129130
3. Implementation is based on assumption that all class static properties are elements of Enum. <!-- If there is a need to declare
130131
any static property that isn't an Enum element then you should override the `\Dbalabka\Enumeration\Enumeration::$notEnumVars` method. -->
131132
4. The method `Dbalabka\Enumeration\Enumeration::initialize()` should be called after each Enumeration class declaration. Please use the
132-
[vladimmi/construct-static](https://github.com/vladimmi/construct-static) custom loader to avoid boilerplate code.
133+
[StaticConstructorLoader](#class-static-initialization) provided in this library to avoid boilerplate code.
133134

134135
## Usage
135136
```php
@@ -168,17 +169,19 @@ Action::$view = null;
168169

169170
### Class static initialization
170171
This implementation relies on class static initialization which was proposed in [Static Class Constructor](https://wiki.php.net/rfc/static_class_constructor).
171-
The RFC describes possible workarounds. The simplest way is to call the initialization method right after class declaration,
172+
The RFC is still in Draft status but it describes possible workarounds. The simplest way is to call the initialization method right after the class declaration,
172173
but it requires the developer to keep this in mind. Thanks to [Typed Properties](https://wiki.php.net/rfc/typed_properties_v2)
173-
we can control uninitialized properties - PHP will throw and error in case of access to an uninitialized property.
174-
It might be automated with custom autoloader implemented in [vladimmi/construct-static](https://github.com/vladimmi/construct-static) library.
174+
we can control uninitialized properties - PHP will throw an error in case of access to an uninitialized property.
175+
It might be automated with custom autoloader [Dbalabka\StaticConstructorLoader\StaticConstructorLoader](./src/StaticConstructorLoader/StaticConstructorLoader.php)
176+
provided in this library:
175177
```php
176-
<?php
177-
// You should always call the initialize() method right after class declaration
178-
// To avoid manual initialization you can setup the "vladimmi/construct-static" custom loader
179-
Action::initialize();
180-
```
181-
See [examples/class_static_construct.php](examples/class_static_construct.php) for example to overcome this limitation.
178+
<?php
179+
use Dbalabka\StaticConstructorLoader\StaticConstructorLoader;
180+
181+
$composer = require_once(__DIR__ . '/vendor/autoload.php');
182+
$loader = new StaticConstructorLoader($composer);
183+
```
184+
See [examples/class_static_construct.php](examples/class_static_construct.php) for example.
182185

183186
### Serialization
184187
There is no possibility to serialize the singleton. As a result, we have to restrict direct Enum object serialization.

composer.json

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,20 @@
1313
"php": ">=7.1"
1414
},
1515
"require-dev": {
16-
"vladimmi/construct-static": "dev-master",
1716
"myclabs/php-enum": "^1.0",
1817
"phpunit/phpunit": "^7.5",
1918
"phpbench/phpbench": "^0.16.9"
2019
},
21-
"suggest": {
22-
"vladimmi/construct-static": "Allows to call __constructStatic on class load"
23-
},
2420
"autoload": {
2521
"psr-4": {
26-
"Dbalabka\\Enumeration\\": "src"
22+
"Dbalabka\\": "src"
2723
}
2824
},
2925
"autoload-dev": {
3026
"psr-4": {
3127
"Dbalabka\\Enumeration\\Examples\\": "examples",
32-
"Dbalabka\\Enumeration\\Tests\\": "tests"
28+
"Dbalabka\\Enumeration\\Tests\\": "tests\\Enumeration",
29+
"Dbalabka\\StaticConstructorLoader\\Tests\\": "tests\\StaticConstructorLoader"
3330
}
3431
}
3532
}

examples/class_static_construct.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
declare(strict_types=1);
33

44
use Dbalabka\Enumeration\Examples\Enum\Color;
5+
use Dbalabka\StaticConstructorLoader\StaticConstructorLoader;
56

67
$composer = require_once(__DIR__ . '/../vendor/autoload.php');
7-
$loader = new ConstructStatic\Loader($composer);
8+
$loader = new StaticConstructorLoader($composer);
89

910
assert(Color::$red instanceof Color && Color::$red === Color::$red);

src/Enumeration.php renamed to src/Enumeration/Enumeration.php

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
use Dbalabka\Enumeration\Exception\EnumerationException;
77
use Dbalabka\Enumeration\Exception\InvalidArgumentException;
8+
use Dbalabka\StaticConstructorLoader\StaticConstructorInterface;
89
use function array_search;
910
use function get_class_vars;
1011
use function sprintf;
@@ -17,7 +18,7 @@
1718
*
1819
* @author Dmitry Balabka <dmitry.balabka@gmail.com>
1920
*/
20-
abstract class Enumeration
21+
abstract class Enumeration implements StaticConstructorInterface
2122
{
2223
const INITIAL_ORDINAL = 0;
2324

@@ -28,13 +29,7 @@ abstract class Enumeration
2829

2930
private static $initializedEnums = [];
3031

31-
/**
32-
* This method should be called right after enumerate class declaration.
33-
* Unfortunately, PHP does not support static initialization.
34-
* See static init RFC: https://wiki.php.net/rfc/static_class_constructor
35-
* Typed Properties will help to control of calling this method.
36-
*/
37-
final protected static function __constructStatic() : void
32+
final public static function __constructStatic() : void
3833
{
3934
if (self::class === static::class) {
4035
return;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Dbalabka\StaticConstructorLoader\Exception;
5+
6+
/**
7+
* @author Dmitrijs Balabka <dmitry.balabka@gmail.com>
8+
*/
9+
class StaticConstructorLoaderException extends \Exception
10+
{
11+
12+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Dbalabka\StaticConstructorLoader;
5+
6+
/**
7+
* @author Dmitrijs Balabka <dmitry.balabka@gmail.com>
8+
*/
9+
interface StaticConstructorInterface
10+
{
11+
/**
12+
* This method should be called right after enumerate class declaration.
13+
* Unfortunately, PHP does not support static initialization.
14+
* See static init RFC: https://wiki.php.net/rfc/static_class_constructor
15+
*/
16+
public static function __constructStatic();
17+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Dbalabka\StaticConstructorLoader;
5+
6+
use Composer\Autoload\ClassLoader;
7+
use Dbalabka\StaticConstructorLoader\Exception\StaticConstructorLoaderException;
8+
9+
/**
10+
* Decorates the Composer autoloader to statically initialize the class.
11+
* This is a very lightweight workaround which is described in https://wiki.php.net/rfc/static_class_constructor
12+
*
13+
* @author Dmitrijs Balabka <dmitry.balabka@gmail.com>
14+
*/
15+
final class StaticConstructorLoader
16+
{
17+
/**
18+
* @var ClassLoader
19+
*/
20+
private $classLoader;
21+
22+
public function __construct(ClassLoader $classLoader)
23+
{
24+
$this->classLoader = $classLoader;
25+
26+
// find Composer autoloader
27+
$loaders = spl_autoload_functions();
28+
$otherLoaders = [];
29+
$composerLoader = null;
30+
foreach ($loaders as $loader) {
31+
if (is_array($loader)) {
32+
if ($loader[0] === $classLoader) {
33+
$composerLoader = $loader;
34+
break;
35+
}
36+
if ($loader[0] instanceof self) {
37+
throw new StaticConstructorLoaderException(sprintf('%s already registered', self::class));
38+
}
39+
}
40+
$otherLoaders[] = $loader;
41+
}
42+
43+
if (!$composerLoader) {
44+
throw new StaticConstructorLoaderException(sprintf('%s was not found in registered autoloaders', ClassLoader::class));
45+
}
46+
47+
// unregister Composer autoloader and all preceding autoloaders
48+
array_map('spl_autoload_unregister', array_merge($otherLoaders, [$composerLoader]));
49+
50+
// restore the original queue order
51+
$loadersToRestore = array_merge([[$this, 'loadClass']], array_reverse($otherLoaders));
52+
$flagTrue = array_fill(0, count($loadersToRestore), true);
53+
array_map('spl_autoload_register', $loadersToRestore, $flagTrue, $flagTrue);
54+
}
55+
56+
public function loadClass($className)
57+
{
58+
$result = $this->classLoader->loadClass($className);
59+
if ($result === true && $className !== StaticConstructorInterface::class && is_a($className, StaticConstructorInterface::class, true)) {
60+
$className::__constructStatic();
61+
}
62+
return $result;
63+
}
64+
}
File renamed without changes.

0 commit comments

Comments
 (0)