I think it's easy enough to work around that as long as the programmer is aware of it.
--
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.
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?
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
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.
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.
--
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.
When we are talking about problems with wrappers, we are talking about Roles being wrappers that wraps around Objects.
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.--
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.--
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.
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.
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.