I have an interesting scenario in that I need a function to be defined in order to make tests for another function. The function I want to test looks something like this:
if (function_exists('foo') && ! function_exists('baz')) {
/**
* Baz function
*
* #param integer $n
* #return integer
*/
function baz($n)
{
return foo() + $n;
}
}
The reason I am checking for the existence of foo is because it may or may not be defined in a developer's project and the function baz relies on foo. Because of this, I only want baz to be defined if it can call foo.
The only problem is that so far it has been impossible to write tests for. I tried creating a bootstrap script in the PHPUnit configuration that would define a fake foo function and then require the Composer autoloader, but my main script still thinks foo is not defined. foo is not a Composer package and can not otherwise be required by my project. Obviously Mockery will not work for this either. My question is if anyone more experienced with PHPUnit has come across this issue and found a solution.
Thanks!
Start with a slight refactor of the code to make it more testable.
function conditionalDefine($baseFunctionName, $defineFunctionName)
{
if(function_exists($baseFunctionName) && ! function_exists($defineFunctionName))
{
eval("function $defineFunctionName(\$n) { return $baseFunctionName() + \$n; }");
}
}
Then just call it like this:
conditionalDefine('foo', 'bar');
Your PHPUnit test class will contain the following tests:
public function testFunctionIsDefined()
{
$baseName = $this->mockBaseFunction(3);
$expectedName = uniqid('testDefinedFunc');
conditionalDefine($baseName, $expectedName);
$this->assertTrue(function_exists($expectedName));
$this->assertEquals(5, $expectedName(2));
}
public function testFunctionIsNotDefinedBecauseItExists()
{
$baseName = $this->mockBaseFunction(3);
$expectedName = $this->mockBaseFunction($value = 'predefined');
conditionalDefine($base, $expectedName);
// these are optional, you can't override a func in PHP
// so all that is necessary is a call to conditionalDefine and if it doesn't
// error, you're in the clear
$this->assertTrue(function_exists($expectedName));
$this->assertEquals($value, $expectedName());
}
public function testFunctionIsNotDefinedBecauseBaseFunctionDoesNotExists()
{
$baseName = uniqid('testBaseFunc');
$expectedName = uniqid('testDefinedFunc');
conditionalDefine($base, $expectedName);
$this->assertFalse(function_exists($expectedName));
}
protected function mockBaseFunction($returnValue)
{
$name = uniqid('testBaseFunc');
eval("function $name() { return '$value'; }");
return $name;
}
That is sufficient for what you're asking. You can however, further refactor this code extracting the function generation into a code generator. That what you can write unit tests against the generator to make sure it creates the kind of function you expect.
This should work!
use MyProject\baz;
class YourTestCase
{
/** #var callable **/
protected $mockFoo;
/** #var callable **/
protected $fakeFoo;
public function setUp()
{
if (function_exists('foo')) {
$this->mockFoo = function($foosParams) {
foo($foosParams);
// Extra Stuff, as needed to make the test function right.
};
}
$this->fakeFoo = function($foosParams) {
// Completely mock out foo.
};
}
public function testBazWithRealFoo()
{
if (!$this->mockFoo) {
$this->markTestIncomplete('This system does not have the "\Foo" function.');
}
$actualResults = baz($n, $this->mockFoo);
$this->assertEquals('...', $actualResults);
}
public function testBazWithMyFoo()
{
$actualResults = baz($n, $this->fakeFoo);
$this->assertEquals('...', $actualResults);
}
}
Then modify your existing code:
if (function_exists('foo') && ! function_exists('baz')) {
/**
* Baz function
*
* #param integer $n
* #return integer
*/
function baz($n)
{
return foo() + $n;
}
}
namespace MyProject
{
function baz($bazParams, callable $foo = '\foo')
{
return $foo() + $bazParams;
}
}
Then instead of calling baz($n), you need to call:
use MyProject\baz;
baz($bazParams);
It's like Dependency Injection for functions, yo ;-)
Is this sufficient?
to-test.php:
<?php
if (function_exists('foo') && ! function_exists('baz')) {
/**
* Baz function
*
* #param integer $n
* #return integer
*/
function baz($n)
{
return foo() + $n;
}
}
BazTest.php:
<?php
class BazTest extends PHPUnit_Framework_TestCase {
public function setUp()
{
function foo()
{
// Appropriate mock goes here
return 1;
}
include __DIR__ . '/to-test.php';
}
public function testBaz()
{
$this->assertEquals(2, baz(1));
$this->assertEquals(3, baz(2));
}
}
Running the test yields:
PHPUnit 5.4.8 by Sebastian Bergmann and contributors.
. 1 / 1 (100%)
Time: 54 ms, Memory: 8.00MB
OK (1 test, 2 assertions)
It seems this is a situation where you need to mock visibility, which is definitely outside the realm of PHPUnit which a library of classes. So you can still use PHPUnit, but might have to step outside of it to achieve your goal.
The only way I can think of to create a mock of a function is right inside the test case, above the object extending \PHPUnit_Framework_TestCase.
function foo() {
return 1;
}
if (function_exists('foo') && ! function_exists('baz')) {
/**
* Baz function
*
* #param integer $n
* #return integer
*/
function baz($n)
{
return foo() + $n;
}
}
class TestBaz extends \PHPUnit_Framework_TestCase
{
public function test_it() {
$this->assertSame(4,baz(3));
}
}
You might have to create two files, one with foo and one without, or four if you want to test foo/not foo and baz/not baz. This is definitely an outside-the-box option, that I wouldn't normally recommend, but in your case, it might be your best bet.
Adding it in the bootstrap file doesn't work - why?
Is it because sometimes the function baz is (A) correctly created by the system and sometimes (B) you need to mock it? Or (C) do you always need to mock it?
Case A: Why is the code creating a vital function sporadically on the fly?
Case B: A function can only be registrered once, and never unregistered or overwritten. Therefore, you either go with the mock or you don't. No mixing is allowed.
Case C: If you always need to mock it, and you add it to the bootstrap file, it will be defined. Regarding what you've tried, either your bootstrap file for phpunit isn't loaded correctly or you misspelled the function's name.
I'm sure you've correctly configured your phpunit bootstrapping, but for good measure, does it look anything like the following:
/tests/phpunit.xml:
<phpunit
bootstrap="phpunit.bootstrap.php"
</phpunit>
/tests/phpunit.bootstrap.php:
<?php
require(__DIR__ . "/../bootstrap.php"); // Application startup logic; this is where the function "baz" gets defined, if it exists
if (function_exists('foo') && ! function_exists('baz')) {
/**
* Baz function
*
* #param integer $n
* #return integer
*/
function baz($n)
{
return foo() + $n;
}
}
Don't create the function baz on the fly in your tests, e.g. in a setUp function.
Test suites in phpunit use the same bootstrapper. Therefore, if you need to test cases where function baz is defined and other cases where it is not defined (and you need to mock it), you need to split up the tests folder, e.g. in two different folders, each with their phpunit.xml and phpunit.bootstrap.php files. E.g. /tests/with-baz and /tests/mock-baz. From these two folders, run the tests separately. Just create symlinks to the phpunit in each subfolder (e.g. from /test/with-baz create ln -s ../../vendor/bin/phpunit if composer is in the root) to ensure you run the same version of phpunit in both scenarios.
The ultimate solution is, of course, to figure out where the baz function is being defined and manually include the culprit script file, if at all possible, to ensure the correct logic is being applied.
Alternative
Use phpunit's #runInSeparateProcess annotation and define the function as needed.
<?php
class SomeTest extends \PHPUnit_Framework_TestCase
{
/**
* #runInSeparateProcess
*/
public function testOne()
{
if (false === function_exists('baz')) {
function baz() {
return 42;
}
}
$this->assertSame(42, baz());
}
public function testTwo()
{
$this->assertFalse(function_exists('baz'));
}
}
Related
I am trying to test methods from the following class I have written (there are more functions than what is shown, basically, one function for each is_*() method):
class Validate {
private static $initialized = false;
/**
* Construct won't be called inside this class and is uncallable from the outside. This prevents
* instantiating this class. This is by purpose, because we want a static class.
*/
private function __construct() {}
/**
* If needed, allows the class to initialize itself
*/
private static function initialize()
{
if(self::$initialized) {
return;
} else {
self::$initialized = true;
//Set any other class static variables here
}
}
...
public static function isString($string) {
self::initialize();
if(!is_string($string)) throw new InvalidArgumentException('Expected a string but found ' . gettype($string));
}
...
}
When I test if the methods throw an exception on invalid input, it works great! However, when I test if the method works as expected, PHPUnit complains because I have no assert in the test. The specific error is:
# RISKY This test did not perform any assertions
However, I don't have any value to assert against so I'm not sure how to overcome this.
I've read some about testing static methods, but that mostly seems to cover dependencies between static methods. Further, even non-static methods could have no return value, so, how to fix this?
For reference, my test code:
class ValidateTest extends PHPUnit_Framework_TestCase {
/**
* #covers ../data/objects/Validate::isString
* #expectedException InvalidArgumentException
*/
public function testIsStringThrowsExceptionArgumentInvalid() {
Validate::isString(NULL);
}
/**
* #covers ../data/objects/Validate::isString
*/
public function testIsStringNoExceptionArgumentValid() {
Validate::isString("I am a string.");
}
}
Test void function with assertNull:
/**
* #covers ../data/objects/Validate::isString
*/
public function testIsStringNoExceptionArgumentValid() {
$this->assertNull( Validate::isString("I am a string.") );
}
To prevent the warning about the assertions you can use the #doesNotPerformAssertions annotation as explained in the documentation: https://phpunit.de/manual/current/en/appendixes.annotations.html#idp1585440
Or if you prefer code over annotation:
$this->doesNotPerformAssertions();
One solution I have come upon is the following, based on example 2.12 from chapter 2 of PHPUnit. It feels a little hacky to me, but it's the best I've found so far. Also, based on this PHPUnit Gitub issue discussion, it seems several other people want this feature but that there are no plans to implement it.
Change testIsStringNoExceptionArgumentValid() to the following:
/**
* #covers ../data/objects/Validate::isString
*/
public function testIsStringNoExceptionArgumentValid() {
try {
Validate::isString("I am a string.");
} catch (InvalidArgumentException $notExpected) {
$this->fail();
}
$this->assertTrue(TRUE);
}
If you want to test a void function you only need to run it without any assertion.
If it there is any issue it will throw an exception and test will fails. No need to put $this->assertTrue(TRUE); as you are not running an assertion and having assertions is not required to test your code.
You will get a message like
Time: 7.39 seconds, Memory: 16.00 MB
OK (1 test, 0 assertions)
Process finished with exit code 0
If I have a class Foo which extends class Bar, and an instance of Bar, is there anyway to use that instance of Bar to 'populate' a new instance of Foo?
Essentially, I am only able to retrieve Bar, but I want to use Foo in my code as it gives me lots of additional methods.
I saw quite a few solutions come up to similar questions but they all seemed to be python or c#.
I cant use ReflectionClass::newInstanceArgs as I cant access the data that went in to the constructor of Bar to create it in the first place.
The recommended way to accomplish this would be through dependency injection. Your constructor for Foo could accept an instance of Bar, and then you'd have to write the code to load the state of the new Foo object from the Bar object.
If PHP had a feature that does exactly what you describe, it would be problematic, because even if Foo extends Bar, the two classes could still be very different. I don't see how PHP could be smart enough to know how to automagically turn an instance of one class into an instance of another class.
With that said, under the correct circumstances, the (very hacky) "solution" below could do what you describe. I would not recommend actually using it. I mainly just wanted to show that you'd have to resort to some weird stuff to do this.
function convertObject($object, $newClass)
{
return unserialize(
preg_replace(
'/^O\:\d+\:"[^"]+"/',
'O:'.strlen($newClass).':"'.$newClass.'"',
serialize($object)
)
);
}
There is no built-in way to easily do what you want to. The interfaces of these classes must be redesigned a bit. Perhaps something like:
<?php
class Bar
{
...
}
class Foo extends Bar
{
public static function fromBar(Bar $bar)
{
$foo = new self();
... (copy data here) ...
return $foo;
}
}
I just wrote something this example is based on for extending custom drivers that may need additional support package(s) for the aforementioned core driver file.
// File: parent.php
class parent_class() {
protected $_protected_object; // set in constructor
protected $_protected_var = "Something dynamic";
public function __construct() {
// keep in mind that if you don't override this constructor, this will execute when extended
}
public function create_resource() {
$this->_protected_object = new SomeObjectInstance();
}
public function child_class() {
static $child = null;
if (is_null($child)) {
$file = "child.php";
if (!\file_exists($file)) {
throw new Exception("Couldn't load '$file', doesn't exist");
} else {
require_once($file);
$child = new child_class();
$child->__overload("_protected_object", $this->_protected_object)->__overload("_protected_var", $this->_protected_var);
}
}
return $child;
}
}
// File: child.php
class child_class extends parent_class {
protected function __overload($index, &$value) {
$this->$index =& $value;
return $this;
}
public function __construct() {
// REMEMBER: if you don't declare this method, the parent's constructor will execute
}
public function extended_func() {
// code
}
public function etc() {
// code
}
}
// Code instantiating objects:
$parent = new parent_class();
$parent->create_resource();
var_dump($parent);
/**
* Would output something like this:
*
* object(parent_class)#1 (2) {
* ["_protected_object":protected]=>
* object(SomeObjectInstance)#2 (*) {
* %OBJECT PROPERTIES%
* }
* ["_protected_var":protected]=>
* string(17) "Something dynamic"
* }
*/
$child = $parent->child_class();
var_dump($child);
/**
* Would output something like this:
*
* object(child_class)#3 (2) {
* ["_protected_object":protected]=>
* &object(SomeObjectInstance)#2 (*) {
* %OBJECT PROPERTIES%
* }
* ["_protected_var":protected]=>
* &string(17) "Something dynamic"
* }
*/
Notice that the second var_dump() for $child outputs the same information as $parent, except that you can now see said properties are preceded with ampersands (&), denoting that there are now references. So, if you change anything in the parent class instance in regard to those two properties, it will be reflected in the child class instance.
Is there any way to define different mock-expects for different input arguments? For example, I have database layer class called DB. This class has method called "Query ( string $query )", that method takes an SQL query string on input. Can I create mock for this class (DB) and set different return values for different Query method calls that depends on input query string?
It's not ideal to use at() if you can avoid it because as their docs claim
The $index parameter for the at() matcher refers to the index, starting at zero, in all method invocations for a given mock object. Exercise caution when using this matcher as it can lead to brittle tests which are too closely tied to specific implementation details.
Since 4.1 you can use withConsecutive eg.
$mock->expects($this->exactly(2))
->method('set')
->withConsecutive(
[$this->equalTo('foo'), $this->greaterThan(0)],
[$this->equalTo('bar'), $this->greaterThan(0)]
);
If you want to make it return on consecutive calls:
$mock->method('set')
->withConsecutive([$argA1, $argA2], [$argB1], [$argC1, $argC2])
->willReturnOnConsecutiveCalls($retValueA, $retValueB, $retValueC);
The PHPUnit Mocking library (by default) determines whether an expectation matches based solely on the matcher passed to expects parameter and the constraint passed to method. Because of this, two expect calls that only differ in the arguments passed to with will fail because both will match but only one will verify as having the expected behavior. See the reproduction case after the actual working example.
For you problem you need to use ->at() or ->will($this->returnCallback( as outlined in another question on the subject.
Example:
<?php
class DB {
public function Query($sSql) {
return "";
}
}
class fooTest extends PHPUnit_Framework_TestCase {
public function testMock() {
$mock = $this->getMock('DB', array('Query'));
$mock
->expects($this->exactly(2))
->method('Query')
->with($this->logicalOr(
$this->equalTo('select * from roles'),
$this->equalTo('select * from users')
))
->will($this->returnCallback(array($this, 'myCallback')));
var_dump($mock->Query("select * from users"));
var_dump($mock->Query("select * from roles"));
}
public function myCallback($foo) {
return "Called back: $foo";
}
}
Reproduces:
phpunit foo.php
PHPUnit 3.5.13 by Sebastian Bergmann.
string(32) "Called back: select * from users"
string(32) "Called back: select * from roles"
.
Time: 0 seconds, Memory: 4.25Mb
OK (1 test, 1 assertion)
Reproduce why two ->with() calls don't work:
<?php
class DB {
public function Query($sSql) {
return "";
}
}
class fooTest extends PHPUnit_Framework_TestCase {
public function testMock() {
$mock = $this->getMock('DB', array('Query'));
$mock
->expects($this->once())
->method('Query')
->with($this->equalTo('select * from users'))
->will($this->returnValue(array('fred', 'wilma', 'barney')));
$mock
->expects($this->once())
->method('Query')
->with($this->equalTo('select * from roles'))
->will($this->returnValue(array('admin', 'user')));
var_dump($mock->Query("select * from users"));
var_dump($mock->Query("select * from roles"));
}
}
Results in
phpunit foo.php
PHPUnit 3.5.13 by Sebastian Bergmann.
F
Time: 0 seconds, Memory: 4.25Mb
There was 1 failure:
1) fooTest::testMock
Failed asserting that two strings are equal.
--- Expected
+++ Actual
## ##
-select * from roles
+select * from users
/home/.../foo.php:27
FAILURES!
Tests: 1, Assertions: 0, Failures: 1
From what I've found, the best way to solve this problem is by using PHPUnit's value-map functionality.
Example from PHPUnit's documentation:
class SomeClass {
public function doSomething() {}
}
class StubTest extends \PHPUnit_Framework_TestCase {
public function testReturnValueMapStub() {
$mock = $this->getMock('SomeClass');
// Create a map of arguments to return values.
$map = array(
array('a', 'b', 'd'),
array('e', 'f', 'h')
);
// Configure the mock.
$mock->expects($this->any())
->method('doSomething')
->will($this->returnValueMap($map));
// $mock->doSomething() returns different values depending on
// the provided arguments.
$this->assertEquals('d', $stub->doSomething('a', 'b'));
$this->assertEquals('h', $stub->doSomething('e', 'f'));
}
}
This test passes. As you can see:
when the function is called with parameters "a" and "b", "d" is returned
when the function is called with parameters "e" and "f", "h" is returned
From what I can tell, this feature was introduced in PHPUnit 3.6, so it's "old" enough that it can be safely used on pretty much any development or staging environments and with any continuous integration tool.
It seems Mockery (https://github.com/padraic/mockery) supports this. In my case I want to check that 2 indices are created on a database:
Mockery, works:
use Mockery as m;
//...
$coll = m::mock(MongoCollection::class);
$db = m::mock(MongoDB::class);
$db->shouldReceive('selectCollection')->withAnyArgs()->times(1)->andReturn($coll);
$coll->shouldReceive('createIndex')->times(1)->with(['foo' => true]);
$coll->shouldReceive('createIndex')->times(1)->with(['bar' => true], ['unique' => true]);
new MyCollection($db);
PHPUnit, this fails:
$coll = $this->getMockBuilder(MongoCollection::class)->disableOriginalConstructor()->getMock();
$db = $this->getMockBuilder(MongoDB::class)->disableOriginalConstructor()->getMock();
$db->expects($this->once())->method('selectCollection')->with($this->anything())->willReturn($coll);
$coll->expects($this->atLeastOnce())->method('createIndex')->with(['foo' => true]);
$coll->expects($this->atLeastOnce())->method('createIndex')->with(['bar' => true], ['unique' => true]);
new MyCollection($db);
Mockery also has a nicer syntax IMHO. It appears to be a tad slower than PHPUnits built-in mocking capability, but YMMV.
Intro
Okay I see there is one solution provided for Mockery, so as I don't like Mockery, I am going to give you a Prophecy alternative but I would suggest you first to read about the difference between Mockery and Prophecy first.
Long story short: "Prophecy uses approach called message binding - it means that behaviour of the method does not change over time, but rather is changed by the other method."
Real world problematic code to cover
class Processor
{
/**
* #var MutatorResolver
*/
private $mutatorResolver;
/**
* #var ChunksStorage
*/
private $chunksStorage;
/**
* #param MutatorResolver $mutatorResolver
* #param ChunksStorage $chunksStorage
*/
public function __construct(MutatorResolver $mutatorResolver, ChunksStorage $chunksStorage)
{
$this->mutatorResolver = $mutatorResolver;
$this->chunksStorage = $chunksStorage;
}
/**
* #param Chunk $chunk
*
* #return bool
*/
public function process(Chunk $chunk): bool
{
$mutator = $this->mutatorResolver->resolve($chunk);
try {
$chunk->processingInProgress();
$this->chunksStorage->updateChunk($chunk);
$mutator->mutate($chunk);
$chunk->processingAccepted();
$this->chunksStorage->updateChunk($chunk);
}
catch (UnableToMutateChunkException $exception) {
$chunk->processingRejected();
$this->chunksStorage->updateChunk($chunk);
// Log the exception, maybe together with Chunk insert them into PostProcessing Queue
}
return false;
}
}
PhpUnit Prophecy solution
class ProcessorTest extends ChunkTestCase
{
/**
* #var Processor
*/
private $processor;
/**
* #var MutatorResolver|ObjectProphecy
*/
private $mutatorResolverProphecy;
/**
* #var ChunksStorage|ObjectProphecy
*/
private $chunkStorage;
public function setUp()
{
$this->mutatorResolverProphecy = $this->prophesize(MutatorResolver::class);
$this->chunkStorage = $this->prophesize(ChunksStorage::class);
$this->processor = new Processor(
$this->mutatorResolverProphecy->reveal(),
$this->chunkStorage->reveal()
);
}
public function testProcessShouldPersistChunkInCorrectStatusBeforeAndAfterTheMutateOperation()
{
$self = $this;
// Chunk is always passed with ACK_BY_QUEUE status to process()
$chunk = $this->createChunk();
$chunk->ackByQueue();
$campaignMutatorMock = $self->prophesize(CampaignMutator::class);
$campaignMutatorMock
->mutate($chunk)
->shouldBeCalled();
$this->mutatorResolverProphecy
->resolve($chunk)
->shouldBeCalled()
->willReturn($campaignMutatorMock->reveal());
$this->chunkStorage
->updateChunk($chunk)
->shouldBeCalled()
->will(
function($args) use ($self) {
$chunk = $args[0];
$self->assertTrue($chunk->status() === Chunk::STATUS_PROCESSING_IN_PROGRESS);
$self->chunkStorage
->updateChunk($chunk)
->shouldBeCalled()
->will(
function($args) use ($self) {
$chunk = $args[0];
$self->assertTrue($chunk->status() === Chunk::STATUS_PROCESSING_UPLOAD_ACCEPTED);
return true;
}
);
return true;
}
);
$this->processor->process($chunk);
}
}
Summary
Once again, Prophecy is more awesome! My trick is to leverage the messaging binding nature of Prophecy and even though it sadly looks like a typical, callback javascript hell code, starting with $self = $this; as you very rarely have to write unit tests like this I think it's a nice solution and it's definitely easy to follow, debug, as it actually describes the program execution.
BTW: There is a second alternative but requires changing the code we are testing. We could wrap the troublemakers and move them to a separate class:
$chunk->processingInProgress();
$this->chunksStorage->updateChunk($chunk);
could be wrapped as:
$processorChunkStorage->persistChunkToInProgress($chunk);
and that's it but as I didn't want to create another class for it, I prefer the first one.
I'm trying to get NetBeans 6.8 code completion to work for this. It doesn't seem to do it by itself, but I'm wondering if there's some phpdoc magic or something that may help, since it seems pretty darn good at using that.
Take the following two classes:
class A {
public $B;
public function __construct() {
$this->B = new D();
}
public function C() {
echo "C";
}
}
class D {
public $E;
public function __construct() {
// Do stuff.
}
public function F() {
echo "F";
}
}
Now, let's say I do $A = new A(); and then start typing $A->B->
Assuming that both classes are defined in the same file, this works perfectly. I get code complete suggestions for the E variable and the F method.
However, if the classes are split up into A.php and D.php respectively, and included into another file (say, index.php), doing the same thing after including both files gives only No Suggestions.
Any ideas? Thanks in advance!
I think netbeans doesn't look at the actual includes you do, but rather at the project's include path. Set that, and it should work. The reason probably has to do with the fact that most people use autoloading anyway, and following that would be a bit too much to ask.
It works for me in most cases, but if you have problems use /* #var $variable ClassName */ before line with $A = new A()
You can use a shortcut for this: type vdoc and press tab.
To extend upon Mchl's anwser:
If both files are in your project, and you use correct doc-blocks, code completion will find what you're looking for. To give an example:
/**
* Class A
*
* #author Yourname <And#Email>
* #package Example
*/
class A {
/**
* #var D
*/
public $B;
/**
* Constructor
*/
public function __construct() {
$this->B = new D();
}
/**
* Function C
*
* #return string
*/
public function C () {
return "C";
}
}
Typing the /** and hitting enter right before a method or variable will trigger the creation of such a block, provided it has been written already...
I have an helper class with some static functions. All the functions in the class require a ‘heavy’ initialization function to run once (as if it were a constructor).
Is there a good practice for achieving this?
The only thing I thought of was calling an init function, and breaking its flow if it has already run once (using a static $initialized var). The problem is that I need to call it on every one of the class’s functions.
Sounds like you'd be better served by a singleton rather than a bunch of static methods
class Singleton
{
/**
*
* #var Singleton
*/
private static $instance;
private function __construct()
{
// Your "heavy" initialization stuff here
}
public static function getInstance()
{
if ( is_null( self::$instance ) )
{
self::$instance = new self();
}
return self::$instance;
}
public function someMethod1()
{
// whatever
}
public function someMethod2()
{
// whatever
}
}
And then, in usage
// As opposed to this
Singleton::someMethod1();
// You'd do this
Singleton::getInstance()->someMethod1();
// file Foo.php
class Foo
{
static function init() { /* ... */ }
}
Foo::init();
This way, the initialization happens when the class file is included. You can make sure this only happens when necessary (and only once) by using autoloading.
Actually, I use a public static method __init__() on my static classes that require initialization (or at least need to execute some code). Then, in my autoloader, when it loads a class it checks is_callable($class, '__init__'). If it is, it calls that method. Quick, simple and effective...
NOTE: This is exactly what OP said they did. (But didn't show code for.) I show the details here, so that you can compare it to the accepted answer. My point is that OP's original instinct was, IMHO, better than the answer he accepted.
Given how highly upvoted the accepted answer is, I'd like to point out the "naive" answer to one-time initialization of static methods, is hardly more code than that implementation of Singleton -- and has an essential advantage.
final class MyClass {
public static function someMethod1() {
MyClass::init();
// whatever
}
public static function someMethod2() {
MyClass::init();
// whatever
}
private static $didInit = false;
private static function init() {
if (!self::$didInit) {
self::$didInit = true;
// one-time init code.
}
}
// private, so can't create an instance.
private function __construct() {
// Nothing to do - there are no instances.
}
}
The advantage of this approach, is that you get to call with the straightforward static function syntax:
MyClass::someMethod1();
Contrast it to the calls required by the accepted answer:
MyClass::getInstance->someMethod1();
As a general principle, it is best to pay the coding price once, when you code a class, to keep callers simpler.
If you are NOT using PHP 7.4's opcode.cache, then use Victor Nicollet's answer. Simple. No extra coding required. No "advanced" coding to understand. (I recommend including FrancescoMM's comment, to make sure "init" will never execute twice.) See Szczepan's explanation of why Victor's technique won't work with opcode.cache.
If you ARE using opcode.cache, then AFAIK my answer is as clean as you can get. The cost is simply adding the line MyClass::init(); at start of every public method. NOTE: If you want public properties, code them as a get / set pair of methods, so that you have a place to add that init call.
(Private members do NOT need that init call, as they are not reachable from the outside - so some public method has already been called, by the time execution reaches the private member.)
There is a way to call the init() method once and forbid it's usage, you can turn the function into private initializer and ivoke it after class declaration like this:
class Example {
private static function init() {
// do whatever needed for class initialization
}
}
(static function () {
static::init();
})->bindTo(null, Example::class)();
I am posting this as an answer because this is very important as of PHP 7.4.
The opcache.preload mechanism of PHP 7.4 makes it possible to preload opcodes for classes. If you use it to preload a file that contains a class definition and some side effects, then classes defined in that file will "exist" for all subsequent scripts executed by this FPM server and its workers, but the side effects will not be in effect, and the autoloader will not require the file containing them because the class already "exists". This completely defeats any and all static initialization techniques that rely on executing top-level code in the file that contains the class definition.
If you don't like public static initializer, reflection can be a workaround.
<?php
class LanguageUtility
{
public static function initializeClass($class)
{
try
{
// Get a static method named 'initialize'. If not found,
// ReflectionMethod() will throw a ReflectionException.
$ref = new \ReflectionMethod($class, 'initialize');
// The 'initialize' method is probably 'private'.
// Make it accessible before calling 'invoke'.
// Note that 'setAccessible' is not available
// before PHP version 5.3.2.
$ref->setAccessible(true);
// Execute the 'initialize' method.
$ref->invoke(null);
}
catch (Exception $e)
{
}
}
}
class MyClass
{
private static function initialize()
{
}
}
LanguageUtility::initializeClass('MyClass');
?>
Some tests of assigning static public properties :
settings.json :
{
"HOST": "website.com",
"NB_FOR_PAGINA": 8,
"DEF_ARR_SIZES": {
"min": 600,
"max": 1200
},
"TOKEN_TIME": 3600,
"WEBSITE_TITLE": "My website title"
}
now we want to add settings public static properties to our class
class test {
/** prepare an array to store datas */
public static $datas = array();
/**
* test::init();
*/
public static function init(){
// get json file to init.
$get_json_settings =
file_get_contents(dirname(__DIR__).'/API/settings.json');
$SETTINGS = json_decode($get_json_settings, true);
foreach( $SETTINGS as $key => $value ){
// set public static properties
self::$datas[$key] = $value;
}
}
/**
*
*/
/**
* test::get_static_properties($class_name);
*
* #param {type} $class_name
* #return {log} return all static properties of API object
*/
public static function get_static_properties($class_name) {
$class = new ReflectionClass($class_name);
echo '<b>infos Class : '.$class->name.'</b><br>';
$staticMembers = $class->getStaticProperties();
foreach( $staticMembers as $key => $value ){
echo '<pre>';
echo $key. ' -> ';
if( is_array($value) ){
var_export($value);
}
else if( is_bool($value) ){
var_export($value);
}
else{
echo $value;
}
echo '</pre>';
}
// end foreach
}
/**
* END test::get_static_properties();
*/
}
// end class test
ok now we test this code :
// consider we have the class test in API folder
spl_autoload_register(function ($class){
// call path to API folder after
$path_API = dirname(__DIR__).'/API/' . $class . '.php';
if( file_exists($path_API) ) require $path_API;
});
// end SPL auto registrer
// init class test with dynamics static properties
test::init();
test::get_static_properties('test');
var_dump(test::$HOST);
var_dump(test::$datas['HOST']);
this return :
infos Class : test
datas -> array (
'HOST' => 'website.com',
'NB_FOR_PAGINA' => 8,
'DEF_ARR_SIZES' =>
array (
'min' => 600,
'max' => 1200,
),
'TOKEN_TIME' => 3600,
'WEBSITE_TITLE' => 'My website title'
)
// var_dump(test::$HOST);
Uncaught Error: Access to undeclared static property:
test::$HOST
// var_dump(test::$datas['HOST']);
website.com
Then if we modify the class test like this :
class test {
/** Determine empty public static properties */
public static $HOST;
public static $NB_FOR_PAGINA;
public static $DEF_ARR_SIZES;
public static $TOKEN_TIME;
public static $WEBSITE_TITLE;
/**
* test::init();
*/
public static function init(){
// get json file to init.
$get_json_settings =
file_get_contents(dirname(__DIR__).'/API/settings.json');
$SETTINGS = json_decode($get_json_settings, true);
foreach( $SETTINGS as $key => $value ){
// set public static properties
self::${$key} = $value;
}
}
/**
*
*/
...
}
// end class test
// init class test with dynamics static properties
test::init();
test::get_static_properties('test');
var_dump(test::$HOST);
this return :
infos Class : test
HOST -> website.com
NB_FOR_PAGINA -> 8
DEF_ARR_SIZES -> array (
'min' => 600,
'max' => 1200,
)
TOKEN_TIME -> 3600
WEBSITE_TITLE -> My website title
// var_dump(test::$HOST);
website.com
I actually need to initialize an object with public static properties that I will reuse in many other classes, which I think is supposed to, I don't want to do new api() in every method where I would need, for example to check the host of the site or indicate it. Also I would like to make things more dynamic so that I can add as many settings as I want to my API, without having to declare them in my initialization class.
All other methods I've seen no longer work under php > 7.4
I keep looking for a solution for this problem.
Note - the RFC proposing this is still in the draft state.
class Singleton
{
private static function __static()
{
//...
}
//...
}
proposed for PHP 7.x (see https://wiki.php.net/rfc/static_class_constructor )