Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
.. index::
2
    single: Pass-By-Reference Method Parameter Behaviour
3
 
4
Preserving Pass-By-Reference Method Parameter Behaviour
5
=======================================================
6
 
7
PHP Class method may accept parameters by reference. In this case, changes
8
made to the parameter (a reference to the original variable passed to the
9
method) are reflected in the original variable. An example:
10
 
11
.. code-block:: php
12
 
13
    class Foo
14
    {
15
 
16
        public function bar(&$a)
17
        {
18
            $a++;
19
        }
20
 
21
    }
22
 
23
    $baz = 1;
24
    $foo = new Foo;
25
    $foo->bar($baz);
26
 
27
    echo $baz; // will echo the integer 2
28
 
29
In the example above, the variable ``$baz`` is passed by reference to
30
``Foo::bar()`` (notice the ``&`` symbol in front of the parameter?).  Any
31
change ``bar()`` makes to the parameter reference is reflected in the original
32
variable, ``$baz``.
33
 
34
Mockery handles references correctly for all methods where it can analyse
35
the parameter (using ``Reflection``) to see if it is passed by reference. To
36
mock how a reference is manipulated by the class method, we can use a closure
37
argument matcher to manipulate it, i.e. ``\Mockery::on()`` - see the
38
:ref:`argument-validation-complex-argument-validation` chapter.
39
 
40
There is an exception for internal PHP classes where Mockery cannot analyse
41
method parameters using ``Reflection`` (a limitation in PHP). To work around
42
this, we can explicitly declare method parameters for an internal class using
43
``\Mockery\Configuration::setInternalClassMethodParamMap()``.
44
 
45
Here's an example using ``MongoCollection::insert()``. ``MongoCollection`` is
46
an internal class offered by the mongo extension from PECL. Its ``insert()``
47
method accepts an array of data as the first parameter, and an optional
48
options array as the second parameter. The original data array is updated
49
(i.e. when a ``insert()`` pass-by-reference parameter) to include a new
50
``_id`` field. We can mock this behaviour using a configured parameter map (to
51
tell Mockery to expect a pass by reference parameter) and a ``Closure``
52
attached to the expected method parameter to be updated.
53
 
54
Here's a PHPUnit unit test verifying that this pass-by-reference behaviour is
55
preserved:
56
 
57
.. code-block:: php
58
 
59
    public function testCanOverrideExpectedParametersOfInternalPHPClassesToPreserveRefs()
60
    {
61
        \Mockery::getConfiguration()->setInternalClassMethodParamMap(
62
            'MongoCollection',
63
            'insert',
64
            array('&$data', '$options = array()')
65
        );
66
        $m = \Mockery::mock('MongoCollection');
67
        $m->shouldReceive('insert')->with(
68
            \Mockery::on(function(&$data) {
69
                if (!is_array($data)) return false;
70
                $data['_id'] = 123;
71
                return true;
72
            }),
73
            \Mockery::any()
74
        );
75
 
76
        $data = array('a'=>1,'b'=>2);
77
        $m->insert($data);
78
 
79
        $this->assertTrue(isset($data['_id']));
80
        $this->assertEquals(123, $data['_id']);
81
 
82
        \Mockery::resetContainer();
83
    }
84
 
85
Protected Methods
86
-----------------
87
 
88
When dealing with protected methods, and trying to preserve pass by reference
89
behavior for them, a different approach is required.
90
 
91
.. code-block:: php
92
 
93
    class Model
94
    {
95
        public function test(&$data)
96
        {
97
            return $this->doTest($data);
98
        }
99
 
100
        protected function doTest(&$data)
101
        {
102
            $data['something'] = 'wrong';
103
            return $this;
104
        }
105
    }
106
 
107
    class Test extends \PHPUnit\Framework\TestCase
108
    {
109
        public function testModel()
110
        {
111
            $mock = \Mockery::mock('Model[test]')->shouldAllowMockingProtectedMethods();
112
 
113
            $mock->shouldReceive('test')
114
                ->with(\Mockery::on(function(&$data) {
115
                    $data['something'] = 'wrong';
116
                    return true;
117
                }));
118
 
119
            $data = array('foo' => 'bar');
120
 
121
            $mock->test($data);
122
            $this->assertTrue(isset($data['something']));
123
            $this->assertEquals('wrong', $data['something']);
124
        }
125
    }
126
 
127
This is quite an edge case, so we need to change the original code a little bit,
128
by creating a public method that will call our protected method, and then mock
129
that, instead of the protected method. This new public method will act as a
130
proxy to our protected method.