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, I thought of something to try -- the code below works!
Can anyone think of
- a reason not to do this,
- a reason this won't ultimately work, or
- 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 Collection {
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.
protected
, it can still be changed in the code. There's no real security in doing it this way that the variable$type
won't get changed accidentally. What I was hoping for was insurance against that. – Clearwing