The ReflectionMethod instance from PHP (http://php.net/manual/en/class.reflectionmethod.php) has the getDocComment method that returns the annotation of a method. This works ok, unless you use unserialized object.
$ref = new ReflectionClass('a');
var_dump(method_exists($ref, 'getDocComment')); //bool(true)
var_dump($ref->getDocComment()); //bool(false)
$ref = unserialize(serialize($ref));
var_dump(method_exists($ref, 'getDocComment')); //bool(true)
var_dump($ref->getDocComment()); //PHP Warning: Uncaught Error: Internal error: Failed to retrieve the reflection object
Is there any way of testing if the ReflectionMethod object has correctly defined doc comment? I mean, I do not care about getting the annotation after serialize/unserialize, but I want to check if calling getDocComment is safe.
Edit: According to responses that advice error handling + fallback, I rephrase the Q.
I have some simple cache of reflections (array of ReflectionMethod objects). Until I use item from that cache, I wold like to chech its correctness. I do NOT want to handle error, I want to "predict error". Awesome would be something like hasDocComment method that does not generate any error, but returns only true/false within any ReflectionMethod object state.
The general approach of serializing reflection objects is wrong. There exists a PHP Bug report for it, but it has been set to "irrelevant":
https://bugs.php.net/bug.php?id=70719
The reason is, that you cannot connect a reflection object back to its class again, because you would have to deal with source code changes and all kinds of stuff. What you should do instead is, to serialize the name of the class and generate a NEW reflection object from that class, when you unserialize.
Code Example:
class A { }
$ref = new ReflectionClass('A');
var_dump(method_exists($ref, 'getDocComment'));
// serialize only the class name
$refClass = unserialize(serialize($ref->getName()));
// get a new reflection object from that class ...
$ref = new ReflectionClass($refClass);
var_dump(method_exists($ref, 'getDocComment'));
// If you want to serialize an object
$a = new A();
$a2 = unserialize(serialize($a));
$ref = new ReflectionClass(get_class($a2));
var_dump(method_exists($ref, 'getDocComment'));
If you need to be able to handle errors, you can try/catch the execution block. Since alt-php71-7.1.0-1 (which you seem to be using), this will throw an instance of Error instead of simply a Fatal Error, which allows you to do error handling.
<?php
class A { }
$ref = new ReflectionClass('A');
var_dump(method_exists($ref, 'getDocComment')); //bool(true)
var_dump($ref->getDocComment()); //bool(false)
// serialize only the class name
$refClass = unserialize(serialize($ref));
try {
$refClass->getDocComment();
// do your work
}
catch (Error $e) {
echo "Malformed Reflection object: ".$e->getMessage();
}
Demo
And since you can still get the class name from the malformed Reflection instance, you can instantiate a new one right in your catch block:
<?php
class A { }
$ref = new ReflectionClass('A');
var_dump(method_exists($ref, 'getDocComment')); //bool(true)
var_dump($ref->getDocComment()); //bool(false)
// serialize only the class name
$refClass = unserialize(serialize($ref));
try {
$refClass->getDocComment();
}
catch (Error $e) {
$recoveredRef = new ReflectionClass($refClass->getName());
var_dump($recoveredRef);
var_dump($recoveredRef->getDocComment()); // works correctly
echo "Malformed Reflection object, but recovered: ".$e->getMessage();
}
Demo
Related
I've been trawling through the PHP docs trying to identify if there is any method that will allow me to differentiate between instances of a built-in class (such as DateTime or PDO) and User-defined Classes, but without any success.
The only approach that I have found so far is to try and bind a closure to the instance. When doing this with a built-in class, it displays a warning (yuk) and returns a null.
$targetObject = new DateTime();
$closure = function();
$test = #$closure->bindTo($targetObject, get_class($targetObject));
if ($test === false) {
throw new Exception('General failure');
} elseif ($test === null) {
throw new Exception('Unable to bind to internal class');
}
Is anybody aware of a cleaner approach to this problem?
Take a look at the reflection API. Manual
<?php
$d = new DateTime();
$r = new ReflectionClass($d);
echo ($r->isInternal());
This seems like a fairly simple reflection problem, yet I can't figure it our. I use Laravel 4.2 on Debian with PHP 5.6.6-1.
Basicly what happens is that I want to spawn a new object from a class in a Laravel QueueHandler like so:
$className = 'MyClass';
$myobject = new $className ();
and this doesn't work. I tried everything I can possibly think of and have no clue where to look. This code doesn;t work while it should:
<?php
use Pronamic\Twinfield\Secure\Config;
use Pronamic\Twinfield\Customer\CustomerFactory;
class TwinfieldQueueHandler {
private $twinfieldConfig = null;
...
try {
$twinfieldFactoryClass = 'CustomerFactory';
//returns 0
echo strcmp('CustomerFactory', $twinfieldFactoryClass);
//works
$test0 = new CustomerFactory ($this->twinfieldConfig);
//throws an exeption with message: "Class CustomerFactory does not exist"
$r = new ReflectionClass($twinfieldFactoryClass);
$test1 = $r->newInstanceArgs($this->twinfieldConfig);
//gives error PHP Fatal error: Class 'CustomerFactory' not found in {file} on line {line}
$test2 = new $twinfieldFactoryClass ($this->twinfieldConfig);
} catch (Exception $e) {
Log::error($e->getMessage());
}
Has anyone got any pointers on where to look and how to debug this?
ReflectionClass will ignore your current namespace and use statements completely. You have to specify the fully qualified name of the class:
$r = new ReflectionClass('Pronamic\Twinfield\Customer\CustomerFactory');
As a user points out on php.net:
To reflect on a namespaced class in PHP 5.3, you must always specify the fully qualified name of the class - even if you've aliased the containing namespace using a "use" statement.
Note that you could work around this by passing an object:
$test0 = new CustomerFactory ($this->twinfieldConfig);
$r = new ReflectionClass($test0);
Can using iterator_to_array() on a MongoCursor instance throw an exception in PHP 5.3? In other words, do I need to wrap iterator_to_array() calls on MongoCursor instances in try-catch statements or not?
e.g.,
$mongo = new Mongo();
$mongo_db = $mongo['my_database'];
$mongo_coll = $mongo_db['my_collection'];
// This
$cursor = $mongo_coll->find();
$documents = iterator_to_array($cursor);
// Versus this.
$cursor = $mongo_coll->find();
try {
$documents = iterator_to_array($cursor);
} catch (Exception $e) {
//...
}
iterator_to_array() can throw exceptions because it calls next().
I think the first comment as of now on this page http://www.php.net/manual/en/mongo.queries.php will be of interest to you, but don't know if it will be first when you view it so here is the deal.
You can check if the cursor is valid by using $cursor->valid().
And the comment says that you might have to rewind the cursor after receiving it, as it is sometime not rewound when received.
...
$cursor = $mongo_coll->find();
$cursor->rewind();
if ($cursor->valid()) {
$documents = iterator_to_array($cursor);
}
The advantage to the above try catch block is that the try catch block might throw the exception while you could have used the cursor when the cursor was actually valid.
Find method returns Traversable object or throws an exception.
Iterator_to_array is accepting Traversable object.
Exception should appear only if something really bad happens in Mongo driver or in Mongo during iteration. Maybe disconnection.
FWIW I'm using SimpleTest 1.1alpha.
I have a singleton class, and I want to write a unit test that guarantees that the class is a singleton by attempting to instantiate the class (it has a private constructor).
This obviously causes a Fatal Error:
Fatal error: Call to private FrontController::__construct()
Is there any way to "catch" that Fatal Error and report a passed test?
No. Fatal error stops the execution of the script.
And it's not really necessary to test a singleton in that way. If you insist on checking if constructor is private, you can use ReflectionClass:getConstructor()
public function testCannotInstantiateExternally()
{
$reflection = new \ReflectionClass('\My\Namespace\MyClassName');
$constructor = $reflection->getConstructor();
$this->assertFalse($constructor->isPublic());
}
Another thing to consider is that Singleton classes/objects are an obstacle in TTD since they're difficult to mock.
Here's a complete code snippet of Mchl's answer so people don't have to go through the docs...
public function testCannotInstantiateExternally()
{
$reflection = new \ReflectionClass('\My\Namespace\MyClassName');
$constructor = $reflection->getConstructor();
$this->assertFalse($constructor->isPublic());
}
You can use a concept like PHPUnit's process-isolation.
This means the test code will be executed in a sub process of php. This example shows how this could work.
<?php
// get the test code as string
$testcode = '<?php new '; // will cause a syntax error
// put it in a temporary file
$testfile = tmpfile();
file_put_contents($testfile, $testcode);
exec("php $tempfile", $output, $return_value);
// now you can process the scripts return value and output
// in case of an syntax error the return value is 255
switch($return_value) {
case 0 :
echo 'PASSED';
break;
default :
echo 'FAILED ' . $output;
}
// clean up
unlink($testfile);
If I am correct, SimpleTest will allow you to assert a PHP error is thrown. However, I can't figure out how to use it, based on the documentation. I want to assert that the object I pass into my constructor is an instance of MyOtherObject
class Object {
public function __construct(MyOtherObject $object) {
//do something with $object
}
}
//...and in my test I have...
public function testConstruct_ExpectsAnInstanceOfMyOtherObject() {
$notAnObject = 'foobar';
$object = new Object($notAnObject);
$this->expectError($object);
}
Where am I going wrong?
Type hinting throws E_RECOVERABLE_ERROR which can be caught by SimpleTest since PHP version 5.2. The following will catch any error containing the text "must be an instance of". The constructor of PatternExpectation takes a perl regex.
public function testConstruct_ExpectsAnInstanceOfMyOtherObject() {
$notAnObject = 'foobar';
$this->expectError(new PatternExpectation("/must be an instance of/i"));
$object = new Object($notAnObject);
}
PHP has both errors and exceptions, which work slightly different. Passing a wrong type to a typehinted function will raise an exception. You have to catch that in your test case. Eg.:
public function testConstruct_ExpectsAnInstanceOfMyOtherObject() {
$notAnObject = 'foobar';
try {
$object = new Object($notAnObject);
$this->fail("Expected exception");
} catch (Exception $ex) {
$this->pass();
}
}
or simply:
public function testConstruct_ExpectsAnInstanceOfMyOtherObject() {
$this->expectException();
$notAnObject = 'foobar';
$object = new Object($notAnObject);
}
But note that this will halt the test after the line where the exception occurs.
Turns out, SimpleTest doesn't actually support this. You can't catch Fatal PHP errors in SimpleTest. Type hinting is great, except you can't test it. Type hinting throws fatal PHP errors.
you have to expect the error before it happens, then SimpleTest will swallow it and count a pass, if the test gets to the end and there is no error then it will fail. (there's expectError and expectException that act in the same way, for PHP (non-fatal) errors and Exceptions, respectively.)