6

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

  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 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.

5
  • I think I answered 1 or 2 -- even though the variable is 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. Commented Jun 9, 2010 at 1:56
  • If I understand correctly you are giving the instanceof operation a string ($this->type) when it should just be a class name (see php.net/manual/en/language.operators.type.php). I think you should instead use 'is_a' as that expects a string which is what $this->type is. Commented Jun 9, 2010 at 2:21
  • Not a true statement -- look at Example #5 on the page you linked to. Commented Jun 9, 2010 at 2:36
  • 3
    By the way, 2-space indentation is heresy. Commented Jun 9, 2010 at 3:26
  • My bad on the indents -- I don't actually code like that, I was just typing quickly for an example. Commented Jun 9, 2010 at 21:33

6 Answers 6

4

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).

Sign up to request clarification or add additional context in comments.

2 Comments

@Nathan Loding Your new code gave me PHP Notice: Undefined property: PostCollection::$type.
I made a typo! The class definition should be class PostCollection extends Collection -- I'll update it, good catch.
3

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.

5 Comments

or whats wrong with parenthesis? !($obj instanceof (self::type))
@seanmonstar - that exact expression threw an error for me in PHP. Did it work for you? I got an error regarding 'unexpected "("'.
@Nathan no i didnt actually open a shell and test it, sorry. I just assumed it was a matter of operator precedence, and it was parsing it the wrong way like ($obj instanceof self)::type
@Nathan although, looking at, could you not use get_class($obj) == self::type?
@seanmonstar - I'm ultimately going to end up with is_a($obj, self::type) statement, I think. I'm trying to decide right now if that's ultimately the route I want to go ...
2

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.

7 Comments

Except I said it's "kinda modeled after." I have a need for a container for multiple objects of the same type, and being able to easily add, remove, find, and iterate through them -- and it's in a PHP app. If this was a .Net app, I'd use a List<T> object. In PHP, I have the pleasure of creating my own variation on that.
You might want to read my post beyond the first paragraph, as I go on to explain how PHP may already give you the tools you need to create this behavior without implementing a construct from a very different language. :)
The SplObjectStorage and the ArrayObject options aren't quite what I want. I don't see any reason to not attempt to create a construct from one language to another unless there are technical reasons that they cannot be accomplished. In this case, creating a collection class that mimics the List object in .Net is very possible within PHP, it's the strongly typed part that's not. The joy of PHP over .Net is that you can build these objects yourself. I'm not writing one language in another -- I'm exploring possibilities in one language based off experience with another language.
"I don't see any reason to not attempt to create a construct from one language to another unless..." Every language has a culture attached to it. That culture frequently establishes a way of doing things, a standard operating procedure, a common set of idioms. Let's pick on Perl for a moment. It's perfectly possible to write PHP in Perl. You can create every one of the builtins and make your Perl programs function exactly like they were written in PHP. Show that code to a Perl programmer, though, and they'll look at you as if you've grown a second head. Being able to does not imply should.
That being said, +1 for exploring what a language can do. Even "bad ideas" can be good ideas if you learned something as a result.
|
1

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.

1 Comment

That's a good thought -- in this case, it will be an Entity object, but a good foundation.
0

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());

Comments

-1

Try using PHP's is_a function instead of instanceof as that expects a string as the Class name.

1 Comment

This is not a true statement. Look at Example #5 ("Using instanceof with other variables") on php.net/manual/en/language.operators.type.php.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.