DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
The Latest "Software Integration: The Intersection of APIs, Microservices, and Cloud-Based Systems" Trend Report
Get the report
  1. DZone
  2. Coding
  3. Languages
  4. Practical PHP Refactoring: Encapsulate Collection

Practical PHP Refactoring: Encapsulate Collection

Giorgio Sironi user avatar by
Giorgio Sironi
·
Sep. 14, 11 · Interview
Like (0)
Save
Tweet
Share
7.52K Views

Join the DZone community and get the full member experience.

Join For Free

In the scenario of today, a method returns an array (or a collection object) kept as a field on the object, or allows it to be set with a brand new instance.

This refactoring, Encapsulate Collection, favors encapsulation over exposing a primitive type: it substitutes the accessor/mutator couple with more specific methods, for example add() and remove() for changing the contents of the collection on element at the time.

In the PHP case, collections are mostly arrays, but also specialized SPL classes like ArrayObject or SplDoublyLinkedList and its derivatives; other infrastructure classes like implementations of Doctrine\Common\Collections\Collection are still considered collections by this refactoring.

Why hiding a collection?

Particularly in the case of SPL or vendor classes, the Api of the collection objects is really wide: they have dozens of methods. The refactoring hides many of these methods by keeping the object as a private field, and exposes only the necessary operations as public methods on the containing object (this should remind you of something). The only objects you can consider as already encapsulated are first-class collections that you have defined, and that do not expose unused methods like the library ones.

Another issue with exposing collection objects is the aliasing problem: when a collection object is returned, it can be subsequently modified by client code that maintains a reference to it. Only an array is safe in this case, being passed by value. The refactoring avoids this potential action at a distance by returning an array or a copy of the original object.

Steps

  1. Introduce new methods, such as add() and remove(). Each should take an element of the collection as argument.
  2. Valorize the collection field with an empty collection object if it's not already initialized.
  3. In the client code, replace calls to the setter method with code calling add() or remove().
  4. Still in the client code, replace calls to the getter method that then modify the collection with add() and remove().
  5. Modify the getter: deleting it is not necessary, but the method should return a read-only copy of the collection. A quick way to obtain this semantics is to expose an array copy (ArrayObject::getArrayCopy() or Collection::toArray()) or a Traversable object (ArrayObject::getIterator()).

Potentially, you can go on in moving client code that uses the getter inside the class itself, to further encapsulate the collection.

You can add further methods like clear() to delete all elements or removeByPosition($elementNumber); the point is to provide finer-grained operations which describe the collection's available operations instead of a catch-all setter/getter which will always push logic away in the client code.

Example

In the initial state, there are a setter and a getter on the containing object. We use an ArrayObject to execute this refactoring over an object instead of an array(), which is a primitive type passed by value; the procedure is still valid for Dotrine collections or other generic collection objects taken from libraries, or implemented on your own.

<?php
class EncapsulateCollection extends PHPUnit_Framework_TestCase
{
    public function testCollectionCanBePopulatedAndInspected()
    {
        $user = new User();
        $groups = new ArrayObject(array(
            new Group('sysadmins'),
            new Group('developers'),
            new Group('economists')
        ));
        $user->setGroups($groups);
        $this->assertEquals(3, count($user->getGroups()));
    }
}

class User
{
    private $groups;

    public function setGroups(ArrayObject $groups)
    {
        $this->groups = $groups;
    }

    public function getGroups()
    {
        return $this->groups;
    }
}

/**
 * A simple Value Object in this example, little more than a string.
 */
class Group
{
    private $name;

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

We introduced add(), the only method needed to implement the client code (our test). You should not add more methods than actually needed, throwing away encapsulation in the process.

In this step we also initialize the field with an empty instance.

class User
{
    private $groups;

    public function __construct()
    {
        $this->groups = new ArrayObject();
    }

    public function addGroup(Group $group)
    {
        $this->groups->append($group);
    }

    public function setGroups(ArrayObject $groups)
    {
        $this->groups = $groups;
    }

    public function getGroups()
    {
        return $this->groups;
    }
}

Now we replace the setter usage with calls to add(). The setter can then be removed.

<?php
class EncapsulateCollection extends PHPUnit_Framework_TestCase
{
    public function testCollectionCanBePopulatedAndInspected()
    {
        $user = new User();
        $user->addGroup(new Group('sysadmins'));
        $user->addGroup(new Group('developers'));
        $user->addGroup(new Group('economists'));
        $this->assertEquals(3, count($user->getGroups()));
    }
}

class User
{
    private $groups;

    public function __construct()
    {
        $this->groups = new ArrayObject();
    }

    public function addGroup(Group $group)
    {
        $this->groups->append($group);
    }

    public function getGroups()
    {
        return $this->groups;
    }
}

Finally, we change also the getter semantics by forcing it to return an array copy (primitive type) instead of an object that can be used to modify the collection.

class User
{
    private $groups;

    public function __construct()
    {
        $this->groups = new ArrayObject();
    }

    public function addGroup(Group $group)
    {
        $this->groups->append($group);
    }

    /**
     * @return array
     */
    public function getGroups()
    {
        return $this->groups->getArrayCopy();
    }
}

We could have returned

new ArrayObject($this->groups->getArrayCopy());

in case we needed to maintain an object as output.

PHP Object (computer science)

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Microservices Testing
  • 11 Observability Tools You Should Know
  • Choosing the Right Framework for Your Project
  • 5 Steps for Getting Started in Deep Learning

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: