One of my favourite OO design philosophies is Tell Don't Ask (TDA), this is one of the best ways I have found to promote loose coupling, have clear intent within my code and not have leaky state between objects.

The general principle is that you should only tell objects to do things and not query their state (basically following the Law of Demeter).

A really basic example would be querying an object to send some sort of alert, like this:

class Account {  
    private $balance;
    private $limit;

    public function __construct($balance, $limit)
    {
        $this->balance = $balance;
        $this->limit = $limit;
    }

    public function getBalance() 
    {
        return $this->balance;
    }

    public function getLimit() 
    {
        return $this->limit;
    }

    public function alert() 
    {
        // send user alert...
    }
}

// Inside some sort of service object etc
$acnt = new Account(100, 100);

if ($acnt->getBalance() >= $acnt->getLimit()) {  
    $acnt->alert();
}

In the above, we are querying the objects state to make a decision about what it should do. A better approach to this is to simply tell the object to do that thing.

class Account {  
    private $balance;
    private $limit;

    public function __construct($balance, $limit)
    {
        $this->balance = $balance;
        $this->limit = $limit;
    }

    public function checkAccountLimitAndSendAlert() 
    {
        if ($this->balance >= $this->limit) {
            // Send alert
        }
    }
}

$acnt = new Account(100, 100);
$acnt->checkAccountLimitAndSendAlert();

As we can see the refactored code is much easier to read and explicitly defines the behaviour of the account limit alert, it also now doesn't leak any of its internal state.

Another great thing about TDA is that it pushes you into a more message based design rather than a data-centric one. By using messages (calling a method) our objects now talk to each other just like OOP was meant to.

We can take TDA a step further with Command Query Separation this defines that you should separate methods that change state from methods that query state. By using CQS we can be confident any querying of an object has no side-effects and have clear intent from our commands and know they mutate state.

When working with CQS I like to introduce explicit command objects, though this is not required by the pattern, it tends to keep things cleaner and easier to quickly see what is a command and what is a query.

If we take our earlier example, this could look something like:

final class CheckAccountLimitCommand {  
    public $limit;

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

class Account {  
    private $balance;

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

    public function onCheckAccountLimit(CheckAccountLimitCommand $command) 
    {
        if ($this->balance >= $command->limit) {
            // Send alert
        }
    }

    public function getSomething()
    {
        // A query...
    }
}

$acnt = new Account(100);
$acnt->process(new CheckAccountLimitCommand(100));

This is a little bit of a convoluted example, we would probably not move limit into the command, but I wanted to show how commands pass data into objects. From the example though we can now see that all behaviour of the object is now controlled by the commands it processes. This gives us clear intent and makes it easy to see what methods mutate and which methods query (the getters).

CQS (and TDA) also provides the basis for the CQRS pattern. CQRS (Command Query Responsibility Segregation) takes CQS further by separating command and query into different layers. It also takes the message centric aspect of TDA and applies that to the entire architecture, where commands mutate the domain, the domain produces events, events are pipelined to a read model and state is stored using event sourcing (events stored in a log). All this together gives you a powerful scalable architecture, particularly suited to DDD.

So Tell Don't Ask is pretty cool :)

Footnote

If you are really trying to eliminate the majority of getters in your code using TDA etc, one way to tackle view rendering is to introduce a canvas object. For example:

class MyEntity {  
    public function render(Canvas $canvas)
    {
        $canvas->foo = $this->foo;
        $canvas->bar = $this->bar;
    }
}

$canvas = new Canvas('<p>{{foo}} - {{bar}}</p>');
$myEntity->render($canvas);