Skip to content

Commit

Permalink
Merge branch 'introduction'
Browse files Browse the repository at this point in the history
* introduction:
  Update demo to show DeclareParents in action #32
  Initial support for Introduction advice #32
  Added TraitIntroductionInfo advice #32
  Fix typo in the class name #32
  • Loading branch information
lisachenko committed Jan 16, 2013
2 parents 6fd2acd + de35295 commit 22a5419
Show file tree
Hide file tree
Showing 13 changed files with 468 additions and 8 deletions.
8 changes: 8 additions & 0 deletions demos/Aspect/DebugAspect.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Go\Lang\Annotation\Before;
use Go\Lang\Annotation\Around;
use Go\Lang\Annotation\Pointcut;
use Go\Lang\Annotation\DeclareParents;

/**
* Debug aspect
Expand All @@ -28,6 +29,13 @@ class DebugAspect implements Aspect
*/
protected $message = '';

/**
* @DeclareParents(value="Example", interface="Serializable", defaultImpl="Aspect\Introduce\SerializableImpl")
*
* @var null
*/
protected $introduction = null;

/**
* Aspect constructor
*
Expand Down
39 changes: 39 additions & 0 deletions demos/Aspect/Introduce/SerializableImpl.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php
/**
* Go! OOP&AOP PHP framework
*
* @copyright Copyright 2012, Lissachenko Alexander <[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/

namespace Aspect\Introduce;

/**
* Example class to test aspects
*/
trait SerializableImpl
{
/**
* String representation of object
* @return string the string representation of the object or null
*/
public function serialize()
{
return serialize(get_object_vars($this));
}

/**
* Constructs the object
* @param string $serialized <p>
* The string representation of the object.
* </p>
* @return mixed the original value unserialized.
*/
public function unserialize($serialized)
{
$data = unserialize($serialized);
foreach($data as $key=>$value) {
$this->$key = $value;
}
}
}
61 changes: 61 additions & 0 deletions src/Go/Aop/Framework/TraitIntroductionInfo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php
/**
* Go! OOP&AOP PHP framework
*
* @copyright Copyright 2013, Lissachenko Alexander <[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/

namespace Go\Aop\Framework;

use Go\Aop\IntroductionInfo;

/**
* @package go
*/
class TraitIntroductionInfo implements IntroductionInfo
{

/**
* Name of the interface to introduce
*
* @var string
*/
private $introducedInterface;

/**
* Name of the class with implementation (trait)
*
* @var string
*/
private $implementationClass;

/**
* Create a DefaultIntroductionAdvisor for the given advice.
*/
public function __construct($interfaceType, $implementationClass)
{
$this->introducedInterface = $interfaceType;
$this->implementationClass = $implementationClass;
}

/**
* Return the additional interfaces introduced by this Advisor or Advice.
*
* @return array|string[] the introduced interfaces
*/
public function getInterfaces()
{
return array($this->introducedInterface);
}

/**
* Return the list of traits with realization of introduced interfaces
*
* @return array|string[] the implementations
*/
public function getTraits()
{
return array($this->implementationClass);
}
}
2 changes: 1 addition & 1 deletion src/Go/Aop/IntroductionAdvisor.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*
* Introduction is the implementation of additional interfaces (not implemented by a target) via AOP advice.
*/
interface IntroductionAdvisor extends Advisor, IntroductionInfo
interface IntroductionAdvisor extends Advisor
{

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,11 @@
/**
* Interface supplying the information necessary to describe an introduction of trait.
*
* IntroductionAdvisors must implement this interface.
*
* If an Advice implements this, it may be used as an introduction without an IntroductionAdvisor.
* In this case, the advice is self-describing, providing not only the necessary behavior,
* but describing the interfaces it introduces.
*/
interface IntroductionInfo
interface IntroductionInfo extends Advice
{

/**
Expand All @@ -26,4 +24,11 @@ interface IntroductionInfo
* @return array|string[] the introduced interfaces
*/
public function getInterfaces();

/**
* Return the list of traits with realization of introduced interfaces
*
* @return array|string[] the implementations
*/
public function getTraits();
}
26 changes: 25 additions & 1 deletion src/Go/Aop/Support/AbstractChildCreator.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ class AbstractChildCreator
*/
protected $interfaces = array();

/**
* List of additional traits for using
*
* @var array
*/
protected $traits = array();

/**
* Source code for properties
*
Expand Down Expand Up @@ -163,6 +170,22 @@ public function addInterface($interface)
$this->interfaces[] = $interfaceName;
}

/**
* Add a trait for child
*
* @param string|ReflectionClass|ParsedReflectionClass $trait
*/
public function addTrait($trait)
{
$traitName = $trait;
if ($trait instanceof ReflectionClass || $trait instanceof ParsedReflectionClass) {
if (!$trait->isTrait()) {
throw new \InvalidArgumentException("Trait expected to add");
}
$traitName = $trait->name;
}
$this->traits[] = $traitName;
}
/**
* Creates a property
*
Expand Down Expand Up @@ -193,12 +216,13 @@ public function __toString()
ksort($this->methodsCode);
ksort($this->propertiesCode);
$prefix = join(' ', Reflection::getModifierNames($this->class->getModifiers()));
$code = sprintf("%s\n%sclass %s extends %s%s\n{\n%s\n%s\n}",
$code = sprintf("%s\n%sclass %s extends %s%s\n{\n%s\n\n%s\n%s\n}",
$this->class->getDocComment(),
$prefix ? "$prefix " : '',
$this->name,
$this->parentClassName,
$this->interfaces ? ' implements ' . join(', ', $this->interfaces) : '',
$this->traits ? $this->indent('use ' . join(', ', $this->traits) .';') : '',
$this->indent(join("\n", $this->propertiesCode)),
$this->indent(join("\n", $this->methodsCode))
);
Expand Down
18 changes: 16 additions & 2 deletions src/Go/Aop/Support/AopChildFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Go\Core\AspectContainer;
use Go\Core\AspectKernel;
use Go\Aop\Advice;
use Go\Aop\IntroductionInfo;
use Go\Aop\Intercept\Joinpoint;
use Go\Aop\Framework\ClassFieldAccess;
use Go\Aop\Framework\ReflectionMethodInvocation;
Expand Down Expand Up @@ -64,7 +65,7 @@ public static function generate($parent, array $advices)

foreach ($advices as $name => $value) {

list ($type, $pointName) = explode(':', $name);
list ($type, $pointName) = explode(':', $name, 2);
switch ($type) {
case AspectContainer::METHOD_PREFIX:
case AspectContainer::STATIC_METHOD_PREFIX:
Expand All @@ -75,8 +76,18 @@ public static function generate($parent, array $advices)
$aopChild->interceptProperty($parent->getProperty($pointName));
break;

case AspectContainer::INTRODUCTION_TRAIT_PREFIX:
/** @var $value IntroductionInfo */
foreach ($value->getInterfaces() as $interface) {
$aopChild->addInterface($interface);
}
foreach ($value->getTraits() as $trait) {
$aopChild->addTrait($trait);
}
break;

default:
throw new \InvalidArgumentException("Unsupported point $pointName");
throw new \InvalidArgumentException("Unsupported point `$type`");
}
}
}
Expand Down Expand Up @@ -145,6 +156,9 @@ protected static function wrapWithJoinPoints($classAdvices, $className)
$joinpoint = new ClassFieldAccess($className, $joinPointName, $advices);
break;

case AspectContainer::INTRODUCTION_TRAIT_PREFIX:
continue;

default:
throw new UnexpectedValueException("Invalid joinpoint `{$joinPointType}` type. Not yet supported.");
}
Expand Down
126 changes: 126 additions & 0 deletions src/Go/Aop/Support/DeclareParentsAdvisor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<?php
/**
* Go! OOP&AOP PHP framework
*
* @copyright Copyright 2012, Lissachenko Alexander <[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/

namespace Go\Aop\Support;

use InvalidArgumentException;
use ReflectionClass;

use Go\Aop\Advice;
use Go\Aop\ClassFilter;
use Go\Aop\IntroductionInfo;
use Go\Aop\IntroductionAdvisor;

/**
* Introduction advisor delegating to the given object.
*/
class DeclareParentsAdvisor implements IntroductionAdvisor
{

/**
* @var null|IntroductionInfo
*/
private $advice = null;

/**
* Type pattern the introduction is restricted to
*
* @var ClassFilter
*/
private $classFilter;

/**
* Create a DefaultIntroductionAdvisor for the given advice.
*/
public function __construct(ClassFilter $classFilter, IntroductionInfo $info)
{
$this->classFilter = $classFilter;
$this->advice = $info;
}

/**
* Can the advised interfaces be implemented by the introduction advice?
*
* Invoked before adding an IntroductionAdvisor.
*
* @return void
* @throws \InvalidArgumentException if the advised interfaces can't be implemented by the introduction advice
*/
public function validateInterfaces()
{
$refInterface = new ReflectionClass(reset($this->advice->getInterfaces()));
$refImplementation = new ReflectionClass(reset($this->advice->getTraits()));
if (!$refInterface->isInterface()) {
throw new \InvalidArgumentException("Only interface can be introduced");
}
if (!$refImplementation->isTrait()) {
throw new \InvalidArgumentException("Only trait can be used as implementation");
}

foreach($refInterface->getMethods() as $interfaceMethod) {
if (!$refImplementation->hasMethod($interfaceMethod->name)) {
throw new \DomainException("Implementation requires method {$interfaceMethod->name}");
}
}
}

/**
* Returns an advice to apply
*
* @return Advice|IntroductionInfo|null
*/
public function getAdvice()
{
return $this->advice;
}

/**
* Return whether this advice is associated with a particular instance or shared with all instances
* of the advised class
*
* @return bool Whether this advice is associated with a particular target instance
*/
public function isPerInstance()
{
return false;
}

/**
* Return the filter determining which target classes this introduction should apply to.
*
* This represents the class part of a pointcut. Note that method matching doesn't make sense to introductions.
*
* @return ClassFilter The class filter
*/
public function getClassFilter()
{
return $this->classFilter;
}

/**
* Set the class filter for advisor
*
* @param ClassFilter $classFilter Filter for classes
*/
public function setClassFilter(ClassFilter $classFilter)
{
$this->classFilter = $classFilter;
}

/**
* Return string representation of object
*
* @return string
*/
public function __toString()
{
$adviceClass = get_class($this->advice);
$interfaceClasses = join(',', $this->advice->getInterfaces());
return get_called_class() . ": advice [{$adviceClass}]; interfaces [{$interfaceClasses}] ";
}
}
Loading

0 comments on commit 22a5419

Please sign in to comment.