http://phing.info/

Source Code Coverage

Designed for use with PHPUnit2, Xdebug and Phing.

Methods: 14 LOC: 400 Statements: 185
Legend: executednot executeddead code
Source file Statements Methods Total coverage
Builder.php 87.0% 71.4% 85.9%
   
1
<?php
2
/**
3
 * Xyster Framework
4
 *
5
 * This source file is subject to the new BSD license that is bundled
6
 * with this package in the file LICENSE.txt.
7
 * It is also available through the world-wide-web at this URL:
8
 * http://www.opensource.org/licenses/bsd-license.php
9
 *
10
 * @category  Xyster
11
 * @package   Xyster_Type
12
 * @copyright Copyright LibreWorks, LLC (http://libreworks.net)
13
 * @license   http://www.opensource.org/licenses/bsd-license.php New BSD License
14
 * @version   $Id: Builder.php 418 2010-10-18 21:40:08Z jonathanhawk $
15
 */
16
namespace Xyster\Type\Proxy;
17
use Xyster\Type\Type;
18
/**
19
 * A mediator for setting and getting values from a named field
20
 *
21
 * @category  Xyster
22
 * @package   Xyster_Type
23
 * @copyright Copyright LibreWorks, LLC (http://libreworks.net)
24
 * @license   http://www.opensource.org/licenses/bsd-license.php New BSD License
25
 */
26
class Builder
27
{
28
    protected static $_types = array();
29
    protected static $_cache = true;
30
31
    protected $_parent;
32
    protected $_interfaces;
33
    protected $_handler;
34
    protected $_callConstructor = false;
35
    protected $_className;
36
37
    /**
38
     * Creates a proxy class and returns an instance of it
39
     *
40
     * @param array $args Optional. Arguments to pass to the constructor
41
     * @return \Xyster\Type\Proxy\IProxy the proxy object created
42
     */
43
    public function create( array $args = null )
44
    {
45 1
        $type = $this->createType();
46 1
        array_unshift($args, $this->_handler);
47 1
        return $type->getClass()->newInstanceArgs($args);
48
    }
49
50
    /**
51
     * Creates a proxy class
52
     *
53
     * @return \Xyster\Type\Type
54
     */
55
    public function createType()
56
    {
57 2
        $key = '';
58 2
        if ( self::usesCache() ) {
59 2
            if ( $this->_parent ) {
60 2
                $key .= $this->_parent->getName();
61 2
            }
62 2
            $key .= '|';
63 2
            foreach( $this->_interfaces as $iface ) {
64 2
                $key .= $iface->getName() . '|';
65 2
            }
66 2
            if ( $this->_handler ) {
67 2
                $key .= spl_object_hash($this->_handler) . '|';
68 2
            }
69 2
            $key = ( $this->_callConstructor ) ? 'ccy' : 'ccn';
70 2
            if ( isset(self::$_types[$key]) ) {
71 1
                return self::$_types[$key];
72
            }
73 2
        }
74 2
        eval($this->_getClass());
75 2
        $type = new \Xyster\Type\Type($this->_className);
76 2
        if ( self::usesCache() ) {
77 2
            self::$_types[$key] = $type;
78 2
        }
79 2
        return $type;
80
    }
81
82
    /**
83
     * Sets whether the parent constructor is called (default is false)
84
     *
85
     * @param boolean $flag
86
     * @return \Xyster\Type\Proxy\Builder provides a fluent interface
87
     */
88
    public function setCallParentConstructor( $flag = true )
89
    {
90 2
        $this->_callConstructor = $flag;
91 2
        return $this;
92
    }
93
94
    /**
95
     * Sets the handler used for method callbacks
96
     *
97
     * @param \Xyster\Type\Proxy\IHandler $handler
98
     * @return \Xyster\Type\Proxy\Builder provides a fluent interface
99
     */
100
    public function setHandler( IHandler $handler )
101
    {
102 3
        $this->_handler = $handler;
103 3
        return $this;
104
    }
105
106
    /**
107
     * Sets any interfaces the generated class should implement
108
     *
109
     * @param array $interfaces An array of \Xyster\Type\Type objects
110
     * @return \Xyster\Type\Proxy\Builder provides a fluent interface
111
     * @throws ProxyException if any items in array aren't interfaces
112
     */
113
    public function setInterfaces( array $interfaces )
114
    {
115 4
        foreach( $interfaces as $interface ) {
116 4
            if ( !$interface instanceof Type ||
117 4
                !$interface->isObject() ||
118 4
                !$interface->getClass()->isInterface() ) {
119 1
               throw new ProxyException('Not an interface: ' . $interface);
120
            }
121 3
            $this->_interfaces[] = $interface;
122 3
        }
123 3
        return $this;
124
    }
125
126
    /**
127
     * Sets the parent type the generated class should extend
128
     *
129
     * @param Xyster\Type\Type $parent
130
     * @return \Xyster\Type\Proxy\Builder provides a fluent interface
131
     * @throws ProxyException if the type is invalid as a parent
132
     */
133
    public function setParent( Type $parent )
134
    {
135 3
        if ( !$parent->isObject() || $parent->getClass()->isInterface() || $parent->getClass()->isFinal() ) {
136
            throw new ProxyException('Parent type must be a non-final or abstract class');
137
        }
138 3
        $this->_parent = $parent;
139 3
        return $this;
140
    }
141
142
    /**
143
     * Whether the builder caches the classes it generates
144
     *
145
     * @return boolean
146
     */
147
    public static function usesCache()
148
    {
149 3
        return self::$_cache;
150
    }
151
152
    /**
153
     * Sets whether the builder caches the classes it generates (default true)
154
     *
155
     * @param boolean $flag
156
     */
157
    public static function setCache( $flag = true )
158
    {
159 2
        self::$_cache = $flag;
160
    }
161
162
    /**
163
     * Gets the full class source
164
     *
165
     * @return string
166
     */
167
    private function _getClass()
168
    {
169 2
        $class = $this->_getClassHead() .
170 2
            " private \$_handler;\n" .
171 2
            " private \$_class;\n" .
172 2
            $this->_getConstructor() .
173 2
            " public function getHandler()\n { return \$this->_handler; }\n";
174 2
        $methods = array();
175 2
        foreach( $this->_parent->getClass()->getMethods() as $method ) {
176 2
            if ( !$method->isConstructor() && !$method->isFinal() ) {
177 2
                $methods[$method->getName()] = $this->_getMethodDefinitionFromExisting($method);
178 2
            }
179 2
        }
180 2
        foreach( $this->_interfaces as $interface ) {
181 2
            foreach( $interface->getClass()->getMethods() as $method ) {
182 2
                if ( !$method->isConstructor() ) {
183 2
                    $methods[$method->getName()] = $this->_getMethodDefinitionFromExisting($method);
184 2
                }
185 2
            }
186 2
        }
187 2
        $class .= implode("\n", $methods) . "}\n";
188 2
        return $class;
189
    }
190
191
    /**
192
     * Gets the string for the proxy head declaration
193
     *
194
     * @return string
195
     */
196
    private function _getClassHead()
197
    {
198 2
        $def = 'class ';
199 2
        $extends = '';
200 2
        $className = 'XysterTypeProxy_';
201 2
        if ( $this->_parent ) {
202 2
            $className .= str_replace('\\', '_', $this->_parent->getName()) . '_';
203 2
            $extends = ' extends ' . $this->_parent->getName();
204 2
        }
205 2
        $className .= substr(md5(microtime()), 0, 8);
206 2
        $this->_className = $className;
207 2
        $def .= $className . $extends . ' implements \Xyster\Type\Proxy\IProxy';
208 2
        foreach( $this->_interfaces as $iface ) {
209 2
            $def .= ', ' . $iface->getName();
210 2
        }
211 2
        $def .= " {\n";
212 2
        return $def;
213
    }
214
215
    /**
216
     * Gets the body of the constructor method
217
     *
218
     * @return string
219
     */
220
    private function _getConstructor()
221
    {
222 2
        $def = '';
223
        $body = "\$this->_handler = \$handler;\n" .
224 2
            "\$this->_class = new \ReflectionClass(__CLASS__);\n";
225
        $decl = "%s function __construct(\Xyster\Type\Proxy\IHandler " .
226 2
            "\$handler%s) {\n";
227
228 2
        if ( $this->_parent && $this->_parent->getClass()->getConstructor() ) {
229 2
            $constructor = $this->_parent->getClass()->getConstructor();
230 2
            if ( !$constructor->isFinal() ) {
231 2
                $visibility = 'public';
232 2
                if ($constructor->isPrivate()) {
233
                    $visibility = 'private';
234 2
                } else if ($constructor->isProtected()) {
235
                    $visibility = 'protected';
236
                }
237 2
                $params = '';
238 2
                if ( $this->_callConstructor ) {
239 1
                    $params = $this->_getMethodParameters($constructor);
240 1
                    $paramNames = array();
241 1
                    if ( $params ) {
242 1
                        $params = ', ' . $params;
243 1
                        foreach( $constructor->getParameters() as $param ) {
244 1
                            $paramNames[] = '$' . $param->getName();
245 1
                        }
246 1
                    }
247 1
                    $body .= "parent::__construct(" . implode(',', $paramNames) . ");\n";
248 1
                }
249 2
                $def .= sprintf($decl, $visibility, $params);
250 2
            }
251 2
        } else {
252
            $def .= sprintf($decl, 'public', '');
253
        }
254 2
        $def .= $body;
255 2
        $def .= "}\n";
256 2
        return $def;
257
    }
258
259
    /**
260
     * Gets the method definition from a ReflectionMethod object
261
     *
262
     * @param ReflectionMethod $method
263
     * @return string
264
     */
265
    private function _getMethodDefinitionFromExisting(\ReflectionMethod $method)
266
    {
267
        /*
268
         * This method body was taken from PHPUnit_Framework_MockObject_Mock,
269
         * specifically the 'generateMethodDefinitionFromExisting' method.
270
         * Used under compatible BSD license.
271
         * Copyright (c) 2002-2008, Sebastian Bergmann <sb@sebastian-bergmann.de>
272
         */
273 2
        if ($method->isPrivate()) {
274
            $modifier = 'private';
275 2
        } else if ($method->isProtected()) {
276
            $modifier = 'protected';
277
        } else {
278 2
            $modifier = 'public';
279
        }
280
281 2
        if ($method->isStatic()) {
282
            $modifier .= ' static';
283
        }
284
285 2
        if ($method->returnsReference()) {
286
            $reference = '&';
287
        } else {
288 2
            $reference = '';
289
        }
290
291 2
        return $this->_getMethodDefinition(
292 2
          $method->getDeclaringClass()->getName(),
293 2
          $method->getName(),
294 2
          $modifier,
295 2
          $reference,
296 2
          $this->_getMethodParameters($method)
297 2
        );
298
    }
299
300
    /**
301
     * Gets the actual method definition
302
     *
303
     * @param string $className
304
     * @param string $methodName
305
     * @param string $modifier
306
     * @param string $reference
307
     * @param string $parameters
308
     * @return string
309
     */
310
    private function _getMethodDefinition($className, $methodName, $modifier, $reference = '', $parameters = '')
311
    {
312 2
        $parent = $this->_parent;
313 2
        $parentMethod = 'null';
314 2
        if ( $parent ) {
315 2
            $class = $parent->getClass();
316 2
            if ( $class->hasMethod($methodName) &&
317 2
                !$class->getMethod($methodName)->isAbstract() ) {
318 2
                $parentMethod = 'new \ReflectionMethod("'.$class->getName().'", __FUNCTION__)';
319 2
            }
320 2
        }
321 2
        $handlerSource = '';
322 2
        if ( $this->_handler ) {
323
            $handlerSource =
324
                "        return \$this->_handler->invoke(\$this,\n" .
325 2
                "            \$this->_class->getMethod(__FUNCTION__),\n" .
326 2
                "            \$args,\n" .
327 2
                "            %s);\n";
328 2
        }
329 2
        return sprintf(
330
          "\n    %s function %s%s(%s) {\n" .
331 2
          "        \$args = func_get_args();\n" .
332 2
          $handlerSource .
333 2
          "    }\n",
334 2
          $modifier,
335 2
          $reference,
336 2
          $methodName,
337 2
          $parameters,
338
          $parentMethod
339 2
        );
340
    }
341
342
    /**
343
     * Gets the method parameter declaration
344
     *
345
     * @param ReflectionMethod $method
346
     * @return string
347
     */
348
    private function _getMethodParameters( \ReflectionMethod $method )
349
    {
350
        /*
351
         * This method body was taken from PHPUnit_Framework_MockObject_Mock,
352
         * specifically the 'generateMethodDefinition' method.
353
         * Used under compatible BSD license.
354
         * Copyright (c) 2002-2008, Sebastian Bergmann <sb@sebastian-bergmann.de>
355
         */
356 2
        $parameters = array();
357
358 2
        foreach ($method->getParameters() as $parameter) {
359 2
            $name     = '$' . $parameter->getName();
360 2
            $typeHint = '';
361
362 2
            if ($parameter->isArray()) {
363
                $typeHint = 'array ';
364
            } else {
365
                try {
366 2
                    $class = $parameter->getClass();
367
                }
368
369 2
                catch (ReflectionException $e) {
370
                    $class = FALSE;
371
                }
372
373 2
                if ($class) {
374
                    $typeHint = $class->getName() . ' ';
375
                }
376
            }
377
378 2
            $default = '';
379
380 2
            if ($parameter->isDefaultValueAvailable()) {
381
                $value   = $parameter->getDefaultValue();
382
                $default = ' = ' . var_export($value, TRUE);
383
            }
384
385 2
            else if ($parameter->isOptional()) {
386
                $default = ' = null';
387
            }
388
389 2
            $ref = '';
390
391 2
            if ($parameter->isPassedByReference()) {
392
                $ref = '&';
393
            }
394
395 2
            $parameters[] = $typeHint . $ref . $name . $default;
396 2
        }
397
398 2
        return implode(', ', $parameters);
399
    }
400 1
}


Report generated at 2010-10-18T17:19:48-04:00