I am a programmer and architect (the kind that writes code) with a focus on testing and open source; I maintain the PHPUnit_Selenium project. I believe programming is one of the hardest and most beautiful jobs in the world. Giorgio is a DZone MVB and is not an employee of DZone and has posted 635 posts at DZone. You can read more from them at their website. View Full User Profile

How to remove getters and setters

02.22.2011
| 11551 views |
  • submit to reddit

Getters and setters are one of the first abstraction step that is thought over public fields in object-oriented programming. However, the paradigm was never about encapsulating properties by providing a special reading/writing mechanism via methods, but about objects responding to messages.

In other words, encapsulation is (not only, but also) about being capable of changing private fields. If you write getters and setters, you introduce a leaky abstraction over private fields, since the names and number of your public methods are influenced coupled to them. They aren't really private anymore: for example in service classes I pass dependencies for private methods in the constructor, and the client code is never affected when these dependencies change. With getters and setters, a field addition, removal or renaming affects the contract of the class.

In this article, we're going to take this User Entity class and explore the many ways we have for removing getters and setters. The choice between them depends mainly on what you need them for: it's the client code that should decide what contracts wants from these classes.

<?php
class User
{
private $nickname;
private $password;
private $location;
private $favoriteFood;
private $active;
private $activationKey;

public function setNickname($nickname)
{
$this->nickname = $nickname;
}

public function getNickname()
{
return $nickname;
}

public function setPassword($password)
{
$this->password = $password;
}

public function getPassword()
{
return $this->password;
}

// ... you get the picture: other 8 getters or setters
}

The various techniques are ordered by complexity. As always, I express use cases for the User class via a test case. All the code is on Github.

Constructor

First, we can pass some fields in the constructor. If we do not expose the field then, the value will be immutable and the client code will never even know that this value exists.

For example, our User has an immutable nickname, which serves also as a primary key:

    public function testPassWriteOnlyDataInTheConstructor()
{
$user = new User('giorgiosironi');
// should not explode
//removed the setter as it cannot be changed
}

class User
{
public function __construct($nickname, $activationKey = '')
{
$this->nickname = $nickname;
$this->activationKey = $activationKey;
}
}

Information Expert

Next, we have the Information Expert pattern: assign an operation to the object with the greatest knowledge for accomplishing it. If you do so, you won't need to expose private fields via getters and setters, since you will model some behavior as code in that class, which can see private fields.

For example, when we register an User we want to activate it via email verification, so we send an activation key via mail. But to check it, we don't need to extract the value from the User object.

    // Information Expert
/**
* @expectedException InvalidArgumentException
*/
public function testAnUserIsActivated()
{
$user = new User('giorgiosironi', 'ABC');
$user->activate('AB');
}

class User
{
public function activate($key)
{
if ($this->activationKey === $key) {
$this->active = true;
return;
}
throw new InvalidArgumentException('Key for activation is incorrect.');
}
}

This style is an example of the Tell, Don't Ask principle: we tell our User to do something instead of asking it for information and doing it ourselves.

Double Dispatch

Putting behavior inside an Entity class is nice, but sometimes the operation needs some external dependency to work, like a login mechanism needing a storage for the identity of the user (usually the session). We have two types of coupling to solve here:

  • static: the User class should not depend on any other infrastructure class, which in turn refers the database or the session storage.
  • runtime: the User class cannot hold a reference to an infrastructure object, for instance because we want to serialize it or to create it simply with a new operator, or our ORM does not support injection of collaborators.

The first issues is solved by introducing an interface implemented by the infrastructure class; the second by passing in the dependency via Double Dispatch instead of via the constructor like we do with service classes.

    public function testUsersLogin()
{
$user = new User('giorgiosironi');
$user->setPassword('gs'); // will be removed in next tests
// in reality, we would use a SessionLoginAdapter or something like that
$loginAdapterMock = $this->getMock('LoginAdapter');
$loginAdapterMock->expects($this->once())
->method('storeIdentity')
->with('giorgiosironi');

$user->login('gs', $loginAdapterMock);
}

class User
{
public function login($password, LoginAdapter $loginAdapter)
{
if ($this->password == $password) {
$loginAdapter->storeIdentity($this->nickname);
return true;
} else {
return false;
}
}
}

Still an example of Tell, Don't Ask, but more real world now.

Command and changesets

You really have to put data inside this object: the user has just compiled a form and you have to get thos input values in. So how do we define that operation? As an atomic method call, by passing in what I call a Changeset but it's a specialization of a Command (not Command pattern but CommandQueryResponsibilitySegregation). In the simplest cases, it's just a Value Object or a Data Transfer Object with no behavior.

    public function testCommandForChangingPassword()
{
$user = new User('giorgiosironi');
$passwordChange = new ChangeUserPassword('', 'gs');
$user->handle($passwordChange);
$this->assertEquals('gs', $user->getPassword()); //deprecated, will be removed in next tests
}

class User
{
public function handle($command)
{
if ($command instanceof ChangeUserPassword) {
$this->handleChangeUserPassword($command);
}
if ($command instanceof SetUserDetails) {
$this->handleSetUserDetails($command);
}
// support other commands here...
}

private function handleChangeUserPassword(ChangeUserPassword $command)
{
if ($command->getOldPassword() == $this->password) {
$this->password = $command->getNewPassword();
} else {
throw new Exception('The old password is not correct.');
}
}
}

Think about it: you will have to put these getters and setters somewhere; it's best to put them on an object which is a data structure than that on your Entity. This way:

  • you will be coupled to the current fields only on this particular operation and not when you pass an User around.
  • you will make it clear that you support only a full update operation, and it's not ok to call an isolate setter.

Actually in PHP you could just use an array as a Changeset, but a class provides a stricter contract. Also public fields are not viable for a contract as no error will be raised by PHP if you assign a non-existent field on a Changeset object.

Rendering on a canvas

In the Growing Object-Oriented Software mailing list, there has been recently a discussion on how to emulate getters via callbacks. This solution is the specular of our Changeset argument used for extracting data instead of updating it.

    public function testCanvasForRenderingAnObject()
{
$user = new User('giorgiosironi');
$detailsSet = new SetUserDetails('Italy', 'Pizza'); //THIS may have set/get
$user->handle($detailsSet);
// canvas can also be a form, or xml, or json...
$canvas = new HtmlCanvas('<p>{{location}}</p><p>{{favoriteFood}}</p>');
$user->render($canvas);
$this->assertEquals('<p>Italy</p><p>Pizza</p>', (string) $canvas);
}

class User
{
public function render(Canvas $canvas)
{
$canvas->nickname = $this->nickname;
$canvas->location = $this->location;
$canvas->favoriteFood = $this->favoriteFood;
}
}

Again, the canvas is hidden behind an interface and can be anything: an HTML view, a form, a JSON or RSS feed generator...

CQRS

In Command-Query Responsibility Segregation, you use the ORM for mapping your objects in the database, and fill your report screens by querying it directly, or even by querying another store which is continuously rebuilt from your master database.

I don't know of any implementations of CQRS in PHP, but this mechanism promises to at least eliminate getters, as your domain objects will be write-only.

Conclusion

The full code is in this Github repository, as always.

You have no excuses now: go and ditch one of your getters and setters immediately. Your code will breath a bit of fresh air.

Published at DZone with permission of Giorgio Sironi, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Wojciech Soczyński replied on Tue, 2011/02/22 - 9:28am

I think that your login example is not a good one. Why ? How can a user authenticate himself ? It's very little real-world (domain driven) for me. I would rather do this that way:

class Guardian {

private $_user;

public function currentUser(){
return $this->_user;
}

public function authenticate($username, $password){
$aCredentials = array(
'username' => $username,
'password' => $password,
);
$mUser = $this->_repository->find($aCredentials);
if(empty($mUser)){
return false;
}
$this->_user = $mUser;
return true;

}

}

 In this fashion, you don't need any getters or setters at all ;). Btw. the CQRS concept itself looks very promising, it's only very inconvenient that most of examples are in C#.

Giorgio Sironi replied on Tue, 2011/02/22 - 1:24pm in response to: Wojciech Soczyński

I chose a functional example which would demonstrate how you can do double dispatch in an Entity, like User. He cannot authenticate himself unless you pass in a LoginAdapter. :) The same goes for a CreditCard that can charge you if passed a PointOfSale, and so on.

Keith Pope replied on Thu, 2011/02/24 - 1:07pm

Nice article, I have been researching CQRS for a while now, I created a very simple example here: https://gist.github.com/562799 Also I did start trying to port NCQRS to PHP but have abandoned it for now https://github.com/muteor/phpcqrs, will hopefully get some time to work on my own complete implementation at some point.

Giorgio Sironi replied on Sun, 2011/02/27 - 9:58am in response to: Keith Pope

Maybe we are bound to end up on the approach of CQRS as an evolution of DDD, and it is very promising; however it is likely to move away some logic from the PHP code (translating from the write store to the read one), and that's something I want to investigate.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.