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);
Related
I am implementing a plugin controller CLASS for client created custom plugins.
Since the names of the classes of each plugin could very well end up clashing with other plugins already installed, I want to make sure we don't get fatal errors when a new plugin comes in.
I want to report to the user that the new plugin is clashing with an already installed one.
So basically atm I testing with 2 files both containing exactly the same code and am getting:
Fatal error: Cannot declare class [myclassname], because the name is already in use
I have tried catching this with no success using:
try {
include_once $file;
} catch (ClosedGeneratorException|DOMException|ErrorException|IntlException|LogicException|BadFunctionCallException|BadMethodCallException|DomainException|InvalidArgumentException|LengthException|OutOfRangeException|PharException|ReflectionException|RuntimeException|OutOfBoundsException|OverflowException|PDOException|RangeException|UnderflowException|UnexpectedValueException|SodiumException $ex) {
echo "Unable to load file.";
}
All these I got from Lists of Throwable and Exception tree as of 7.2.0 in https://www.php.net/manual/en/class.exception.php
The manual specifies that:
The thrown object must be an instance of the Exception class or a
subclass of Exception. Trying to throw an object that is not will
result in a PHP Fatal Error.
Is it possible that this is not an object of instance/suclass of the Exception class?
What am I missing?
Short answer: no, you're not missing anything. Declaring a duplicate class name can't be caught in any version of PHP (including 8.0.0 as far as the alpha releases). See https://3v4l.org/2TLA3
For some additional background, PHP does sometimes move errors like these underneath the Throwable hierarchy, so that they can be detected at runtime. PHP 7 added support for catching an attempt to instantiate a missing class, and 7.3 added support for catching an attempt to extend a missing class. See https://3v4l.org/BDnm9 for a brief demo of those.
In the end, to avoid this fatal error I decide to parse the files for the first available class declaration using tokens as follows:
public function getClassInFile($filenpath) {
$src = $this->uncommentFile($filenpath);
$class ='';
if (preg_match('/class[\s\n]+([a-zA-Z0-9_]+)[\s\na-zA-Z0-9_]+\{/', $src, $matches)) {
$class = $matches[1];
}
return $class;
}
public function uncommentFile($filenpath) {
$fileStr = file_get_contents($filenpath);
$newStr = '';
$commentTokens = array(T_COMMENT);
if (defined('T_DOC_COMMENT'))
$commentTokens[] = T_DOC_COMMENT; // PHP 5
if (defined('T_ML_COMMENT'))
$commentTokens[] = T_ML_COMMENT; // PHP 4
$tokens = token_get_all($fileStr);
foreach ($tokens as $token) {
if (is_array($token)) {
if (in_array($token[0], $commentTokens))
continue;
$token = $token[1];
}
$newStr .= $token;
}
return $newStr;
}
In this way, I can check whether the class I am expecting to find is in there, or if the class in there already exists, thus avoiding to include_once altogether in these cases.
Problem solved.
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
can anyone tell me what could cause this error "Fatal error: Class PDO not found"
when I call the singleton class like this: $db = db::krijgInstantie();
I use an mvc design and this error is weird, because I use the same code for another site
public static function krijgInstantie()
{
if (!self::$instantie)
{
$config = config::krijgInstantie();
$db_type = $config->config_waarden['database']['db_type'];
$hostnaam = $config->config_waarden['database']['db_hostnaam'];
$dbnaam = $config->config_waarden['database']['db_naam'];
$db_wachtwoord = $config->config_waarden['database']['db_wachtwoord'];
$db_gebruikersnaam = $config->config_waarden['database']['db_gebruikersnaam'];
$db_poort = $config->config_waarden['database']['db_poort'];
self::$instantie = new PDO("$db_type:host=$hostnaam;port=$db_poort;dbname=$dbnaam",$db_gebruikersnaam, $db_wachtwoord);
self::$instantie-> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
return self::$instantie;
}
thanks, Richard
Your PHP installation is missing the PDO module. Check your PHP.ini.
See also: http://php.net/manual/en/pdo.installation.php
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.)