PHP wrapper-based implementation that allows nested, recursive contexts

115 views
Skip to first unread message

Matthew Browne

unread,
May 12, 2013, 2:57:46 AM5/12/13
to object-co...@googlegroups.com
Hi all,
I created a new implementation of DCI for PHP using a wrapper approach with an important enhancement -- it keeps track of roles that have already been bound (using a hash) to help with the object identity problem.

You can check it out at the following link. The object identity workaround is in the Context::bindRole() method; also see MoneyTransfer::test().

https://gist.github.com/mbrowne/5562643

So far the only problem I have detected with this approach is that the object identity is no longer equivalent when the same data object is bound to more than one role, but I think it's easy enough to work around that as long as the programmer is aware of it.

Here are a few "out-takes":

abstract class Context
{
    static protected $rolePlayers = array();
   
    /**
     * Bind the methods of a role to a data object and returns the new role player,
     * OR returns the existing role player if the role has already
     * been bound to the given data object.
     *
     * @param object $dataObject
     * @param Context $context
     * @return Role
     */
    function bindRole($dataObject, $roleClassName) {
        if ($dataObject instanceof Role) $dataObject = $dataObject->data;
        $hash = spl_object_hash($dataObject) . $roleClassName;
       
        $thisClass = get_class($this);
        if (!isset(self::$rolePlayers[$thisClass][$hash])) {
            $rolePlayer = new $roleClassName($dataObject, $this);
            self::$rolePlayers[$thisClass][$hash] = $rolePlayer;
            return $rolePlayer;
        }
        return self::$rolePlayers[$thisClass][$hash];
    }
}


class MoneyTransfer extends \DCI\Context
{
    ...

    function __construct($sourceAccount, $destinationAccount, $amount) {
        $this->sourceAccount = Roles\SourceAccount::init($sourceAccount, $this);
        $this->destinationAccount = Roles\DestinationAccount::init($destinationAccount, $this);
        $this->amount = $amount;
    }
   
    function transfer() {
        $this->sourceAccount->transfer($this->amount);
    }
   
    function test() {
        $nestedContext = new MoneyTransfer($this->sourceAccount, $this->destinationAccount, $this->amount);
       
        var_dump($nestedContext->sourceAccount === $this->sourceAccount); //true
    }
}

rune funch

unread,
May 12, 2013, 5:17:43 AM5/12/13
to object-co...@googlegroups.com

2013/5/12 Matthew Browne <mbro...@gmail.com>

I think it's easy enough to work around that as long as the programmer is aware of it.

That requires the programmer to be on the team which is by no means given Ie. it's rules out using third party libraries that might rely on identity, such as most maps does and some optimizations

-Rune

Matthew Browne

unread,
May 12, 2013, 11:43:53 AM5/12/13
to object-co...@googlegroups.com
Thanks for your feedback...

Let's say I released this DCI implementation as an open-source library. Suppose it gained some traction and there came a point where a number of 3rd party libraries using it were available (I think that's a long way off given the nature of PHP and its community, but that's another story).

I was originally going to reply that in my implementation, I don't think the problem we're discussing is particularly counter-intuitive. But upon further reflection, I see how the fact that a wrapper is being used might not be obvious to someone looking at the code for the first time.

I still think that the more surprising thing for the programmer (because it might be surprising even if he/she understood that a wrapper was being used) would be if an object playing the same role in a nested context didn't equal the one in the parent context, and I think I have solved that.

I'm sticking with this implementation for now because it works in PHP 5.3 (current release is 5.4) and requires fewer hacks than the method injection approach I posted earlier. I haven't tried it yet but I believe my enhancement to a more naiive wrapper implementation should make it possible for the Djikstra algorithm to work (which as I understand it is a good test of an implementation).

Would you agree that all things considered, especially the much less hacky nature of the wrapper approach when programming in PHP, that the wrapper implementation is preferable for PHP?

Trygve made an excellent point in an old post I read on here expressing his wish that people would invest more into creating languages and tools that are specifically designed for DCI than in workarounds and hacks for existing languages. Unfortunately I have to code in PHP a lot for work, in many cases because I'm extending or revamping existing systems written in PHP...so I'm just trying to find the next-best thing to a proper DCI implementation.
--
You received this message because you are subscribed to a topic in the Google Groups "object-composition" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/object-composition/g4BMSdluuC8/unsubscribe?hl=en.
To unsubscribe from this group and all its topics, send an email to object-composit...@googlegroups.com.
To post to this group, send email to object-co...@googlegroups.com.
Visit this group at http://groups.google.com/group/object-composition?hl=en.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

James O Coplien

unread,
May 12, 2013, 11:56:41 AM5/12/13
to object-co...@googlegroups.com

On May 12, 2013, at 5:43 , Matthew Browne wrote:

Would you agree that all things considered, especially the much less hacky nature of the wrapper approach when programming in PHP, that the wrapper implementation is preferable for PHP?

No.

Matthew Browne

unread,
May 12, 2013, 12:14:41 PM5/12/13
to object-co...@googlegroups.com
Thanks Jim. I take that to mean that method injection is preferable to wrappers whenever possible. I'm concerned about the method injection code I wrote earlier being too confusing to intermediate-level PHP coders who may need to maintain the systems I'm working on, but there's certainly no win-win here.

Speaking of next best things, I'm curious as to what the "micro-SOA" that Ant talked about in this thread would look like.

I realize that the micro-SOA he described does not accomplish what DCI does; some might say it doesn't even come very close. But I do wonder if it's a potentially viable strategy for organizing my code along context and role boundaries without the learning overhead (for a programmer new to the system) of a real DCI implementation. At least then the issue of the role player and the data object not being equal wouldn't even come up.

I'm still sticking with DCI for the PHP system I'm currently working on, but if I just want to quickly write up a use case in code, organized according to roles, in some other system where the learning overhead for other programmers doesn't seem worth it (I could elaborate on this but just suppose for now that it's really not worth it), I'm wondering if micro-SOA would fit the bill.

rune funch

unread,
May 12, 2013, 1:15:05 PM5/12/13
to object-co...@googlegroups.com

2013/5/12 Matthew Browne <mbro...@gmail.com>
I don't think the problem we're discussing is particularly counter-intuitive. But upon further reflection, I see how the fact that a wrapper is being used might not be obvious to someone looking at the code for the first time

I'm not talking about someone using your code. I'm talking about you using someone else's code. However in PHP that might not be much of a problem since PHP accoring to php.net doesn't have an identity operator. That means that if you make your wrapper the same type as then wrapped you have no operator that can tell the difference. However if you do not do that. If the wrapper is of a different type than the wrapped object then any piece of code you use that uses === will fail e.g. if you used this collection class

class Collection
    

    function 
contains(obj) { 
        for(var $i = 0; $i < $arr.length;$++){
           if($arr[$i] === obj){
              return true;
           }
        }
        return false; 
    } 
   //...


you could then write

    if(!collection.Contains(obj)){
       collection.add(obj)
    }

which would result in that the "object" was always added. I've put quotes around object because it would of course actually be multiple objects but conceptually they are one and the same which is the problem.
This code being my code is written independently of the code that is going to use it in some other project.

-Rune 


James O Coplien

unread,
May 12, 2013, 2:25:43 PM5/12/13
to object-co...@googlegroups.com

On May 12, 2013, at 6:14 , Matthew Browne wrote:

Thanks Jim. I take that to mean that method injection is preferable to wrappers whenever possible. I'm concerned about the method injection code I wrote earlier being too confusing to intermediate-level PHP coders who may need to maintain the systems I'm working on, but there's certainly no win-win here.

Method injection has problems, too. Wrappers have really,  really nasty problems, and they're not DCI. In my exploration of DCI or other forms of object composition, rather than layering, I'll take injection over layering.

If the code is too opaque to novice PHP programmers, then 1. get PHP fixed, 2. stop using PHP  3. build a library or 4. find other solutions. At some point you have to draw the line. There's no reason that DCI should be doomed to succeed in PHP.

Matthew Browne

unread,
May 12, 2013, 5:02:05 PM5/12/13
to object-co...@googlegroups.com
On 5/12/13 2:25 PM, James O Coplien wrote:
> Method injection has problems, too. Wrappers have really, really
> nasty problems, and they're not DCI. In my exploration of DCI or other
> forms of object composition, rather than layering, I'll take injection
> over layering.
I fully agree that wrappers are not DCI, and I don't doubt that you're
right about them having worse problems than method injection, even with
a smarter role player factory as in my implementation. But I'm guessing
these problems include more than just the issue of when the same object
plays more than one role in the same context. So far that's the only
problem I see with my wrapper implementation. But I think I might be
missing something because to me that single concession seems acceptable
in my particular case, even if it's not a good solution to present to
the PHP community (thanks Rune for the clarification on that).
> If the code is too opaque to novice PHP programmers, then 1. get PHP
> fixed, 2. stop using PHP 3. build a library or 4. find other
> solutions. At some point you have to draw the line. There's no reason
> that DCI should be doomed to succeed in PHP.

I don't see how true DCI can be implemented in PHP without source code
transformation or a custom PHP extension written in C. Of these I think
source code transformation would be easier to implement and also have
fewer potential installation issues. But it would still add complexity
in its own way because if it ever broke in some particular case and a
future programmer were left to handle it, they would need to learn the
inner workings of the source code transformation (a greater challenge
than just learning DCI). More importantly, the source code
transformation would need to be explained in the documentation which
would be time-consuming for me and would be tangential to the future
system maintainer's goal of actually learning DCI programming.

In the end I'm not sure it's worth it to invest in such a tool for PHP
unless PHP evolves into a better overall language. In an ideal world I
would use another language or language extension better suited for DCI,
but that's generally only an option for new projects given that many
clients come to us with existing PHP systems (many of which are in a
condition where it makes sense to rewrite large parts of them or add new
features using DCI, but not rewrite the entire system in a new language).

So I see either method injection or wrappers as being reasonable ways of
writing good quality code for these systems (or even new systems if
there are very strong reasons to choose PHP). I realize that method
injection isn't perfect but I think future maintainers would be able to
use it successfully without any major confusion or issues. And I think
that may even be true for my wrapper implementation, but as I said I'm
probably not understanding all of the ways it can fail. Other reasons
for giving the wrapper approach real consideration are that it's much
more performant than my wrapper implementation and the internal DCI
implementation code is easier to follow.

If I become convinced that using these approaches is something I will
later regret, then I'll suck it up and write a source transformation
library. I don't quite know how to do that but I've seen discussions of
it here and I could probably reference Rune's Maroon implementation to
figure it out (possibly with some help from you all).

I think the implementations I've already done are about as good as it
gets in native PHP. I really would rather avoid writing an extension to
the PHP engine, so source transformation seems like the only other
option. It seems like a very good option technically speaking, I'm just
not sure if the pros outweigh the cons when considering the larger picture.

Matthew Browne

unread,
May 12, 2013, 5:54:45 PM5/12/13
to object-co...@googlegroups.com
There is one other possibility for a native PHP DCI-like implementation but it's even further from real DCI.

I'm thinking of an implementation where the internal data object is the ONLY way to access a role player's domain-model properties and methods, e.g.:

class SourceAccountRole extends Role
{
    function withdraw($amount) {
        $this->data->decreaseBalance($amount);
    }
   
    ...
}


If roles were bound like this:

$sourceAccount = new SourceAccountRole($sourceAccount);

then there would no longer be any reason for the programmer to expect that the role player identity should be the same as the data object identity (and consequently be surprised if it's not).

But this is my least favorite approach so far...I'm just mentioning it for the sake of completeness and in case anyone thinks it merits further consideration.

James O Coplien

unread,
May 13, 2013, 1:45:36 AM5/13/13
to object-co...@googlegroups.com
On May 12, 2013, at 11:02 , Matthew Browne wrote:

> I don't see how true DCI can be implemented in PHP without source code transformation or a custom PHP extension written in C.

Then give it up; or, see options 1, 2 and 3.

Matthew Browne

unread,
May 13, 2013, 9:17:06 AM5/13/13
to object-co...@googlegroups.com

Thanks...it's ultimately my job of course to decide which option is most worth my time, and I'm not asking you all to decide that for me (I was just giving some background).

Do you have any links handy that summarize the problems with the wrapper approach? If not, I can search through this forum. I'm still not understanding why it's so terribly awful given my concession that programmers would need to be aware of the caveats.

Trygve Reenskaug

unread,
May 15, 2013, 6:24:10 AM5/15/13
to object-co...@googlegroups.com
Source code transformation could be a workable solution in many language environments. I realize that nearly all my Squeak compiler extensions are essentially source code transformations. I have given the details in a long message on the evolution list  (dci-ev...@googlegroups.com)
--Trygve
--

Trygve Reenskaug      mailto: try...@ifi.uio.no
Morgedalsvn. 5A       http://folk.uio.no/trygver/
N-0378 Oslo             http://fullOO.info
Norway                     Tel: (+47) 22 49 57 27

rune funch

unread,
May 15, 2013, 7:30:05 AM5/15/13
to object-co...@googlegroups.com
So is both maroon and Marvin essentially. There's a bit more than just source transformation in the latter and I do envision a DCI centric semantic analyzer to be included in maroon (a flag you can specify that will enforce verifiable DCI rules, such as binding all roles in the same method, than binding needs to take place prior to executing the meet of an interactions and other such rules)
The Marvin example pretty much shows that it's possible to build the transformation on top of any existing language. Marvin is (compile time) strongly typed and the difficult parts was in extending the type checking to:
a) Allow roles to be played by any object that adheres to the contract
b) verify that the roleplayers adheres to the contract

Both made even more interesting by the fact that I wanted to support generics without using dynamic as the type of the roleplayers.

I've bootstrapped maroon which have been an excellent journey. I've learned a lot of the pit falls I had in the original version (most of them RUby specific) but also one that's related to all transformation effort line-by-line debugging requires quite some effort (I  kinda have that in Marvin but not completely), it's not part of maroon yet. The closest you can come atm is writing the generated classes to disk and using those for debugging. That's how I do when working on maroon. I'd love to fix that but it's not on the top of the backlog yet
aside from the source line-by-line debugging I've found that transformation works well


-Rune


2013/5/15 Trygve Reenskaug <try...@ifi.uio.no>

--
You received this message because you are subscribed to the Google Groups "object-composition" group.
To unsubscribe from this group and stop receiving emails from it, send an email to object-composit...@googlegroups.com.

Risto Välimäki

unread,
May 15, 2013, 12:21:53 PM5/15/13
to object-co...@googlegroups.com
When we are talking about problems with wrappers, we are talking about Roles being wrappers that wraps around Objects. 

Not the other way around. If you inject stateless "role-method-object" into a role playing Object (and hide it well using PHP magic methods), there are no object identity problems other than those usual problems that most method injection techniques do contain. Basically it's just the same thing (and I bet that in some languages 100%, exactly done so) that method injection does. 

Of course there is a problem with getting rid of the injected method, although much less severe than for example those with Ruby that render Ruby's built-in method injection completely unusable for DCI.

Best bet for almost every language out there is "no-injection" (and certainly no-wrappers) solution with quite simple source code transformation. That's already done for C#/.Mono (Marvin) & Scala (dci compiler plugin). "no-injection" is the most beautiful solution with most clean syntax, doesn't pollute/affect "enhanced" Object any ways, is free from all kinds of accidental polymorphism and (at least in solutions I have seen) is lightning fast because it doesn't need any kind of memory operations let alone reflection/routing in run time. Being fast & light for memory is certainly not in TOP 10 goals for DCI, but it's still nice.

With PHP it's easy to do sort of "method injection"with "object injection", but then you need to implement that ability to role playing objects, which is something I'd rather avoid. Also all the horrors of unintended method overriding that are there with 99% of all method injection styles are waiting for innocent developer. That's why I did choose wrapper solution instead in "php-dciv" (Role wrapping the Object) with some help of magic methods to hide the fact, but I think it's only applicable for short living (execution-wise, not lifetime-wise) web apps because of the possible identity problems.

-Risto


2013/5/15 rune funch <funchs...@gmail.com>

James O Coplien

unread,
May 15, 2013, 2:36:46 PM5/15/13
to object-co...@googlegroups.com

On May 15, 2013, at 6:21 , Risto Välimäki wrote:

When we are talking about problems with wrappers, we are talking about Roles being wrappers that wraps around Objects. 


No. A role can be thought of as a collection of traits of an object. It does not wrap an object. I think you are confused.

If you have an object *representing* a role, and that object wraps another object, then you have trouble. Don't confuse roles with how they are implemented. There are many ways to represent roles — injection; the JIT approach that Trygve uses in Squeak, the compile-time bindings with C++ traits — but these representations are neither necessary nor sufficient to having roles.

James O Coplien

unread,
May 15, 2013, 2:39:09 PM5/15/13
to object-co...@googlegroups.com
The first C++ compiler — cfront — was often criticized as being just a "source translator" from C++ to C. It eventually evolved to what we know today.

All compilers are translators: it's just a matter of level.

Read: "Le ton beau de Mareau" by Hofstadter.

Matthew Browne

unread,
May 16, 2013, 1:55:19 AM5/16/13
to object-co...@googlegroups.com
Thanks everyone for the very helpful feedback. And thanks Trygve and Rune for the additional details about source code transformation. I still haven't decided whether I want to take on such a thing for PHP but another platform I'm very interested in these days is node.js, which I think could be a great platform for DCI. In any case, this info on how to implement source transformation is very informative.

On a related note, I think Andreas's implementation of DCI in Haxe means that DCI code that compiles down to PHP, Javascript, etc. is already a reality - I say the more options the better!

Matthew Browne

unread,
May 16, 2013, 2:44:36 AM5/16/13
to object-co...@googlegroups.com
I think Risto meant something like the following code -- it's the complete reverse of the usual wrapper implementation. Basically every object that has any chance of being used in a DCI context would need to extend from a base Object class. Note that in PHP, __call, __get, and __set are "magic methods" that are automatically called if a method or property with a given name isn't found.

Some more comments below the example...

abstract class Object
{
��� private $roles = array();
��� private $roleMethods = array();

��� function addRole($roleClassName) {
������� $role = new $roleClassName($this);
������� $this->roles[] = $role;
�������
������� //Get method names for this role using reflection
������� ...
������� foreach ($methodNames as $methodName) {
����������� $this->roleMethods[$methodName][$roleClassName] = $role;
������� }
��� }
���
��� function removeAllRoles() {
������� $this->roles = array();
������� $this->roleMethods = array();
��� }
���
��� function __call($methodName, $args) {
������� $rolesWithThisMethod = $this->roleMethods[$methodName];
������� if (count($rolesWithThisMethod) == 1) {
����������� $role = current($rolesWithThisMethod);
����������� //Call $role->$methodName() with the given arguments
����������� return call_user_func_array(array($role, $methodName), $args);
������� }
������� //else handle naming conflict or role method not found
������� ...
��� }
}

abstract class Role
{
��� private $data;

��� function __construct($data) {
������� $this->data = $data;
��� }

��� function __get($propName) {
������� return $this->data->$propName;
��� }
���
��� function __set($propName, $val) {
������� $this->data->$propName = $val;
��� }
���
��� function __call($methodName, $args) {
������� return call_user_func_array(array($this->data, $methodName), $args);
��� }
}

class Account extends Object
{
��� ...
}

class MoneyTransfer extends Object
{
��� ...
��� function __construct($sourceAcct, $destinationAcct, $amount) {
������� $this->sourceAcct = $sourceAcct;
������� $sourceAcct->addRole('MoneyTransfer\SourceAccount');
������� ...
��� }
��� ...
}

//PHP doesn't support inner classes so I'm using a namespace instead
namespace MoneyTransfer
{
��� class SourceAccount extends \Role {
������� ...

��� }
��� ...
}



This is a clever idea; when working on my wrapper implementation it never occurred to me that the object could wrap the role rather than the role wrapping the object.

From the context, the role player only ever has one identity here -- that of the domain object / data object. From inside the role, $this->data would not be equal to $this but role methods should never be using $this->data anyway since the implementation allows $this to work naturally. Remember that $this->data is the parent object here (the object wraps the role)!

In PHP 5.4, instead of a base Object class it could be a trait, called AssignableToRole like in my PHP 5.4 example.

Of course I see what you're saying, Risto, about this not being without its problems. In many cases requiring all objects (at least all domain model-related objects and DCI contexts) to extend from a base Object class (or to include the AssignableToRole trait -- in PHP traits are a compile-time concept) would be a steep requirement, but actually in the app I'm working on I think it could be doable. I have a base DomainObject class and Collection class already. It's actually pretty common in PHP frameworks to have all classes extend from a base Object class. If any of the other classes need to implement __call() though they'd have to be sure to call parent::__call() when they were done.

I figure clean-up could be handled with the __destruct() method or an __unbindAllRoles() method on the context.

Thanks for mentioning this approach; I think it could actually be a real possibility for my work in PHP, although as you say the injectionless approach (which would require source transformation) would be more elegant. I'll think more about this and post back if this ends up being viable...I think it at least deserves some experimentation and then my next best bet, given that I don't have time to get into source transformation right at the moment, would probably be the PHP 5.4 approach I linked to above, which may be better (if more complex in implementation) since there's only one $this. But I'm not sure if that matters when it's the object wrapping the role.

P.S. to Risto Your DCIV framework was one of the things I checked out as I was learning about DCI and I found it useful to see how you had organized a web framework around DCI.


On 5/15/13 2:36 PM, James O Coplien wrote:

On May 15, 2013, at 6:21 , Risto V�lim�ki wrote:

When we are talking about problems with wrappers, we are talking about Roles being wrappers that wraps around Objects.�


No. A role can be thought of as a collection of traits of an object. It does not wrap an object. I think you are confused.

If you have an object *representing* a role, and that object wraps another object, then you have trouble. Don't confuse roles with how they are implemented. There are many ways to represent roles � injection; the JIT approach that Trygve uses in Squeak, the compile-time bindings with C++ traits � but these representations are neither necessary nor sufficient to having roles.
--


Risto Välimäki

unread,
May 16, 2013, 5:01:20 PM5/16/13
to object-co...@googlegroups.com
Matthew, that's exactly what I meant.

I have not updated the gitorious version of the PHP-DCIV for a long while, but there are currently at least 6 web sites online using that simple "framework".

I just thought that it would be possible to overcome most if not all accidental polymorphism issues with that PHP method injection / "reverse wrapper" technique at least by introducing underscore prefix for Role methods. Anyway, the Role method being accidentally overridden by "Data" method is much less severe and much easier to spot than the other way around, though I dislike both alternatives. When Roles are wrappers there are absolutely no accidental overriding problems, and that's the case with preferred "no-injection" style as well.

Since in fact I'm already using single super class for all of my (database backed) "Data" objects in DCIV, that requirement for role injection ability in Data objects is actually a ~non-issue.

I suppose you can't call magic method's in PHP unless method lookup has already failed, eg. if you have a method called "foo()", calling "foo()" calls "foo()" no matter what magic __call() -methods you have implemented.

Matthew or other PHP gurus here: is there any possibility that you could acquire the name of the file or class where the method call was made? If this is possible, you could easily do "no-injection" (and "no-wrapper") DCI without source transformation, only major problem being that you could NOT use "$this" keyword inside Role methods. Another annoyance is role method syntax having "rolename_" -prefix.

Consider this: (sorry if syntax is not right, I have done a lot C#, Java/Android & Javascript lately)

class MoneyTranfer extends Context {
  $source = new SourceAccount(140);
  $destination = new DestinationAccount(400);
  $amount;  

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

  function transfer() {
    $source->transfer();
  }
  
  // Role method(s):
  function source_transfer() {
    $source->add($destination->sub($amount));
  }
}

Easiest above could work with reflection, but by far easiest/laziest version with crude source code transformation could be:

 ...
 function start() {
   $FooRole->roleMethod();
 }
 // Role methods:
 function $FooRole->roleMethod() {
   $FooRole->instanceMethod();
 }

 --> above transformed:
 function start() {
   FooRole_roleMethod();
 }
 function FooRole_roleMethod() {
   $FooRole->instanceMethod();
 }

To transform you just need to regexp find "function ${[A-Za-z0-9_]*}->{[A-Za-z0-9_]*}(" and then replace all occurences of matching string not including "function " from start with version that hasn't "$" and having "->" translated to "_".

I have to admit that the syntax is a bit dull and also quite odd, but maybe it's anyway quite easy to understand that "function $x->y()" effectively enhances the object $x with method y(). 

"Public beta" version of above could also search & replace all possible occurences of "$this" inside a role methods with " $RoleName".

I could quite easily turn a framework like "php-dciv" into "no-injection" using just simple "compiling" since in PHP it's you as a developer who decides what files to include and where to. File names may also be variables, and also router in "php-dciv" uses that feature. Instead of "context/MoneyTransfer.php" for "MoneyTransfer" context I could include "context/MoneyTransfer.php.compiled" as well.

-Risto 



2013/5/16 Matthew Browne <mbro...@gmail.com>
I think Risto meant something like the following code -- it's the complete reverse of the usual wrapper implementation. Basically every object that has any chance of being used in a DCI context would need to extend from a base Object class. Note that in PHP, __call, __get, and __set are "magic methods" that are automatically called if a method or property with a given name isn't found.

Some more comments below the example...

abstract class Object
{
    private $roles = array();
    private $roleMethods = array();

    function addRole($roleClassName) {
        $role = new $roleClassName($this);
        $this->roles[] = $role;

       
        //Get method names for this role using reflection
        ...
        foreach ($methodNames as $methodName) {

            $this->roleMethods[$methodName][$roleClassName] = $role;
        }
    }
   
    function removeAllRoles() {
        $this->roles = array();
        $this->roleMethods = array();
    }
   
    function __call($methodName, $args) {

        $rolesWithThisMethod = $this->roleMethods[$methodName];
        if (count($rolesWithThisMethod) == 1) {
            $role = current($rolesWithThisMethod);

            //Call $role->$methodName() with the given arguments
            return call_user_func_array(array($role, $methodName), $args);

        }
        //else handle naming conflict or role method not found
        ...
    }
}

abstract class Role
{
    private $data;

    function __construct($data) {
        $this->data = $data;
    }

    function __get($propName) {
        return $this->data->$propName;
    }
   
    function __set($propName, $val) {

        $this->data->$propName = $val;
    }
   
    function __call($methodName, $args) {

        return call_user_func_array(array($this->data, $methodName), $args);
    }
}

class Account extends Object
{

    ...
}

class MoneyTransfer extends Object
{
    ...
    function __construct($sourceAcct, $destinationAcct, $amount) {
        $this->sourceAcct = $sourceAcct;

        $sourceAcct->addRole('MoneyTransfer\SourceAccount');
        ...

    }
    ...
}

//PHP doesn't support inner classes so I'm using a namespace instead
namespace MoneyTransfer
{
    class SourceAccount extends \Role {
        ...

    }
    ...
}



This is a clever idea; when working on my wrapper implementation it never occurred to me that the object could wrap the role rather than the role wrapping the object.

From the context, the role player only ever has one identity here -- that of the domain object / data object. From inside the role, $this->data would not be equal to $this but role methods should never be using $this->data anyway since the implementation allows $this to work naturally. Remember that $this->data is the parent object here (the object wraps the role)!

In PHP 5.4, instead of a base Object class it could be a trait, called AssignableToRole like in my PHP 5.4 example.

Of course I see what you're saying, Risto, about this not being without its problems. In many cases requiring all objects (at least all domain model-related objects and DCI contexts) to extend from a base Object class (or to include the AssignableToRole trait -- in PHP traits are a compile-time concept) would be a steep requirement, but actually in the app I'm working on I think it could be doable. I have a base DomainObject class and Collection class already. It's actually pretty common in PHP frameworks to have all classes extend from a base Object class. If any of the other classes need to implement __call() though they'd have to be sure to call parent::__call() when they were done.

I figure clean-up could be handled with the __destruct() method or an __unbindAllRoles() method on the context.

Thanks for mentioning this approach; I think it could actually be a real possibility for my work in PHP, although as you say the injectionless approach (which would require source transformation) would be more elegant. I'll think more about this and post back if this ends up being viable...I think it at least deserves some experimentation and then my next best bet, given that I don't have time to get into source transformation right at the moment, would probably be the PHP 5.4 approach I linked to above, which may be better (if more complex in implementation) since there's only one $this. But I'm not sure if that matters when it's the object wrapping the role.

P.S. to Risto Your DCIV framework was one of the things I checked out as I was learning about DCI and I found it useful to see how you had organized a web framework around DCI.



On 5/15/13 2:36 PM, James O Coplien wrote:

On May 15, 2013, at 6:21 , Risto Välimäki wrote:

When we are talking about problems with wrappers, we are talking about Roles being wrappers that wraps around Objects. 


No. A role can be thought of as a collection of traits of an object. It does not wrap an object. I think you are confused.

If you have an object *representing* a role, and that object wraps another object, then you have trouble. Don't confuse roles with how they are implemented. There are many ways to represent roles — injection; the JIT approach that Trygve uses in Squeak, the compile-time bindings with C++ traits — but these representations are neither necessary nor sufficient to having roles.
--


Matthew Browne

unread,
May 16, 2013, 10:31:39 PM5/16/13
to object-co...@googlegroups.com
Hi Risto,


On 5/16/13 5:01 PM, Risto V�lim�ki wrote:
I just thought that it would be possible to overcome most if not all accidental polymorphism issues with that PHP method injection / "reverse wrapper" technique at least by introducing underscore prefix for Role methods. Anyway, the Role method being accidentally overridden by "Data" method is much less severe and much easier to spot than the other way around, though I dislike both alternatives. When Roles are wrappers there are absolutely no accidental overriding problems, and that's the case with preferred "no-injection" style as well.
Overall I prefer the reverse wrapper to the regular wrapper because it solves the self schizophrenia problem. As Jim said, a typical wrapper implementation (role wrapping object) is not DCI -- and I would add that that's because DCI is about adding role behavior to objects, not the other way around. The reverse wrapper is basically the same idea as method injection -- you could call it a method injection shim for languages that don't support it.

I was considering with going with the role-wrapping-object approach just for the sake of simplicity (while recognizing its shortcomings), but now I'm leaning toward the reverse wrapper approach which is almost as simple in implementation without the identity problems.

As to accidental naming conflicts, I think it's generally possible to avoid them in the first place. The only times I can think of when they would be unavoidable would be recursive contexts like the Dijkstra algorithm and methods like toString (as Rune pointed out). Am I missing something?

I suppose you can't call magic method's in PHP unless method lookup has already failed, eg. if you have a method called "foo()", calling "foo()" calls "foo()" no matter what magic __call() -methods you have implemented.
Yes, this is unfortunately true in PHP and that's the reason that with the reverse wrapper approach, you wouldn't be able to have a role method that overrides a data object method, which is one shortcoming compared to the regular wrapper approach. It's unfortunate that PHP doesn't give you more control with method lookups - this also means that public properties break lazy-loading in ORM libraries (like Doctrine ORM).

Matthew or other PHP gurus here: is there any possibility that you could acquire the name of the file or class where the method call was made? If this is possible, you could easily do "no-injection" (and "no-wrapper") DCI without source transformation, only major problem being that you could NOT use "$this" keyword inside Role methods. Another annoyance is role method syntax having "rolename_" -prefix.
The only way I know to do this would be with the debug_backtrace() function. I have considered DCI-like techniques that don't attempt to have $this be available for both role methods and data methods, but they seemed awkward compared to techniques looking more like real DCI.

Thanks for the details with regard to super-simple source transformation...my inclination though would be that if I'm going to do source transformation, I'd rather go all the way and make role binding automatic or maybe using the = operator like in Marc's Scala extension. I realize that this could involve significantly more work.

The source transformer could easily find all the role methods if roles were defined using the "trait" keyword (which already exists in PHP 5.4, albeit not as a dynamic concept), e.g.:

class MoneyTransfer {
��� ...
��� trait SourceAccount {...}
��� trait DestinationAccount {...}
��� trait Amount {}���
}

Of course the "trait" keyword isn't allowed inside a class in PHP (nor are inner classes supported), but the source would be transformed so it doesn't matter...

In native PHP I think it makes the most sense to put roles in their own namespace, e.g.:

namespace UseCases
{
��� class MoneyTransfer {

��� ��� ...
��� }
}

namespace UseCases\MoneyTransfer\Roles
{
��� trait SourceAccount {...}
��� trait DestinationAccount {...}
��� trait Amount {}
}


Since PHP traits aren't dynamic, a native PHP library would have to internally create a class for each one, e.g.

eval('class UseCases_MoneyTransfer_SourceAccount extends DCI\Role { use SourceAccount; }');

But I think it would be worth it since "trait" is a more logical keyword to use for roles than "class."


I could quite easily turn a framework like "php-dciv" into "no-injection" using just simple "compiling" since in PHP it's you as a developer who decides what files to include and where to. File names may also be variables, and also router in "php-dciv" uses that feature. Instead of "context/MoneyTransfer.php" for "MoneyTransfer" context I could include "context/MoneyTransfer.php.compiled" as well.
Yes, source transformation would definitely be doable and could work quite well, although I think if one is willing to live with having every potential role-playing object extending from a "RolePlayer" class, or using a "RolePlayer" trait in PHP 5.4, the reverse wrapper approach comes as close to true DCI as method injection which is pretty good.

Reply all
Reply to author
Forward
0 new messages