bruchowski
bruchowski

Reputation: 5351

Accessing outer variable in PHP 7 anonymous class

PHP 7 added support for anonymous classes, however I can't seem to find any information regarding associated scoping issues. I know I can use the use keyword with callables/closures to access outer scoped variables (like function() use ($outer) { // do work with $outer }), is there any way to do that with an anonymous class?

I would like to be able to do so without relying on the anonymous class constructor arguments, and without doing things like adding a setter method or public property to store the value after the instantiation.

Here's an example:

$outer = 'something';

$instance = new class {
    public function testing() {
        var_dump($outer); // would like this to dump the string 'something'
    }
};

Upvotes: 60

Views: 14471

Answers (6)

Ahmed Mazher
Ahmed Mazher

Reputation: 333

Inspired by @ben lebowski @steve answer Use abstract class to handle the constructor part and when ever need just extend it

Example

<?php

$mv=new MyVar();
$cmd = $mv->getFun('use data')->excute('optional');
class MyVar{
    function getFun($out){
        
        return new class ($out,"other data") extends WithData{
            public function excute(...$args){
            var_dump($args);
            var_dump($this->dataList);
            }
        };
    }
}
abstract class WithData{
    protected mixed $dataList;
    public function __construct(...$dataList){
        $this->dataList=$dataList;
    }
}

?>

Upvotes: 0

Ankology
Ankology

Reputation: 180

The unique way to access outside variable in this case is use $ _GLOBAL (I don't recommend). If you do not want to use constructor or setter method, my suggestion is to use a STATIC variable inside the anonymous class and set the value after the attribuition to the variable that contains the instance of anonymous class (Its not possible to define the static value before, because the class is anonymous..). Doing this, you have a better control and a static variable, but in certain way this is not very usual, every time when you create a new anonymous class the instance and it values belongs to the VARIABLE that receives the "new object", maybe is better for you to create a real class.. But follow a example with a static value and a anonymous class:

$i = new class {

    public static $foo;
};

var_dump($i::$foo); //No value

$i::$foo = "Some value";

var_dump($i::$foo); //Has value

Upvotes: 10

Luna
Luna

Reputation: 2322

http://php.net/manual/en/language.variables.scope.php

There are some instructions in the php variable scope documentation.

This script will not produce any output because the echo statement refers to a local version of the $a variable, and it has not been assigned a value within this scope. You may notice that this is a little bit different from the C language in that global variables in C are automatically available to functions unless specifically overridden by a local definition. This can cause some problems in that people may inadvertently change a global variable. In PHP global variables must be declared global inside a function if they are going to be used in that function.

In php, the scope that a method inside a class can access is restricted to the inside of the entire class and cannot be accessed up to other scopes. So I think that the effect you want is not implemented in php, at least until the PHP GROUP decides to change the default behavior of PHP.

Of course, you can still use it by declaring variables as global.

Upvotes: 2

kmuenkel
kmuenkel

Reputation: 2789

If you want your anonymous class to have access to outer properties and methods that are protected or private, you could take advantage of the fact that closures inherit the scope they're defined in, and tie them into some magic methods for seamless behavior.

This is untested, but I'm pretty sure it'd work:

$class = new class {
    /**
     * @var \Closure
     */
    public static $outerScopeCall;

    /**
     * @var \Closure
     */
    public static $outerScopeGet;

    /**
     * @param string $name
     * @param array $arguments
     * @return mixed
     */
    public function __call(string $name, array $arguments = [])
    {
        $closure = static::$outerScopeCall;
        return $closure($name, $arguments);
    }

    /**
     * @param string $name
     * @param array $arguments
     * @return mixed
     */
    public function __get(string $name)
    {
        $closure = static::$outerScopeGet;
        return $closure($name);
    }
};

$class::$outerScopeCall = function (string $name, array $arguments = []) {
    return $this->$name(...$arguments);
};

$class::$outerScopeGet = function (string $name) {
    return $this->$name;
};

The $call closure will have access to the definition of $this from where it's defined as opposed to where

Upvotes: 0

Didier Breedt
Didier Breedt

Reputation: 571

Even though the OP did state that they would like to avoid public properties and anonymous class constructor arguments, the accepted answer is exactly that, so here is an example using a public property, which can be improved with a private property and a setter to maintain encapsulation:

class Foo {
    public function executionMethod() {
        return "Internal Logic";
    }
}

$foo = new Foo();
var_dump("Foo's execution method returns: " . $foo->executionMethod());

$bar = new class extends Foo {
    public $barVal;
    public function executionMethod() {
        return $this->barVal;
    }
};
$bar->barVal = "External Logic";
var_dump("Bar's execution method returns: " . $bar->executionMethod());

I find this useful if you are unable to override the inherited class's constructor.

This will output:

string(46) "Foo's execution method returns: Internal Logic"
string(46) "Bar's execution method returns: External Logic"

Upvotes: 0

ben lebowski
ben lebowski

Reputation: 684

another solution could be

$outer = 'something';

$instance = new class($outer) {

    private $outer;

    public function __construct($outer) {
        $this->outer = $outer
    }

    public function testing() {
        var_dump($this->outer); 
    }
};

Upvotes: 66

Related Questions