Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP Beginner OOP Building an object

Tags:

php

Thanks in advance for help and direction. I am finally making the switch from linear programming to OOP. I am working on my first class ever and I could use a little direction. My first class is a gallery with the following properties

class Gallery
    {
        //Gallery Name
        public $galleryID;
        public $galleryName;

        //Client Name
        public $clientName;

        //Gallery Options
        public $bg_color;
        public $albumAgreement;
        public $maxChanges;
        public $sharing_on;

        //Revisions
        public $revisions;
}

My out put thus looks like:

Gallery Object
(
    [galleryID] => 
    [galleryName] => 
    [clientName] => 
    [bg_color] => 
    [albumAgreement] => 
    [maxChanges] => 
    [sharing_on] => 
    [revisions] => 
)

My next step is I would like to make 'revisions' an object as well so that my output would look like

Gallery Object
(
    [galleryID] => 
    [galleryName] => 
    [clientName] => 
    [bg_color] => 
    [albumAgreement] => 
    [maxChanges] => 
    [sharing_on] => 
    [revisions] => Revisions Object (
        [revisionID] =>
        [revisionName] =>
    )
)

What direction do I go for something like this and what might the class look like?

Thanks

like image 752
SuperNinja Avatar asked Nov 27 '22 05:11

SuperNinja


1 Answers

This is more of a long-form comment, since it explains the origin of your dilemma, but provides no solutions.

OOP Lesson 1: Just because you're using classes doesn't mean you're writing object-oriented code.

There is rarely a good use-case for a public property in your objects. Let's look at the OP's example:

class Gallery {
    public $galleryID;
    public $galleryName;
    // ...
}

Having defined our properties as public, how do the following two snippets of code differ?

$gallery = new Gallery;
$gallery->galleryId = 42;
$gallery->galleryName = 'some name';

// vs:

$gallery = array(
    'galleryId' => 42,
    'galleryName' => 'some name'
);

If you said, "they aren't really different at all," then you'd be correct. In fact, the object-based code will be slower because of the instantiation overhead involved with new. There are some other factors like the ability to pass around references to an object instead of copying a new array, but those don't affect this particular situation.

OOP Lesson 2: Objects are a black box

The problem with creating an object that's nothing more than a collection of mutable properties is that the rest of your code has full knowledge of what happens inside that object. Let's talk about why this is bad ...

Humans just just aren't very good when it comes to complexity. Good software aims to minimize complexity in part by encapsulating functionality into discrete units. In this case, we want to encapsulate all the logic of a "gallery" entity into the Gallery class. This makes sense as part of a domain-driven design (DDD) approach. What we want to do is wall off the Gallery from the outside world; we want its internal implementation to be opaque to the rest of our code. The rest of our application shouldn't know or care how the Gallery functions, just that it works as expected. The added benefit here is that we can focus on making the gallery work how it's supposed to and then forget about it. We aren't forced to remember how Gallery works to work with an Image or a Revision. This loose-coupling is one of the most powerful tools in OO design.

While it might work on very small scales, it's impossible to keep the logic of an entire application in your head at the same time. It doesn't matter how smart you are, our brains just don't have enough RAM.

Moving back to the code, if our application code knows how the Gallery assigns itself a name then we've already allowed the logic of "gallery-ness" to leak out into the rest of the program. What happens when we decide that we want to verify new gallery names when they're assigned? We must now put that validation logic everywhere in our code where we've specified gallery names because we haven't walled off everything about the abstract concept of "gallery-ness." A much better design would be to encapsulate the assignment of Gallery properties within the object itself:

class Gallery {
    private $galleryId;
    private $name;
    public function setName($name) {
        $this->name = $name;
    }
    public function getName($name) {
        return $this->name;
    }
}

If we structure our class in this manner, we always have a single point of entry when we need to assign a name to a gallery. Now, when our requirements for a gallery change down the road (and they will), all of our application code -- which is blind to the logic behind gallery name assignment -- is isolated from breakage. We simply add a new method to our name setter and create minimal upheaval in our program:

class Gallery {
    private $galleryId;
    private $name;
    public function setName($name) {
        $this->validateName($name);
        $this->name = $name;
    }
    private function validateName($name) {
        if (!preg_match('/^[a-z]+$/', $name)) {
            throw new Exception;
        }
    }
    public function getName($name) {
        return $this->name;
    }
}

Addressing the OP

To answer the question of how to represent an encapsulated Revision object as a property of the higher-level Gallery instance we need a bit of context. It appears that what the OP is trying to do is model domain entities that will be written to and retrieved from a backend persistence layer (such as a database, flat text file, etc).

Anemic domain models are one way to handle this, however it's generally considered an anti-pattern. Martin Fowler writes:

The basic symptom of an Anemic Domain Model is that at first blush it looks like the real thing. There are objects, many named after the nouns in the domain space, and these objects are connected with the rich relationships and structure that true domain models have. The catch comes when you look at the behavior, and you realize that there is hardly any behavior on these objects, making them little more than bags of getters and setters. Indeed often these models come with design rules that say that you are not to put any domain logic in the the domain objects. Instead there are a set of service objects which capture all the domain logic. These services live on top of the domain model and use the domain model for data.

With those arguments in mind, you should consider using something like the DataMapper or Gateway pattern to work with domain objects that need to be persisted to some form of backend storage.

Alternatives

Let's forget about the Revision object for a minute and imagine that we want to use a Slideshow object to output the images from a gallery. This class might look like:

class Slideshow {
    private $gallery;
    public function __construct(Gallery $gallery) {
        $this->gallery = $gallery;
    }
    public function play() {
        // do something with the gallery here
    }
}

Ignore the fact that php code wouldn't actually be used to "play" a slideshow as that's something that would happen in client-side code. The important thing here is that the Slideshow is using Composition to access the Gallery. This construction is vastly superior to directly newing a Gallery inside the Slideshow because:

  1. The Slideshow is now pluggable -- we can insert any object that follows the concepts of "gallery-ness" (usually Gallery will be declared to conform to a specified interface contract).

  2. The Slideshow is now imminently testable. How do we handle a situation in which the Gallery provided doesn't have appropriate image types? If we're directly instantiating a Gallery inside a Slideshow we have no way to simulate such conditions. By injecting the dependencies of the Slideshow we allow ourselves the opportunity to test our code's ability to handle different operational conditions.

Of course, sometimes it's appropriate to directly instantiate an object inside of another class. For more guidance on this topic I'd suggest the advice of Miško Hevery in his article To "new" or not to "new".

like image 178
rdlowrey Avatar answered Dec 12 '22 15:12

rdlowrey