PHP instanceof over strings and non-initializable classes - php

I need to check whether a certain class extends or implements a particular interface.
Note that the class name is a variable string, ie, there won't be any instance of this class.
Users are supposed to select a class from a list of classes and the system is supposed to check whether the class implements a certain interface or not. The list of classes is variable (according to the PHP software currently running), some of these classes can be initialized and others cannot.
Here's the code I'm using:
function is_instance_of($obj,$cls){
if(is_string($obj))$obj=new $obj();
if(PHP_MAJOR_VERSION>4)return $obj instanceof $cls;
if(PHP_MAJOR_VERSION>3)return is_a($obj,strtolower($cls));
return false;
}
var_dump(is_instance_of('MyClass','IMyInterface')); // in theory, true
var_dump(is_instance_of('Closure','IMyInterface')); // FATAL ERROR
That last test shows up the following error:
Catchable fatal error: Instantiation of 'Closure' is not allowed in C:\Users\abcdefghijklmn\debug.php on line XX
Things I tried:
Using $obj=new #$obj(); :- error is hidden but it still faults/dies.
Using try{}catch(){} around offending block :- nothing different happens
Using 'class' instanceof 'class' (where $obj is a string) :- returns false unconditionally
Please note that the mandatory class initialization used in this method...sucks. Creating instances means unnecessary memory consumption, loss in speed and more prone to errors (imagine some weirdo class that when instantiated without parameters it proceeds to destroy your HDD ;) ).
So, if there's any other way, I'd simply love to know about it.
Edit: This is (hopefully) the final code:-
/**
* Cross-version instanceof replacement.
* #param object $obj The instance to check.
* #param stirng $cls The class/interface name to look for.
* #return boolean Whether an object extends or implements a particular class
* or interface.
* #link http://stackoverflow.com/questions/4365567/php-instanceof-over-strings-and-non-initializable-classes
*/
function is_instance_of($obj,$cls){
if(is_string($obj) || PHP_MAJOR_VERSION>4){
$rc=new ReflectionClass($obj);
return $rc->implementsInterface($cls);
}else{
if(PHP_MAJOR_VERSION>3)return is_a($obj,strtolower($cls));
return false;
}
}

Try using PHP's ReflectionClass instead, e.g.
$rc = new ReflectionClass($obj);
return $rc->implementsInterface($cls);

Use the ReflectionClass:
function is_instance_of($obj,$cls){
$ref=new ReflectionClass($obj);
return in_array($cls, array_keys($ref->getInterfaces());
}

Related

What are these mysterious things in defining methods?

Back to development after spending some years in a management position, I am dealing with a PHP code, which has some definitions that I cannot understand (looks like I am far beyond of PHP progress on these years). Can someone let me know what campaignDTO and ParamDTO do in this definition?
What will be returned from this method?
/**
* Creates a campaign
* #param campaignDTO $campaign
* #param ParamDTO $param
* #throws \Exception
* #return campaignDTO
*/
public function createCampaign(campaignDTO $campaign, ParamDTO $param)
{
}
Type declarations as per docs:
Type declarations allow functions to require that parameters are of a
certain type at call time. If the given value is of the incorrect
type, then an error is generated: in PHP 5, this will be a recoverable
fatal error, while PHP 7 will throw a TypeError exception.
These are type-hints for run-time validation. It tells the code to expect objects of class type campaignDTO and ParamDTO, or a class that extends from these.
If you pass in an array, or a string, or something that is not a class that is or extends capaignDTO then the code will throw an error.
The function, as it is, returns nothing.
According to the code-comment, it will return an object of type campaignDTO, which looks like the first parameter.

Static property of PHP class to keep value until next usage?

I just stumbled over a PHP class and wonder if there was a valid reason for the way one of it's methods is written.
LogUtility::getLogger() is called as a static method in various other PHP classes of the PHP application. Does the used if statement make sense or is $logManager always null when getLogger() is called?
class LogUtility
{
/**
* #var LogManager
*/
protected static $logManager;
/**
* #return Logger
*/
public static function getLogger($name)
{
if (!self::$logManager) {
self::$logManager = GeneralUtility::makeInstance(LogManager::class);
}
return self::$logManager->getLogger($name);
}
}
You could quickly whip up a test, like below, and test / prove it yourself:
class someClass {
protected static $stored;
public static function test() {
echo '<br>Stored state:' . self::$stored;
if ( ! self::$stored) {
self::$stored = "set";
}
}
}
someClass::test();
someClass::test();
Output is:
Stored state:
Stored state:set
So, based on this simple test, the answer is Yes, the if statement makes sense. The static variable is set and maintained.
$logManager is set to a property of the class, so for now, its pretty much an empty class, just returning with a getter, the instance. This just sets the code to just reuse the object. So a bit of recycling code there.
In the class you are now able to play with this object freely. So if you run this LogUtility from another piece of code by setting it to a var, you have already instantiated it. e.g. $util = new LogUtility();now if someone comes along and tries to instantiate it again, $anotherUtil=new LogUtility(); this recycles the class, passing back the already instantiated instance, instead of instantiating a new one.
Therefore, yes, it kept it. Although, the var doesn't contain the "same value" per say, it contains a reference to the "same" class that was instantiated with he other variable, so it ends up a copy of the same instance there.
It will be null for only the first call in a lifecycle. This implements a design pattern called Singleton.
Check out https://sourcemaking.com/design_patterns/singleton/php/1

How to tell phpDoc a string is a class name?

I often give objects static methods and properties that do not require the object to be initialized. For example:
class SomeObject {
public function __construct($object_id) {
$this->loadProperties($object_id);
}
public static function getSomeStaticString() {
return "some static string";
}
}
Now we subclass these objects and have some sort of controller that returns an object class string under certain circumstances where the object should not yet be initialized. For example:
class SomeObjectController {
public function getSomeObjectWithTheseProperties(array $properties) {
if($properties[0] === "somevalue") {
if($properties[1] === "someothervalue") {
return SomeSubclassObject::class;
}
return SomeObject::class;
}
return NULL;
}
}
At times I might want to call the static function SomeObject::getSomeStaticString() without actually initializing the object (because that would involve an unneeded database fetch). For instance:
$controller = new SomeObjectController;
$properties = array("somevalue", "someothervalue");
$object_class = $controller->getSomeObjectWithTheseProperties($properties);
echo $object_class::getSomeStaticString();
Question: can I somehow tell PhpStorm, preferably through phpDoc, that $object_class is a class string of a subclass of SomeObject?
If I tell my IDE it's a string, it will notify me getSomeStaticString() is an invalid method. On the other hand, if I tell my IDE it's an instance of SomeObject, it thinks I can access regular non-static methods and properties, which I can't.
PHPStan uses the class-string PHPDoc type for this. It has been supported by PHPStorm since 2020.3, but it seems to work properly (with all autocompletion available) as of 2021.2, when they added support for Generics.
In your case the return type annotation would be #return class-string<SomeObject>.
/** #var SomeObject $object_class */
$object_class = $controller->getSomeObjectWithTheseProperties($properties);
Sorry, no other way as to tell that it's an instance of SomeObject.
... if I tell my IDE it's an instance of SomeObject, it thinks I can access regular non-static methods and properties, which I can't.
So? Just do not access non-static methods and properties.
UPDATE 2021-10-26: Since v2020.3 PhpStorm has Psalm Support and PHPStan Support plugins bundled. They support most of the syntax/types supported by those tools (you can check PhpStorm Issue Tracker here for all the tickets for those plugins (resolved and still pending)).
With those tools you can use class-string pseudo type:
https://psalm.dev/docs/annotating_code/type_syntax/scalar_types/#class-string-interface-string
https://phpstan.org/writing-php-code/phpdoc-types#class-string
If you want perfect type hinting you may create an Interface which only lists the static methods (and properties) of your class, and use that Interface as the type hint for your classname string returned from SomeObjectController::getSomeObjectWithTheseProperties().
It adds overhead to the maintenance to ensure the Interface and the classes are kept in sync, but if you need those type hints to work properly, this is the way.
You can also use this workaround:
/**
* #return string|SomeObject
*/
public function getSomeObjectClass()
{
return SomeObject::class;
}

Inherited Setter Method Does Not Set Overridden Instance Variable

I seem to be having some trouble testing that a private instance variable was set. My idea was to stub out the class and make the instance variable public so I could test the setter method. This seems simple enough, but I can't seem to get it to work correctly. Or maybe there's some better way to test setting a private variable.
Class
<?php
namespace PureChat\src\messages;
/**
* Message class for containing information about a message.
*/
class Message
{
/**
* Contains the message text.
* #var string
*/
private $messageText;
/**
* Sets the instance variable $messageText.
* #param string $text The text to assign.
*/
public function setText($text)
{
$this->messageText = $text;
}
}
PHPUnit Test
<?php
use PureChat\src\messages\Message;
class MessageTest extends \PHPUnit_Framework_TestCase
{
public function testMessageCanSetText()
{
$message = new MessageStub;
$message->setText('test-text');
$this->assertEquals(
$message->messageText, 'test-text');
}
}
class MessageStub extends Message
{
public $messageText;
}
When run, the test fails with "Failed asserting that 'test-text' matches expected null." My first thought was that maybe the stub class wasn't inheriting the setText method, so I tested that as well with method_exists, however the method does exist, so I'm at a loss.
Private properties are not inherited, so $messageText already is not inside the MessageStub class. And it cannot be set by the inherited method setText.
And then you are using assertEquals() arguments in the wrong order: The first one should be the value you expect, and the second one is the value you test. Flip the arguments - then the error message makes more sense, because currently the message says you expect NULL - but you expect the string 'test-text'.
And then we come to the topic of testing philosophy. A good unit test only checks the outside of an object, but should not care about the inner workings. If you set a value, the success of setting it should somehow be detectable from the outside. When you only have the very basic setter/getter combo, it is indeed very boring to test them, but that's what you should do: set a valid value, get it back, assert it is the same as before, and assert that no errors occurred (this is done automatically by PHPUnit because any PHP error or exception would make the test fail).
If there is no getter for that value - either there is no use in setting the value anyways because it is never used, or you can test it by testing the part that uses the value.
$message->setText('foo');
$message->saveText(); // uses value from setText()
If you want to test this, you'd probably call these two methods in one test. And you need to test that calling saveText alone will trigger an error. Or saves the default value.
Or you come to the conclusion that having TWO methods to do one thing is not a very good idea because testing them is not that easy, so your experience from testing may make you think about improving the API of that message object. You probably want to get rid of that setText method somehow.
Yes, using Reflection to get access to the private property is a way to test it. But now you are tightly binding your test to the internal construction of the object. If you rename the private property, your test breaks - but it shouldn't break because you haven't change the public interface.
You could use reflection.
$message = new Message;
$message->setText('test-text');
$property = (new \ReflectionObject($message))->getProperty('messageText');
$property->setAccessible(true);
$value = $property->getValue($message);
$property->setAccessible(false); // restore state
echo $value;

Troubleshooting "The (subclass) instance wasn't initialized properly" in custom RecursiveIteratorIterator

I have created a custom iterator that extends RecursiveIteratorIterator which I use to iterate over a Doctrine_Collection from a table that uses the NestedSet behavior (e.g., so that I apply custom sorting to records at each level in the hierarchy).
There are a couple of models in my project that leverage this iterator, so I have created a base class that looks like the following:
/** Base functionality for iterators designed to iterate over nested set
* structures.
*/
abstract class BaseHierarchyIterator
extends RecursiveIteratorIterator
{
/** Returns the component name that the iterator is designed to work with.
*
* #return string
*/
abstract public function getComponentName( );
/** Inits the class instance.
*
* #param $objects Doctrine_Collection Assumed to already be sorted by `lft`.
*
* #throws LogicException If $objects is a collection from the wrong table.
*/
public function __construct( Doctrine_Collection $objects )
{
/** #kludge Initialization will fail horribly if we invoke a subclass method
* before we have initialized the inner iterator.
*/
parent::__construct(new RecursiveArrayIterator(array()));
/* Make sure we have the correct collection type. */
$component = $this->getComponentName();
if( $objects->getTable()->getComponentName() != $component )
{
throw new LogicException(sprintf(
'%s can only iterate over %s collections.'
, get_class($this)
, $component
));
}
/* Build the array for the inner iterator. */
$top = array();
/** #var $object Doctrine_Record|Doctrine_Node_NestedSet */
foreach( $objects as $object )
{
// ... magic happens here ...
}
parent::__construct(
new RecursiveArrayIterator($top)
, RecursiveIteratorIterator::SELF_FIRST
);
}
...
}
A subclass might look something like this:
/** Iterates hierarchically through a collection of User objects.
*/
class UserHierarchyIterator
extends BaseHierarchyIterator
{
/** Returns the component name that the iterator is designed to work with.
*
* #return string
*/
public function getComponentName()
{
return UserTable::getInstance()->getComponentName();
}
...
}
Note the #kludge at the top of the constructor in the base class:
/** #kludge Initialization will fail horribly if we invoke a subclass method
* before we have initialized the inner iterator.
*/
parent::__construct(new RecursiveArrayIterator(array()));
As long as I keep that extra initialization line at the top of the base class' constructor, everything works as expected.
However, if I remove/comment that line, I get the following error as soon as script execution gets to $component = $this->getComponentName():
Fatal error: BaseHierarchyIterator::__construct(): The UserHierarchyIterator instance wasn't initialized properly in /path/to/BaseHierarchyIterator.class.php on line 21.
Alternatively, if I remove the code that calls $this->getComponentName() (and the subsequent conditional block), the constructor still operates as expected (minus the check to make sure the component name is correct).
What is the root cause of this error? Is there a better workaround for this issue?
PHP version info:
PHP 5.3.3 (cli) (built: Jul 3 2012 16:40:30)
Copyright (c) 1997-2010 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies
with Suhosin v0.9.29, Copyright (c) 2007, by SektionEins GmbH
Fatal error: BaseHierarchyIterator::__construct(): The UserHierarchyIterator instance wasn't initialized properly in /path/to/BaseHierarchyIterator.class.php on line 21.
What is the root cause of this error?
Your class extends from RecursiveIteratorIterator. To have an object of this type or a subtype thereof working, PHP needs to have it initialized properly before you call any other method or access a property of it. Properly means here that it's parent constructor has been called, the initialization is done then.
But you call a method before initialization (RecursiveIteratorIterator::__construct() has not been called yet). The method call in your case is:
$this->getComponentName();
You also have realized that so far. If you initialize before that call, you don't have this fatal error. In your case the initialization is:
parent::__construct(new RecursiveArrayIterator(array()));
The strict check by PHP reserves PHP to do some special kind of recursive iterators that can delegate better to underlying data-structures. Recursive traversal is just a bit more complex as well, needs initialization of a stack and such which is all hidden by PHP. So that check is also done for safety reasons.
If you compare that with IteratorIterator, you see that such a fatal error does not exists.
Is there a better workaround for this issue?
I won't call it a workround. You actually implemented something similar comparable for what IteratorAggregate is for. But you just did too much at once, see your constructor, it is doing too much. This is a Flaw: Constructor does Real Work.
So this calls for a cleaner separation of concerns and actually a more basic understanding of iterators could be helpful, too.
The solution is rather straight forward: The only thing you need to do here is to move the data-handling logic from the constructor into an implementation of IteratorAggregate:
abstract class BaseHierarchyIterator implements IteratorAggregate
{
private $objects;
abstract public function getComponentName();
public function __construct(Doctrine_Collection $objects) {
$this->setObjects($objects);
}
public function getIterator() {
/* Build the array for the inner iterator. */
$top = array();
/** #var $object Doctrine_Record|Doctrine_Node_NestedSet */
foreach ($this->objects as $object) {
// ... magic happens here ...
}
return new RecursiveArrayIterator($top);
}
private function setObjects($objects) {
/* Make sure we have the correct collection type. */
$component = $this->getComponentName();
if ($objects->getTable()->getComponentName() != $component) {
throw new LogicException(sprintf(
'%s can only iterate over %s collections.'
, get_class($this)
, $component
));
}
$this->objects = $objects;
}
}
You then can just do recursive Iteration straight away:
$users = new UserHierarchyIterator($objects);
$it = new RecursiveIteratorIterator(
$users, RecursiveIteratorIterator::SELF_FIRST
);
As you can see, the only thing you need to solve your concrete problem is to separate the concerns a bit more. This will help you anyway, so you should be fine with it in the first run:
[Iterator] ---- performs traversal ---> [Container]
The container has an interface the iterator can work on now. This by the way is exactly what an iterator object is. You actually used it wrong. If you follow that trail and as it's common with IteratorAggregate, you can go from there to a true own iterator interface for your containers. Right now you iterate over the doctrine collection before you create the iterator object.
http://www.php.net/manual/en/oop4.constructor.php
PHP doesn't call constructors of the base class automatically from a constructor of a derived class. It is your responsibility to propagate the call to constructors upstream where appropriate.
which is to say, you have to explicitly call the constructor when extending classes, otherwise PHP doesn't know how to initialize the base class.
PHP could try to call the base class constructor with NULL params, but that would almost certainly be wrong and would lead to unexpected results (in this case - you'd probably get a bunch of 'param X expected an array, NULL given' errors if PHP decided to try to pass NULL to the base constructor instead of throwing an error)

Categories