2

Likely this has already been asked, but nevertheless, here goes. This may fall under best practice or security... I'm not really sure.

In my application, I am using a nested object, that is called in the __construct() function. Sort of like this:

class user {
    public $userID = NULL;
    public $someObject = NULL;

    public function __construct() {
        $this->userID = getThisUser();
        $this->someObject = new objectBuilder($this->userID);
    }
    public function getThisUser() {
        // ... 
    }
}

class objectBuilder {
    public $buriedVar = NULL;

    public function __construct($uid = NULL) {
        if( !isset($uid) ) {
            $this->buriedVar = setTheObject($uid);
        } else {
            $this->buriedVar = setTheObject(0);
        }
    }
    public function setTheObject($id) {
        // ...
        return "random string";
    }
}

$tom = new user();

Obviously terrible outline here, but the point is, I can then call $tom->someObject->buriedVar and it'll return "random string".

While looking for a way to nest classes, I noticed no one recommends this as a method for storing objects inside of another object. I'm curious of a few things:

1) Is this insecure?
2) Are the vars inside the nested object exclusive to the call made inside $tom->__construct(), or if I create another object using new objectBuilder() is it overwriting the one inside $tom->someObject? I haven't noticed this, but am not sure how to test for that entirely.
3) Is there something else I'm missing? A best practice reason not to instantiate an object inside a class? I've been using it for years and it works great for what I've done. Is it a speed thing?

  • 1
    _"no one recommends this"_ – Phil Dec 29 '17 at 03:48
  • 3
    By making everything `public`, anything can set the value of `$tom->someObject->buriedVar` to something else. You may want to look at the keywords [`private`](http://php.net/manual/en/language.oop5.visibility.php) and [`static`](http://php.net/manual/en/language.oop5.static.php). And also this [question and answer](https://stackoverflow.com/questions/4361553/what-is-the-difference-between-public-private-and-protected) – Tigger Dec 29 '17 at 03:55
  • @Phil Sorry, I don't mean that they recommend against it, I mean that they haven't recommended it at all. As in... they haven't given it as a suggestion to solve the nesting issues people are asking about. I was just curious if there was a reason why it isn't the go to answer as a workaround for not being able to do `class something { class somethingElse { } }` – Clayton Engle Dec 29 '17 at 03:58
  • @Tigger Yeah. That's what I meant by this being a terrible outline, haha. Thanks for pointing that out in case anyone else happens on this post. – Clayton Engle Dec 29 '17 at 04:00
  • I should point out... this whole question is probably very stupid. I'm getting to the point where I'm trying to learn the finer points of PHP, and this is one of those things that is hard to ask Google. There may not be an answer, or it may just be really obvious. Sorry if so. – Clayton Engle Dec 29 '17 at 04:03
  • Most people use *getters* to prevent write access to what should be otherwise immutable properties. See https://stackoverflow.com/questions/2649096/explain-to-me-what-is-a-setter-and-getter – Phil Dec 29 '17 at 04:03
  • @Phil I've spent some time trying to figure out how getters and setters plays into this, and I've got nothing. I don't mean that to be asinine, I'm genuinely unsure of how it's related. Are you saying not to set any of the vars in the class and overload... like... everything? Do you have a link that better explains what you're describing? – Clayton Engle Dec 29 '17 at 04:43
  • No, I mean don't make the properties `public`. PHP does not have `final` class properties like other OOP languages so there's nothing stopping something like `$tom->someObject = $someOtherObject`. I would make the properties `private` / `protected` and provide a getter, eg `public function getSomeObject() { return $this->someObject; }`. Then, other things can use it but not overwrite it, eg `$tom->getSomeObject()->getBuriedVar()` – Phil Dec 29 '17 at 04:47
  • Haha. Oh. So way unrelated to the original. That's cool. (edit: that sounded sarcastic. not my intention. :P) – Clayton Engle Dec 29 '17 at 04:51

1 Answers1

1

1) Is this insecure?

Not inherently, no.

2) Are the vars inside the nested object exclusive to the call made inside $tom->__construct(), or if I create another object using new objectBuilder() is it overwriting the one inside $tom->someObject? I haven't noticed this, but am not sure how to test for that entirely.

This is a fundamental question between class and object. Objects are instances of a class and there can be multiple. The only things that would be overwritten are static properties and methods. You could test it like this:

<?php
$obj1 = new objectBuilder();
$obj2 = new objectBuilder();
if ($obj1 !== $obj2) {
    echo "objects are not the same\n";
}
if ($obj1->buriedVar !== $obj2->buriedVar) {
    echo "nested objects are not the same either\n";
}
$obj3 = new objectBuilder(1);
if ($obj1->buriedVar != $obj3->buriedVar) {
    echo "even the values of two different buried vars with different values are different.\n";
}

if ($obj1->buriedVar == $obj2->buriedVar) {
    echo "counter-example: nested variables with the same values set are similar.\n";
}

It helps to know the difference between equality and identity (see this SO post).

3) Is there something else I'm missing? A best practice reason not to instantiate an object inside a class? I've been using it for years and it works great for what I've done. Is it a speed thing?

You touched on it briefly. What you should know is that this is not scalable and is difficult to test.

Imagine you're creating a website for dogs.

<?php
class Bio
{
    public function __construct()
    {
        $this->dog = new Dog('Terrier');
    }
}

class Dog
{
    private $animal = 'dog';
    private $noise = 'woof!';
    private $breed;

    public function __construct($breed=null)
    {
        $this->setBreed($breed);
    }

    public function setBreed($breed)
    {
        $this->breed = $breed;
    }
}

What if you want to add a new breed? Well... That's easy enough:

class Bio
{
    // ...
    public function __construct($breed)
    {
        $this->dog = new Dog($breed);
    }
    // ...
}

Cool! You've solved everything.

Except...

One day you want to create a section for cats, because one of your best writers also loves cats, and you sense an untapped market.

Uh oh...

You can refactor the code, of course. But you wrote it a long time ago. Now you have to go in and figure out where everything went. No big deal.. A bit annoying but you fixed it!

But now you have another problem. Turns out that the same author wants to add different traits to the breed. You're surprised this hasn't come up sooner but, hey, it's probably a good thing to have.

Now you need to go in to the Dog object, and the Cat object, and add traits.

Every single time.

On. Every. Bio.

After some reconfiguring, you've created something monstrous like this:

$article1 = new Bio('Terrier', 'dog', ['independent']);
$article2 = new Bio('Persian', 'cat', ['flat-faced']);
//... and so on, and so on

The next time the author asks for something, you fire her and then tear your hair out in a mad rage.

Or, from the beginning, you use Dependency Injection.

<?php
class Bio
{
    private $animal;

    public function __construct(AnimalInterface $animal)
    {
        $this->animal = $animal;
    }
}

interface Animal
{
    public function getType();
    public function setBreed($breed);
    public function getBreed();
    public function setTraits(array $traits);
    public function getTraits();
}

abstract class AbstractAnimal implements AnimalInterface
{
    private $breed;
    private $traits = [];

    abstract public function getType();

    public function setBreed($breed)
    {
        $this->breed = $breed;
    }

    public function getBreed()
    {
        return $this->breed;
    }

    public function setTraits(array $traits)
    {
        $this->traits = $traits;
    }

    public function getTraits()
    {
        return (array)$this->traits;
    }
}

class Cat extends AbstractAnimal
{
    public function getType()
    {
        return 'cat';
    }
}

class Dog extends AbstractAnimal
{
    public function getType()
    {
        return 'dog';
    }
}

This pattern requires little to no editing after it has been created.

Why? Because you are injecting the object to nest into the class, rather than instantiating it in the object.

$bio1 = new Bio($dog); $bio2 = new Bio($cat); can always stay like this. Now you just edit the $dog and $cat objects. The added benefit is that these objects can be used anywhere.

But what about utility classes?

(This is where testability comes in. If you haven't worked with unit testing, I recommend reading up on it in the link to PHPUnit below. I'm not going to dwell on how that works as it's off topic).

Dependency Injection is well and good if you have classes that require customization. But what about utility classes that just house various functions?

class Utils
{
    public function add($a, $b)
    {
        return $a + $b;
    }
}

You might think that you can call this function safely from the constructor. And you can. However, one day you might create a log method in your Utils class:

public function log($msg)
{
    exec("cat '$msg' > /tmp/log.txt");
}

This works just fine. However, when you run tests, your /tmp/log.txt file complains. "Invalid permissions!". When this method is run via your website, log.txt needs to be writeable by www-data.

You could just chmod 777 /tmp/log.txt, but that would mean everyone who has access to your server can write to that log. Additionally, you may not want to always write to the same log when you're testing as when you're navigating through the web interface (Personally, I would find it confusing and cluttering).

PHPUnit and other unit testing services allow you to mock various objects. The problem is that you have classes calling Utils directly.

You have to find a way to manually override the constructor. Look at PHPUnit's manual to find out why this maybe isn't ideal.

So if you're not using Dependency Injection, what do you do?

PHPUnit suggests, amongst other fixes, moving this Utils object instantiation to another method and then stubbing/mocking that method in your unit test (I want to emphasize that this is after recommending Dependency Injection).

So the next best?

public function __construct()
{
    $this->init();
}

private function init()
{
    $this->utils = new Utils;
}

Now when you unit test, you can create a fake init method and it will be called as soon as the class is created.

In conclusion, the way you are currently instantiating classes is not scalable or easily testable in many real world situations. While it may be all right in limited situations, it is better to get used to the DI (Dependency Injection) pattern, because it will save you lots of headaches in the future.

smcjones
  • 4,921
  • 1
  • 20
  • 36
  • Thank you SO much. This is precisely what I was asking, but didn't know how to ask. I've reached that point in PHP where I'm not sure of its nuanced differences from other languages I learned in, and things like this I just don't know to ask. You've seen that and answered perfectly. Thank you! – Clayton Engle Dec 29 '17 at 05:10