0

Short question: Did the behavior of $this in a scope change from PHP 5.3.29 to 5.5.24? I couldn't find any relevant changes in the PHP 5 Changelog.

Details: In this question, I thought I had a solution for my problem (In PHPUnit, expect method call with array as argument). This was my solution

public function test_actionUpload_v10MasterdataFile()
{
    /*
     * Create a stub to disable the original constructor.
     * Exposing data and rendering are stubbed.
     * All other methods behave exactly the same as in the real Controller.
     */
    $sut = $this->getMockBuilder('MasterdataController')
                ->setMethods(array('exposeAndSaveDataLines', 'render'))
                ->disableOriginalConstructor()
                ->getMock();

    $sut->expects($this->once())
        ->method('exposeAndSaveDataLines')
        ->will($this->returnCallback(function($lines) {
            $expectedLines = include ($this->dataDir() . 'ExpectedLines.php');
            PHPUnit_Framework_Assert::assertTrue($this->similar_arrays($lines, $expectedLines));
        }));

    // Execute the test
    $sut->actionUpload();
}

It is working on my local environment (PHP 5.5.24, Zend Engine v2.5.0), but when I copy the code to our test server (PHP 5.3.29, Zend Engine v2.3.0), this does not work, due to this line:

$expectedLines = include ($this->dataDir() . 'ExpectedLines.php');

The error is:

Using $this when not in object context

Could this be due to PHP version, or should I look somewhere else for the reason why it fails on one server, but not on another?

Community
  • 1
  • 1
physicalattraction
  • 6,485
  • 10
  • 63
  • 122
  • you're using `$this` inside a closure, which obviously will not inherit any variables from the calling scope. – Marc B Aug 18 '15 at 14:17

2 Answers2

2

Yes, there is an important difference introduced in 5.4:

  • Added closure $this support back.

Meaning, in 5.3, $this inside an anonymous function did not refer to anything, the surrounding context was not being preserved. 5.4 added support for this (back), so $this inside an anonymous function refers to the $this from the surrounding context. The workaround before was:

$_this = $this;
function () use ($_this) {
    $_this->foo();
};
deceze
  • 510,633
  • 85
  • 743
  • 889
1

The reason is because you have changed scopes at that point. I'm not sure what changed either, but your code should not work, at least the way PHP scope rules are defined. Below is your code around the issue:

$sut->expects($this->once())
    ->method('exposeAndSaveDataLines')
    ->will($this->returnCallback(function($lines) {
        $expectedLines = include ($this->dataDir() . 'ExpectedLines.php');
        PHPUnit_Framework_Assert::assertTrue($this->similar_arrays($lines, $expectedLines));
    }));

Inside the returnCallback function, you're still technically 'inside' the class, but you need to import variables from the outside scope (this behavior in PHP is different from other languages such as Javascript which do not require this). This is how it should work:

$self = $this; // Not necessary after PHP 5.4 where you can just use($this)
$sut->expects($this->once())
        ->method('exposeAndSaveDataLines')
        ->will($this->returnCallback(function($lines) use($self) {
            $expectedLines = include ($self->dataDir() . 'ExpectedLines.php');
            PHPUnit_Framework_Assert::assertTrue($self->similar_arrays($lines, $expectedLines));
        }));
Jeff Lambert
  • 24,395
  • 4
  • 69
  • 96