PHP 'instanceof' failing with class constant
I'm working on a framework that I'm trying to type as strongly as I possibly can. (I'm working within PHP and taking some of the ideas that I like from C# and trying to utilize them within this framework.) I'm creating a Collection class that is a collection of domain entities/objects. It's kinda modeled after the List<T>
object in .Net.
I've run into an obstacle that is preventing me from typing this class. If I have a UserCollection, it should only allow User objects into it. If I have a PostCollection, it should only allow Post objects.
All Collections in this framework need to have certain basic functions, such as add, remove, iterate. I created an interface, but found that I couldn't do the following:
interface ICollection { public function add($obj) }
class PostCollection implements ICollection { public function add(Post $obj) {} }
This broke it's compliance with the interface. But I can't have the interface strongly typed because then all Collections are of the same type. So I attempted the following:
interface ICollection { public function add($obj) }
abstract class Collection implements ICollection { const type = 'null'; }
class PostCollection extends Collection {
const type = 'Post';
public function add($obj) {
if(!($obj instanceof self::type)) {
throw new UhOhException();
}
}
}
When I attempt to run this code, I get syntax error, unexpected T_STRING, expecting T_VARIABLE or '$'
on the instanceof
statement. A little research into the issue and it looks like the root of the cause is that $obj instanceof self
is valid to test against the class. It appears that PHP doesn't process the entire self::type
constant statement in the expression. Adding parentheses around the self::type
variable threw an error regarding an unexpected '('.
An obvious workaround is to not make the type
variable a constant. The expression $obj instanceof $this->type
works just fine (if $type
is declared as a variable, of course).
I'm hoping that there's a way to avoid that, as I'd like to define the value as a constant to avoid any possible change in the variable later. Any thoughts on how I can achieve this, or have I take PHP to it's limit in this regard? Is there a way of "escaping" or encapsulating self::this
so that PHP won't die when processing it?
UPDATE Based on the feedback below, I thought of something to try -- the code below works! Can anyone think of 1) a reason not to do this, 2) a reason this won't ultimately work, or 3) a better way to pull this off?
interface ICollection { public function add($obj) }
abstract class Collection { const type = null; protected $type = self::type; }
class PostCollection extends Collec开发者_StackOverflow中文版tion {
const type = 'Post';
public function add($obj) {
if(!($obj instanceof $this->type)) {
throw new UhOhException();
}
}
}
UPDATE #2: After putting the code above into production, it turns out it doesn't work. I have no idea how it worked when I tested it, but it doesn't work at all. I'm stuck with using a protected
variable, I think.
Another workaround is to do:
$type = self::type;
if (!($obj instanceof $type))
Just 'cause it's a constant doesn't mean you can't put it in a variable for a moment to satisfy the parser.
I'm surprised by this behavior as well, but this should work:
$type = self::type;
if(!($obj instanceof $type)) {
throw new UhOhException();
}
EDIT:
You could do
abstract class Collection {
const type = null;
protected $type = self::type;
}
class PostCollection extends Collection {
const type = "User";
public function add($obj) {
if(!($obj instanceof $this->type)) {
throw new WhateverException();
}
}
}
But you're turning on the complicometer. This has the additional overhead of creating an instance $type
variable for each instance of PostCollection
(and no, you cannot just add static
to the $type
property).
I'm creating a Collection class that is a collection of domain entities/objects. It's kinda modeled after the
List<T>
object in .Net.
It's generally not a good idea to write one language in another language. You don't need Collections in PHP.
If you're going continue down that road, perhaps you should consider using tools supplied to you by PHP. For example, there's ArrayObject, which you can inherit from and override the required methods to ensure that only properly typed things enter the array. ArrayObjects can be used anywhere in PHP where a normal array can be used. Also, the underlying bits and pieces have already been written for you.
The rest of the Standard PHP Library may be of some interest to you, the SplObjectStorage class in particular.
it should readly be like this
public function add(ICollection $obj){
}
now if you try to add an object to the function add that is not an instance of Icollection(thats an example) then it will fail, before you even get to check using the instanceof.
This also works correctly, using a static:
<?php
interface ICollection {
public function add($obj);
}
abstract class Collection implements ICollection {
static protected $_type = 'null';
}
class PostCollection extends Collection {
static protected $_type = 'Post';
public function add($obj) {
if(!($obj instanceof self::$_type)) {
throw new UhOhException();
}
}
}
class Post {}
$coll = new PostCollection();
$coll->add(new Post());
And actually, you probably want to define your add()
method on the Collection
class anyway, which means you'll have to use get_class()
to get around some weirdness with self::type
or even self::$_type
always wanting to return the base Collection
class anyway, so this would probably work:
abstract class Collection implements ICollection {
const type = 'null';
public function add($obj) {
$c = get_class($this);
$type = $c::type;
if(!($obj instanceof $type)) {
throw new UhOhException();
}
}
}
class PostCollection extends Collection {
const type = 'Post';
}
class Post {}
$coll = new PostCollection();
$coll->add(new Post());
Try using PHP's is_a function instead of instanceof as that expects a string as the Class name.
精彩评论