Let me start out by saying that I like the traits proposal as it is now
(you can argue the keywords etc. later). I am just offering an idea that
I also would find acceptable, that gives the most control to the
developer, and allows for non-breaking, stateful traits.
Non-breaking Traits
===================
This idea is a continuation of the stateful traits discussed in
"Stateful Traits and their Formalization" [1]_. I would suggest reading
Section 4: "Stateful traits: reconciling traits and state" before
continuing this discussion.
Instead of automatically including all trait methods into the scope of
the class, all methods and properties are automatically in the local
scope of the trait. The developer then has the option of finer grained
control on the method and property level.
The options include the following:
1) Keeping the method or properties *local* to the scope of the trait.
2) Placing the method or properties into the scope of the including
*class*.
3) Making an *alias* of the property or method and use either scope.
4) *Merging* multiple properties or methods in the class scope.
Keywords Used In Sample Code
----------------------------
For all of the code examples, the following keywords were used:
`trait`
This keyword is used to mark a trait definition exactly as the `class`
keyword works for classes.
`class`
Same as current PHP keyword.
`include`
Used to include a trait into a class definition
`private`
This keyword is used in its current PHP sense as well as to mean that
a property or method is to retain its *local* trait scope.
`public`
This keyword is used in its current PHP sense as well as to mean that
a property or method is to be used within the scope of the including
class.
`as`
Used to *alias* or *merge* a trait into a class.
Keeping Methods and Properties Local
------------------------------------
Keeping a method or property *local* to a trait denies access from
within the class or an object created from the class. It also keeps all
references from methods within **that** trait to the method or property
linked to the local method, even if the same name is used in another
included trait, or is overridden in the including class.
The problem with including an entire trait locally is there is not way
to use the methods or properties. The trait is only useful if you
include at least one of the methods or properties into the class scope.
The following code example shows how *local* scoping works::
trait A1 {
private $a = 'A1';
private function printA() {
echo $this->a;
}
public function callPrintA1() {
$this->printA();
}
}
trait A2 {
private $a = 'A2';
protected function printA() {
echo $this->a;
}
public function callPrintA2() {
$this->printA();
}
}
class A {
include A1 {
public callPrintA1();
}
include A2 {
public printA();
}
public function callPrintA() {
$this->printA();
}
}
$a = new A();
$a->callPrintA1(); // 'A1'
$a->callPrintA2(); // Fatal Error: Undefined method...
$a->callPrintA(); // 'A2'
Placing Methods and Properties into the Class Scope
---------------------------------------------------
When placed into the scope of the class, a method or property behave as
if it were copied and pasted directly into the class, with one
exception: to not break the rules of trait local scope, any method or
property with trait local scope in the class is used instead of a
property or method in the class scope. This exception is the most
important distinction between normal traits and non-breaking traits.
As shown in the code example for local scope, the method call to
`callPrintA1` has a subsequent call to `printA`. Since the `printA`
method from the trait was in the local scope, it is called instead of
the method in the class scope.
Making An Alias
---------------
An alias can be made of a method or property in either local or class
scope. When the alias is made, it is placed into the class scope (what
else would the alias be for?). The alias still adheres to the rules for
methods and properties locally scoped to the trait. Also, any change to
an aliased property changes the original property. The same is not true
for an aliased method being reimplemented in the class; it will not
replace a trait's local method.
The following code example shows how aliasing works::
trait A1 {
private $a = 'A1';
private function printA() {
echo $this->a;
}
public function callPrintA1() {
$this->printA();
}
}
trait A2 {
private $a = 'A2';
protected function printA() {
echo $this->a;
}
public function callPrintA2() {
$this->printA();
}
}
class A {
include A1 {
private $a as $a_from_A1;
public callPrintA1();
}
include A2 {
public printA();
private callPrintA2() as callPrintA();
}
public function setA1( $value ) {
$this->a_from_A1 = $value;
}
}
$a = new A();
$a->callPrintA1(); // 'A1'
$a->setA1( 'A1-changed' );
$a->callPrintA1(); // 'A1-changed'
$a->callPrintA2(); // Fatal Error: Undefined method...
$a->callPrintA(); // 'A2'
Merging Multiple Methods or Properties
--------------------------------------
Merging is an extension of aliasing. Instead of aliasing properties or
methods to unique names, multiple properties or methods are aliased to
the same name, and an implementation of the method or a value for the
property is placed in the combined class.
To access the original method from within the class, multiple aliases
will need to be made, or the method can be made local to the class scope
and accessed from its original name.
The following code example shows how aliasing works::
trait A1 {
private $a = 'A1';
private function printA() {
echo $this->a;
}
public function callPrintA1() {
$this->printA();
}
}
trait A2 {
private $a = 'A2';
protected function printA() {
echo $this->a;
}
public function callPrintA2() {
$this->printA();
}
}
class A {
include A1 {
private $a as $my_a;
public callPrintA1() as callPrintA();
}
include A2 {
private $a as $my_a;
public printA();
public callPrintA2() as callPrintA();
}
private $my_a = 'A';
public function setA( $value ) {
$this->my_a = $value;
}
}
$a = new A();
$a->callPrintA1(); // 'A'
$a->setA( 'A-changed' );
$a->callPrintA1(); // 'A-changed'
$a->callPrintA2(); // 'A-changed'
$a->callPrintA(); // 'A-changed'
Implementation Ideas
====================
Two approaches to implementation are outlined below:
1) Flatten
2) Don't Flatten
I am not familiar with PHP internals, and these are only my thoughts
on the matter. I will let others decide the best route to take on
this.
Flatten
-------
The idea here is to place all of the properties and methods into the
class definition at compile time. This makes it possible to run the code
as if traits don't even exist. The benefits of this is that the run-time
internals of PHP should not be affected. The downside is that compiling
becomes more complicated.
To keep methods and properties from colliding, some kind of
alpha-renaming of those that are locally scoped must be made. The most
common suggestion is to append the trait name (including namespace) to
the front of the method or property seperated by the double colon (::).
This should be an acceptable solution to the issue. However, every use
of the method or property within the trait, must be changed to this new
name.
Don't Flatten
-------------
Instead of flattening, the traits could be kept separate from the class.
Instead, during runtime, when a call is made to a trait method or
property, the correct action is decided. This will require changes to
the run-time internals of PHP, but could provide some benefit to opcode
caches, as they could cache the trait once and use it for each of the
classes that include it.
Conclusion
==========
An outline of non-breaking traits has been given. The changes build upon
the work on stateful traits, and take it to the next obvious step. The
benefit of this approach over multiple inheritance or mix-ins, is that
the developer of the class has full control over exactly how the traits
will be included into the class.
Only simple code examples have been given, and questions will surely
arise over what will happen if you include traits a certain way. In this
case, more explanation and example code may be required.
Bibliography
============
.. [1] Alexandre Bergel, Stéphane Ducasse, Oscar Nierstrasz and Roel
Wuyts, “Stateful Traits and their Formalization,” Journal of Computer
Languages, Systems and Structures, vol. 34, no. 2-3, 2008, pp.
83-108.
http://www.iam.unibe.ch/~scg/Archive/Papers/Berg07eStatefulTraits.pdf
--
"Joshua Thompson" <[EMAIL PROTECTED]>
<http://www.schmalls.com>
--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php