2

In the given PHP class below, I understand that $test will not be accessible from outside this class.

class Resource {
    private $test;
    public function __construct() {
       echo "in init";
       $test = "new val";
    }
}

But we will be able to define, new instance variables as below. Is there a trick to stop this?

$r = new Resource();
$r->val = "value"; 
3
  • 1
    Yes, throw an exception in the magic method __set. Commented May 21, 2018 at 12:18
  • The variable named $test in line 2 is not the same as the one in line 5. What has the class definition got to do with the issue you subsequently describe? Commented May 21, 2018 at 12:24
  • understood symcbean. missed the $this... Commented May 21, 2018 at 12:40

3 Answers 3

1

Using magic methods (namly __set) you can tell the class "if this is not set in here, ignore it", for instance;

<?php

class Resource {
    private $test;
    public function __construct() {
       echo "in init";
       $this->test = "new val";
    }

    public function __set($name, $val)
    {
        // If this variable is in the class, we want to be able to set it
        if (isset($this->{$name})
        {
            $this->{$name} = $val;
        }
        else
        {
            // Do nothing, add an error, anything really
        }
    }
}

$resource = new Resource();
$resource->foo = "bar";
echo $resource->foo; // Will print nothing

For reference, please see the guide

Sign up to request clarification or add additional context in comments.

5 Comments

But if you make it like this, you are able to change private variables, so theres no more private variables, only public vars.
Yes, that is true so +1, however, it does answer the OP's question, it wasn't regarding variable security, merely, making it so you cannot create non-class variables
Sam it works if the variable is set (to a value other than NULL). But not if its declared and not set. gist.github.com/tuxaanand/7edc4cfa88ca74948a506423d5f40a58
@Aanand, you do not want that isset conditional there at all. In your code you block creation/writes to undeclared properties, but you open a backdoor to writing to not null private and protected attributes.
@Progrock, I refer to my prior statement - this is merely about variable accessing, not about variable security
1

Quite safer solution. You should avoid doing those __set methods, because they dont care about private/protected properties. Use class reflection, to see, if property is public and accessible for __set. Small example bellow.

    <?php

class Resource {
    private $test = 55;
    public $foo;
    public $bar;

    public function __set($name, $value)
    {
        if(isset($this->{$name})) {
            $reflection = new ReflectionObject($this);
            $properties = $reflection->getProperties(ReflectionProperty::IS_PUBLIC);
            $isPublic = false;
            /** @var ReflectionProperty $property */
            foreach ($properties as $property) {
                if ($property->getName() == $name) {
                    $isPublic = true;
                    break;
                }
            }
            if ($isPublic) {
                $this->{$name} = $value;
            } else {
                //is private, do not set
                echo 'Im private, dont touch me!';
            }
        } else {
            // not here
            echo 'Im not here';
        }
    }
}

$resource = new Resource();
$resource->test = 45;

4 Comments

The thing is, __set is called when trying to write data to inaccessible properties. If you have a public property, it's accessible. So looping through public properties looking for a name match is pointless.
@Progrock wrong, __set is called also when the variable doesnt exist, it takes care about dynamic variables too.
if the variable doesn't exist, then the property doesn't exist. So what's the point of looking for a public property that isn't there?
See here: 3v4l.org/XYBKm (inaccessible also equates to variables that are not there).
-1

The 'magic method' __set (if declared) is called when writing data to inaccessible properties.

You can use this to your advantage to prevent the creation of undeclared properties:

<?php
class Foo
{
    public  $bar;
    private $baz;

    public function __set($name, $value)
    {
        throw new Exception('Strictly no writes to inaccesible and undeclared properties.');
    }
}

$f = new Foo;
$f->bar = 'hello';
$f->qux = 'earth';

Output:

Fatal error: Uncaught Exception: Strictly no writes to inaccesible and undeclared properties. in /var/www/stackoverflow/so-tmp-f2.php:20 Stack trace: #0 /var/www/stackoverflow/so-tmp-f2.php(26): Foo->__set('qux', 'earth') #`1 {main} thrown in /var/www/stackoverflow/so-tmp-f2.php on line 20

As you can see, the above will throw an exception when trying to write to an undeclared property.

If you try to write to another inaccessible property (in a similar scope) like Foo::baz above (which is declared private), calls will be routed through the __set magic method also. Without the __set method this will result in a Fatal Error.

However, a write to Foo::bar (declared public above) will NOT be routed through the __set magic method.

If you want to enforce this type of strict behaviour in many classes you could implement the above as a trait:

trait Upsetter
{
    public function __set($name, $value)
    {
        throw new Exception('Strictly no writes to inaccesible and undeclared properties.');
    }
}

class Bob
{
    use Upsetter;
}

7 Comments

I'd argue this is not ideal as OP may have other variables which may need to be set from outside the class
@SamSwift웃, you mean access to private parts? I'd say that's a good thing.
I mean any other variable, there could be more private or protected (can't access anyway), but also public variables, if there is only private, that's fine, but logic would dictate there could be instances of "outside interference on public variables"
@SamSwift웃 publically declared variables are still mutable externally with this setup.
OP and future readers would have to account for this - better to be "future proofed" :)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.