PHP 'instanceof' failing with class constant
Asked Answered
C

6

5

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.

Clearwing answered 9/6, 2010 at 1:38 Comment(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.Clearwing
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.Arbitress
Not a true statement -- look at Example #5 on the page you linked to.Clearwing
By the way, 2-space indentation is heresy.Boorer
My bad on the indents -- I don't actually code like that, I was just typing quickly for an example.Clearwing
O
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());
Octarchy answered 9/6, 2010 at 9:40 Comment(0)
B
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).

Boorer answered 9/6, 2010 at 1:41 Comment(2)
@Nathan Loding Your new code gave me PHP Notice: Undefined property: PostCollection::$type.Boorer
I made a typo! The class definition should be class PostCollection extends Collection -- I'll update it, good catch.Clearwing
S
4

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.

Spread answered 9/6, 2010 at 1:41 Comment(5)
or whats wrong with parenthesis? !($obj instanceof (self::type))Penetrating
@Penetrating - that exact expression threw an error for me in PHP. Did it work for you? I got an error regarding 'unexpected "("'.Clearwing
@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)::typePenetrating
@Nathan although, looking at, could you not use get_class($obj) == self::type?Penetrating
@Penetrating - 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 ...Clearwing
K
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.

Kenyettakenyon answered 9/6, 2010 at 3:2 Comment(7)
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.Clearwing
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. :)Kenyettakenyon
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.Clearwing
"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.Kenyettakenyon
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.Kenyettakenyon
The only reason I'm still programming in PHP is it's immense flexibility. It is also it's ultimate downside. But flexibility doesn't necessitate a lack of structure in your code. I've often found that PHP programmers (hope I don't get burned alive for this) lack the same structure and planning that a .Net programmer does. Some PHP apps are coded impeccably, beyond anything in .Net, don't get me wrong, and some .Net apps are terrible. But in general I feel that PHP's flexibility lends itself to poor coding standards. That's what I'm trying to steer clear of.Clearwing
Because C# (and Java) programmers are restricted their toolset, they've developed a different style of code creation compared to PHP. PHP programmers are also restricted by the toolset, but in entirely different ways. Usually the opposite way. Quick and dirty does the job 75% of the time. Try to do MVC in PHP and you'll find nothing to help you without new or third party code that inevitably flies in the face of how PHP normally works. It's a blessing and a curse. Also, if you want real flexibility, I urge you to work with Perl as well, it'll let you do things PHP won't. Moose is excellent.Kenyettakenyon
C
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.

Crosspatch answered 9/6, 2010 at 9:31 Comment(1)
That's a good thought -- in this case, it will be an Entity object, but a good foundation.Clearwing
O
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());
Octarchy answered 9/6, 2010 at 9:40 Comment(0)
A
-1

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

Arbitress answered 9/6, 2010 at 1:45 Comment(1)
This is not a true statement. Look at Example #5 ("Using instanceof with other variables") on php.net/manual/en/language.operators.type.php.Clearwing

© 2022 - 2024 — McMap. All rights reserved.