FAQ
It seems to me, there's a couple of things related to traits that were
missed in this implementation.

Take the following example:

<?php

header('Content-type: text/plain');

class Cart
{
public static $instance;

# public function addItem(CartBehavior $item, $amount=1) // => script
terminates
public function addItem($item, $amount=1)
{
echo "Adding {$amount} {$item->getName()} to shopping cart - price =
".($amount * $item->getPrice())."\n\n";
}
}

Cart::$instance = new Cart;

trait CartBehavior
{
public function addToCart($amount=1)
{
Cart::$instance->addItem($this, $amount);
}
}

class Product
{
use CartBehavior;

private $_name;
private $_price;

public function __construct($name, $price)
{
$this->_name = $name;
$this->_price = $price;
}

public function getPrice()
{
return $this->_price;
}

public function getName()
{
return $this->_name;
}
}

$test = new Product('Fish', 100);
$test->addToCart();

var_dump($test instanceof CartBehavior); // => false

var_dump((new ReflectionClass('Product'))->getTraits()); // CartBehavior is
a ReflectionClass?


The first problem is that type-hinting isn't supported... so you have this
horizontal extensibility now - traits are kind of like interfaces with a
built-in implementation. They're more like interfaces than classes, at
least, in the sense that they can't have state, and you can't create an
instance of a trait.

So the first issue is, you can't use a trait as a type-hint - the addItem()
method can't be type-hinted, so we can't define explicitly what to expect
here. This is one of the most important reasons for having interfaces,
perhaps the second-most important after the ability to enforce conformity
on class implementations. Traits don't work in this case. And sure, you
could add an interface too, and redeclare all your accessors again. But
why? An abstract base-class with some abstract methods seems like a much
stronger tool, except that you can't reuse that horizontally...

Secondly, the instanceof operator doesn't complain when you test to see if
an object carries a trait - if this isn't going to work, it should at least
throw an exception. Although as pointed out, it seems natural to expect
that this would work.

Lastly, when you reflect on a trait, it comes back as an instance of
ReflectionClass. As pointed out, traits are probably more similar to
interfaces than they are to classes, and they definitely don't have
properties, as are exposed by the ReflectionClass type.

I would also point out that the examples in the documentation (which it
seems were just copied from the RFC?) do not demonstrate any real purpose
of this feature. Is this the most anybody has attempted to do with this
feature? Trying to come up with a real example, I didn't get very far
before running into these stumbling blocks.

If this feature is useful in it's current form, I don't see how - and the
examples in the documentation definitely do not demonstrate any real
practical use for this.

Here's another quick example demonstrating these problems:

interface MyInterface
{
public function myMethod();
}

trait MyTrait # implements MyInterface // fails (B)
{
public function myMethod()
{
echo "foo";
}
}

class MyClass implements MyInterface
{
use MyTrait;
}

$test = new MyClass;

var_dump($test instanceof MyInterface); // true
var_dump($test instanceof MyTrait); // false (A)


If I can't use instanceof to check for a trait (A), then I would at least
expect to be able to write a trait that implements an interface (B) - does
that not seem reasonable or logical?

Having to use both the interface and the trait as a pair, and having to
explicitly apply them both to the class, feels like a work-around.

I apologize if I'm somehow missing the big picture here, or maybe I set my
expectations too high - but my first impression of this feature is that
it's crippled and somewhat half-baked... If there was a deliberate and
logical reason for not supporting these features, I would like to
understand why. If not - great work so far, but perhaps this feature is not
quite mature enough for release just yet?

- Rasmus

Search Discussions

  • Johannes Schlüter at Nov 11, 2011 at 4:41 pm
    I keep most of this to other but two comments:
    On Fri, 2011-11-11 at 11:00 -0500, Rasmus Schultz wrote:
    Lastly, when you reflect on a trait, it comes back as an instance of
    ReflectionClass. As pointed out, traits are probably more similar to
    interfaces than they are to classes, and they definitely don't have
    properties, as are exposed by the ReflectionClass type.
    1) Interface use ReflectionClass, too.
    2) Traits may (for whatever reason) have properties
    $ php -r 'trait T { public $p; } ReflectionClass::export("T");'
    Trait [ <user> trait T ] {
    [...]
    - Properties [1] {
    Property [ <default> public $p ]
    }

    In general: Reflection is always quite close to the internal structures
    not far abstracted away. Always closer to the C implementation than PHP
    land. Changing that would affect multiple areas in there. This might
    have benefits here and there but now it resembles the actual workings
    better which has also some benefits ...
    I would also point out that the examples in the documentation (which
    it
    seems were just copied from the RFC?) do not demonstrate any real
    purpose
    of this feature. Is this the most anybody has attempted to do with
    this
    feature? Trying to come up with a real example, I didn't get very far
    before running into these stumbling blocks.
    I once stumbled over
    http://knplabs.com/blog/csi/php-materialized-path-tree

    johannes
  • Stefan Marr at Nov 11, 2011 at 5:02 pm
    Hi Rasmus:

    First, sorry, I don't have currently the time to reiterate all discussions on these questions.
    Please, do me the favor and search the archives for previous discussions.
    I believe _all_ points you raise here have been discussed and commented before, and most of them quite recently.

    Since I believe the answers are already given,
    let me try another approach to tackle your problems.
    From my perspective the purpose of traits is often misunderstood.
    So, let me ask a few questions. They might sound basic,
    but I want to better understand your design rational,
    and the reasons behind your implementation approach.

    If you got the time, please try to answer all of them.
    They might seem suggestive, or even worse, 'rhetorical',
    but that is not on purpose.

    Answering them will help us all better to understand
    what the underlying issues/conceptual problems are we need to tackle,
    either by improving the current implementation and/or documentation.
    On 11 Nov 2011, at 17:00, Rasmus Schultz wrote:

    class Cart
    {
    public static $instance;

    # public function addItem(CartBehavior $item, $amount=1) // => script
    terminates
    public function addItem($item, $amount=1)
    {
    echo "Adding {$amount} {$item->getName()} to shopping cart - price =
    ".($amount * $item->getPrice())."\n\n";
    }
    }
    What is the real purpose of adding the type hint for CartBehavior here?
    My understanding would be, you want to express/document that the given object is required to support a specific set of operations.

    Is there any other semantics that need to be expressed by this annotation?

    On a practical level, you probably want to make sure that the engine never barks at you because it got an object that does not understand a certain operation.

    Is it relevant for the method using that object that it provides a specific implementation for the required set of operations?
    [Side note for the archive: traits do not guarantee that]

    var_dump($test instanceof CartBehavior); // => false
    Beside testing the corner cases of the language definition,
    is there any practical need for this test?
    Do you have an example where it is desirable to know exactly
    that a particular implementation is provided by an object?


    var_dump((new ReflectionClass('Product'))->getTraits()); // CartBehavior is
    a ReflectionClass?
    Well, the reflection API is not perfect yet. Other interesting features,
    like knowing how conflicts where resolved are missing, too. (I think)
    The reason for that is unfortunately missing time.

    Secondly, the instanceof operator doesn't complain when you test to see if
    an object carries a trait - if this isn't going to work, it should at least
    throw an exception.
    You refer to the previous example, I suppose:
    var_dump($test instanceof CartBehavior); // => false
    Why would it be beneficial to throw an exception here?
    $test is not an instance of CartBehavior, since traits cannot be
    instantiated, and do not provide any guarantees in what ever respect.
    So, even if a class uses CartBehavior, you would never be sure that
    it actually also uses all of the implementations provided by it.


    Lastly, when you reflect on a trait, it comes back as an instance of
    ReflectionClass. As pointed out, traits are probably more similar to
    interfaces than they are to classes, and they definitely don't have
    properties, as are exposed by the ReflectionClass type.
    [Well, it can be useful to test for the properties expected by a trait.]
    Yes, that is an implementation artifact of how the reflection API is designed.

    I would also point out that the examples in the documentation (which it
    seems were just copied from the RFC?) do not demonstrate any real purpose
    of this feature. Is this the most anybody has attempted to do with this
    feature? Trying to come up with a real example, I didn't get very far
    before running into these stumbling blocks.
    I frequently search for usages of traits on the web, and by now, there are quite a number
    of articles giving examples and discussing the pros and cons of traits.
    So, no, you are not the first one.

    But, yes, the current documentation page could use some better examples.

    If I can't use instanceof to check for a trait (A), then I would at least
    expect to be able to write a trait that implements an interface (B) - does
    that not seem reasonable or logical?
    The discussion we had on that topic evolved to the following proposal:
    https://wiki.php.net/rfc/horizontalreuse#requiring_composing_class_to_implement_interface

    However, to my understanding, there was no consent on actually adding this.

    Having to use both the interface and the trait as a pair, and having to
    explicitly apply them both to the class, feels like a work-around.
    The discussions we had here suggest otherwise: this is exactly the way to go from a conceptual perspective.
    It keeps the two concepts apart, avoids confusion, and thus, is the conceptual purer alternative.
    However, by answering my questions from above, you can certainly make a point that the practical overhead does not justify this conceptual burden.
    I apologize if I'm somehow missing the big picture here, or maybe I set my
    expectations too high - but my first impression of this feature is that
    it's crippled and somewhat half-baked... If there was a deliberate and
    logical reason for not supporting these features, I would like to
    understand why. If not - great work so far, but perhaps this feature is not
    quite mature enough for release just yet?
    On a personal note: never tell a mother that her child is ugly.
    Please stay constructive. Thanks!

    Best regards
    Stefan


    --
    Stefan Marr
    Software Languages Lab
    Vrije Universiteit Brussel
    Pleinlaan 2 / B-1050 Brussels / Belgium
    http://soft.vub.ac.be/~smarr
    Phone: +32 2 629 2974
    Fax: +32 2 629 3525
  • Rasmus Schultz at Nov 16, 2011 at 12:17 am
    Hi Stefan,

    Appreciate you taking the time to discuss this - and I apologize if I
    jumped the gun with some of these comments.

    I knew about the traits features in Scala, and I guess I assumed this would
    be similar - not so.

    Reading through my own remarks and your comments, I now have a better
    understanding of what traits are, and how they're intended to be used.

    I believe the key to understanding traits, is understanding that traits are
    in fact an implementation detail - an artifact that does not really change
    or affect the nature of OOP in PHP as such, and by design, should not. I
    understand that now - thank you :-)

    From my perspective, a key difference from classes and interfaces, is that
    traits have no meaningful use at run-time - no type-hinting and with no
    real reflection-features that reveal the details. They cannot implement
    interfaces for classes, but classes can use them to implement interfaces.
    Traits just provide a set of method implementations that can be aggregated
    by classes. So in a sense, they're a design-time tool for the programmer.

    So I guess my remaining quibble is that the documentation needs to relay
    this somehow - with a basic real-world example that actually uses an
    interface too, to clarify the difference, and to demonstrate how this tool
    use useful in conjunction with class and interface declarations. Hello
    world really doesn't explain anything, other than the syntax.

    Here's a better example of something useful that actually works:

    trait Accessors
    {
    public function __get($name)
    {
    return $this->{'get'.$name}();
    }

    public function __set($name, $value)
    {
    $this->{'set'.$name}($value);
    }
    }

    class OrderLine
    {
    use Accessors;

    public $price;
    public $amount;

    public function getTotal()
    {
    return $this->price * $this->amount;
    }
    }

    $line = new OrderLine;

    $line->price = 20;
    $line->amount = 3;

    echo "Total cost: ".$line->total;

    This may be a little too magical for an example in the documentation though
    - and doesn't demonstrate interfaces.

    I'll ponder this and post a better example if I can think of one... :-)

    And just one other comment:
    var_dump($test instanceof CartBehavior); // => false
    Why would it be beneficial to throw an exception here?
    CartBehavior is a trait, and not a class or interface. The instanceof
    operator has no meaning for traits - it always returns false.

    You could argue that false is the expected result - I would argue that the
    only reason this happens to work at all, is because traits internally are
    classes.

    Suppose you thought you were actually doing a meaningful type-check of some
    sort? If by mistake you put a trait-name where you meant to put the name of
    an interface or class, such an error could be very hard to spot.

    Since there is no such thing as an "instance of" a trait, the instanceof
    operator should not work for traits.

    Once again, thank you for taking the time to talk about this, Stefan - I
    hope this feedback is useful :-)

    - Rasmus
    On Fri, Nov 11, 2011 at 12:02 PM, Stefan Marr wrote:

    Hi Rasmus:

    First, sorry, I don't have currently the time to reiterate all discussions
    on these questions.
    Please, do me the favor and search the archives for previous discussions.
    I believe _all_ points you raise here have been discussed and commented
    before, and most of them quite recently.

    Since I believe the answers are already given,
    let me try another approach to tackle your problems.
    From my perspective the purpose of traits is often misunderstood.
    So, let me ask a few questions. They might sound basic,
    but I want to better understand your design rational,
    and the reasons behind your implementation approach.

    If you got the time, please try to answer all of them.
    They might seem suggestive, or even worse, 'rhetorical',
    but that is not on purpose.

    Answering them will help us all better to understand
    what the underlying issues/conceptual problems are we need to tackle,
    either by improving the current implementation and/or documentation.
    On 11 Nov 2011, at 17:00, Rasmus Schultz wrote:

    class Cart
    {
    public static $instance;

    # public function addItem(CartBehavior $item, $amount=1) // => script
    terminates
    public function addItem($item, $amount=1)
    {
    echo "Adding {$amount} {$item->getName()} to shopping cart - price =
    ".($amount * $item->getPrice())."\n\n";
    }
    }
    What is the real purpose of adding the type hint for CartBehavior here?
    My understanding would be, you want to express/document that the given
    object is required to support a specific set of operations.

    Is there any other semantics that need to be expressed by this annotation?

    On a practical level, you probably want to make sure that the engine never
    barks at you because it got an object that does not understand a certain
    operation.

    Is it relevant for the method using that object that it provides a
    specific implementation for the required set of operations?
    [Side note for the archive: traits do not guarantee that]

    var_dump($test instanceof CartBehavior); // => false
    Beside testing the corner cases of the language definition,
    is there any practical need for this test?
    Do you have an example where it is desirable to know exactly
    that a particular implementation is provided by an object?


    var_dump((new ReflectionClass('Product'))->getTraits()); // CartBehavior is
    a ReflectionClass?
    Well, the reflection API is not perfect yet. Other interesting features,
    like knowing how conflicts where resolved are missing, too. (I think)
    The reason for that is unfortunately missing time.

    Secondly, the instanceof operator doesn't complain when you test to see if
    an object carries a trait - if this isn't going to work, it should at least
    throw an exception.
    You refer to the previous example, I suppose:
    var_dump($test instanceof CartBehavior); // => false
    Why would it be beneficial to throw an exception here?
    $test is not an instance of CartBehavior, since traits cannot be
    instantiated, and do not provide any guarantees in what ever respect.
    So, even if a class uses CartBehavior, you would never be sure that
    it actually also uses all of the implementations provided by it.


    Lastly, when you reflect on a trait, it comes back as an instance of
    ReflectionClass. As pointed out, traits are probably more similar to
    interfaces than they are to classes, and they definitely don't have
    properties, as are exposed by the ReflectionClass type.
    [Well, it can be useful to test for the properties expected by a trait.]
    Yes, that is an implementation artifact of how the reflection API is
    designed.

    I would also point out that the examples in the documentation (which it
    seems were just copied from the RFC?) do not demonstrate any real purpose
    of this feature. Is this the most anybody has attempted to do with this
    feature? Trying to come up with a real example, I didn't get very far
    before running into these stumbling blocks.
    I frequently search for usages of traits on the web, and by now, there are
    quite a number
    of articles giving examples and discussing the pros and cons of traits.
    So, no, you are not the first one.

    But, yes, the current documentation page could use some better examples.

    If I can't use instanceof to check for a trait (A), then I would at least
    expect to be able to write a trait that implements an interface (B) - does
    that not seem reasonable or logical?
    The discussion we had on that topic evolved to the following proposal:

    https://wiki.php.net/rfc/horizontalreuse#requiring_composing_class_to_implement_interface

    However, to my understanding, there was no consent on actually adding this.

    Having to use both the interface and the trait as a pair, and having to
    explicitly apply them both to the class, feels like a work-around.
    The discussions we had here suggest otherwise: this is exactly the way to
    go from a conceptual perspective.
    It keeps the two concepts apart, avoids confusion, and thus, is the
    conceptual purer alternative.
    However, by answering my questions from above, you can certainly make a
    point that the practical overhead does not justify this conceptual burden.
    I apologize if I'm somehow missing the big picture here, or maybe I set my
    expectations too high - but my first impression of this feature is that
    it's crippled and somewhat half-baked... If there was a deliberate and
    logical reason for not supporting these features, I would like to
    understand why. If not - great work so far, but perhaps this feature is not
    quite mature enough for release just yet?
    On a personal note: never tell a mother that her child is ugly.
    Please stay constructive. Thanks!

    Best regards
    Stefan


    --
    Stefan Marr
    Software Languages Lab
    Vrije Universiteit Brussel
    Pleinlaan 2 / B-1050 Brussels / Belgium
    http://soft.vub.ac.be/~smarr
    Phone: +32 2 629 2974
    Fax: +32 2 629 3525
  • Christopher Jones at Nov 16, 2011 at 2:31 am

    On 11/15/11 4:17 PM, Rasmus Schultz wrote:

    So I guess my remaining quibble is that the documentation needs to relay
    this somehow - with a basic real-world example that actually uses an
    interface too, to clarify the difference, and to demonstrate how this tool
    use useful in conjunction with class and interface declarations. Hello
    world really doesn't explain anything, other than the syntax.

    Here's a better example of something useful that actually works:
    Assuming your example is OK, you could edit the doc and submit it as a patch at https://edit.php.net.

    Chris

    --
    Email: christopher.jones@oracle.com
    Tel: +1 650 506 8630
    Blog: http://blogs.oracle.com/opal/
  • Rasmus Schultz at Nov 17, 2011 at 2:12 am
    who can hook me up with a login, so I can contribute to the documentation?

    Here's a better example of something useful that actually works:
    Assuming your example is OK, you could edit the doc and submit it as a
    patch at https://edit.php.net.
  • Christopher Jones at Nov 17, 2011 at 6:32 am

    On 11/16/11 6:12 PM, Rasmus Schultz wrote:
    who can hook me up with a login, so I can contribute to the documentation?
    You can do it as an "anonymous" user on edit.php.net. Join in the chat so you
    can prod someone to merge your changes - you might also need to remind the doc
    mail list. Once you've had a few patches committed you will likely get karma
    to commit directly.

    Chris
    Here's a better example of something useful that actually works:
    Assuming your example is OK, you could edit the doc and submit it as a
    patch at https://edit.php.net.
    --
    Email: christopher.jones@oracle.com
    Tel: +1 650 506 8630
    Blog: http://blogs.oracle.com/opal/
  • Richard Quadling at Nov 16, 2011 at 9:11 am

    On 16 November 2011 00:17, Rasmus Schultz wrote:
    Here's a better example of something useful that actually works:

    trait Accessors
    {
    public function __get($name)
    {
    return $this->{'get'.$name}();
    }

    public function __set($name, $value)
    {
    $this->{'set'.$name}($value);
    }
    }

    class OrderLine
    {
    use Accessors;

    public $price;
    public $amount;

    public function getTotal()
    {
    return $this->price * $this->amount;
    }
    }

    $line = new OrderLine;

    $line->price = 20;
    $line->amount = 3;

    echo "Total cost: ".$line->total;
    I like that example. It shows, at a fairly simple level, the ability
    to compose a class from potentially multiple traits.

    Putting the __magic_methods in a trait, what a great idea. This
    mechanism is allows for all sorts of easy to expand ideas.

    You _COULD_ use this trait to implement discrete getters and setters ...

    <?php
    /**
    * Trait to provide setter and getter functionality
    * with an exception for any missing method.
    */
    trait Accessors
    {
    public function __get($name)
    {
    if (method_exists($this, 'get' . $name))
    {
    return $this->{'get' . $name}();
    }
    elseif (property_exists($this, $name))
    {
    return $this->{$name};
    }
    else
    {
    throw new Exception("Cannot retrieve value of '$name'.");
    }
    }

    public function __set($name, $value)
    {
    if (method_exists($this, 'set' . $name))
    {
    $this->{'set' . $name}($value);
    }
    elseif (property_exists($this, $name))
    {
    $this->{$name} = $value;
    }
    else
    {
    throw new Exception("Cannot set value for '$name'.");
    }
    }
    }

    /**
    * @property float $price
    * @property float $amount
    * @property float $tax
    * @property-readonly float $total
    */
    class OrderLine
    {
    use Accessors;

    protected $price;
    protected $amount;
    protected $tax;

    public function getTotal()
    {
    return $this->price * $this->amount * (1 + ($this->tax / 100));
    }

    protected function getPrice()
    {
    return $this->price;
    }

    protected function getAmount()
    {
    return $this->amount;
    }

    protected function setPrice($price)
    {
    $this->price = $price;
    }

    protected function setAmount($amount)
    {
    $this->amount = $amount;
    }
    }

    $line = new OrderLine;

    $line->price = 20;
    $line->amount = 3;
    $line->tax = 10;

    echo "Total cost: ".$line->total;
    ?>

    outputs ...

    Total cost : 66

    So, the actual methods are hidden from public, but a developer can
    extend the class to add additional processing.

    Interestingly, I've used property_exists to expose public access to
    protected properties. I suppose that is always possible. Bad design
    really. I would guess you wouldn't mix the different access mechanisms
    (public/protected/private, __get/__set, getter()/setter()).

    Richard.


    --
    Richard Quadling
    Twitter : EE : Zend : PHPDoc : Fantasy Shopper
    @RQuadling : e-e.com/M_248814.html : bit.ly/9O8vFY : bit.ly/lFnVea :
    fan.sh/6/370
  • Stefan Marr at Nov 16, 2011 at 12:09 pm
    Hi Rasmus:
    On 16 Nov 2011, at 01:17, Rasmus Schultz wrote:

    I knew about the traits features in Scala, and I guess I assumed this would
    be similar - not so.
    Right, they are not the same. PHP's traits are much closer to the notion
    of traits introduced in the Smalltalk world, while Scala's traits are rather like
    mixins as for instance in Ruby. Aside issues like typing and whether that type
    information is preserved, the main difference is that PHP's traits do not use
    implicit conflict resolution.

    I believe the key to understanding traits, is understanding that traits are
    in fact an implementation detail - an artifact that does not really change
    or affect the nature of OOP in PHP as such, and by design, should not. I
    understand that now - thank you :-)

    From my perspective, a key difference from classes and interfaces, is that
    traits have no meaningful use at run-time - no type-hinting and with no
    real reflection-features that reveal the details. They cannot implement
    interfaces for classes, but classes can use them to implement interfaces.
    Traits just provide a set of method implementations that can be aggregated
    by classes. So in a sense, they're a design-time tool for the programmer.
    I like that explanation, thanks!

    So I guess my remaining quibble is that the documentation needs to relay
    this somehow - with a basic real-world example that actually uses an
    interface too, to clarify the difference, and to demonstrate how this tool
    use useful in conjunction with class and interface declarations. Hello
    world really doesn't explain anything, other than the syntax. [...]
    This may be a little too magical for an example in the documentation though
    - and doesn't demonstrate interfaces.

    I'll ponder this and post a better example if I can think of one... :-)
    As someone else already pointed out, there are tools to contribute such nuggets to the documentation. Please submit a patch.

    And just one other comment:
    var_dump($test instanceof CartBehavior); // => false
    Why would it be beneficial to throw an exception here?
    CartBehavior is a trait, and not a class or interface. The instanceof
    operator has no meaning for traits - it always returns false.

    You could argue that false is the expected result - I would argue that the
    only reason this happens to work at all, is because traits internally are
    classes.

    Suppose you thought you were actually doing a meaningful type-check of some
    sort? If by mistake you put a trait-name where you meant to put the name of
    an interface or class, such an error could be very hard to spot.
    While I agree that it can be hard to spot, I would usually advocate for less
    intrusive alternatives.

    Hmmm, well, I could put in a warning in strict mode, I guess.
    But that would require a consistent handling within all the other APIs we got, too.
    Especially is_a is such a candidate. And changing is_a seems to be a non-trivial
    thing. So, I will need to look carefully at that.

    Would be great if you could file a bug report for that.
    I hope this feedback is useful :-)
    Yes it is. I think it is especially important that other people actually write down their understanding of the mechanism. Blog posts, mailing list posts, improved documentation,
    they are all important.

    Thanks
    Stefan

    --
    Stefan Marr
    Software Languages Lab
    Vrije Universiteit Brussel
    Pleinlaan 2 / B-1050 Brussels / Belgium
    http://soft.vub.ac.be/~smarr
    Phone: +32 2 629 2974
    Fax: +32 2 629 3525

Related Discussions

Discussion Navigation
viewthread | post
Discussion Overview
groupphp-internals @
categoriesphp
postedNov 11, '11 at 4:00p
activeNov 17, '11 at 6:32a
posts9
users5
websitephp.net

People

Translate

site design / logo © 2022 Grokbase