Methods manipulating it's own object's state are cohesive. If an object has method that operate on it's members (state), then we speak of high cohesion. If a method does not operate on it's members, you have low cohesion. In the latter case it's a good indicator that the method is misplaced on the object.
Example:
class Foo
{
private $fooBar;
public function highCohesionMethod()
{
// do something with $fooBar
}
public function lowCohesionMethod($input)
{
return strtolower($input);
}
}
Cohesion is not limited to setters. Methods can broadly be categorized as Commands or Queries. Commands tell the object to do something. A Command with high cohesion will somehow use the object's internal state for this. Queries ask the object about it's internal state. By doing so, you are not changing the state, but you still have high cohesion. By virtue of the CommandQuerySeparation principle, Commands should not return something, while Queries should.
Example:
class Foo
{
private $fooBar;
// Command
public function setFooBar($fooBar)
{
$this->fooBar = $fooBar;
}
// Query
public function getFooBar($input)
{
return $this->fooBar;
}
}
The consequence of High Cohesion and CommandQuerySeparation is, that you should try to Tell Don't Ask, e.g. try to tell objects what they should do instead of asking them and then putting the logic into the consumer. If you pull state of collaborators from the outside and then make decisions, you are almost always lowering cohesion:
Example:
class Consumer
{
public function askingObjects()
{
if ($this->collaborator->hasFooBar()) {
$fooBar = $this->collaborator->getFooBar();
// do something with $fooBar
// more code …
} else {
throw new RuntimeException('Collaborator has no foo');
}
}
}
As you can see, the consumer is not operating on it's own state, but it's pulling in the collaborator's state to operate on that. To do so, the collaborator has to have methods exposing it's internal state and the consumer has to know about this. In most cases, this is not necessary and violates Information Hiding principle and is much closer to procedural programming than to OOP. Compare with
class Consumer
{
public function tellingObjects()
{
try {
$this->collaborator->doFooBar();
} catch (CollaboratorException $e) {
// handle gracefully
}
}
}
class Collaborator
{
public function doFooBar($input)
{
if ($this->hasFooBar()) {
// do something with $this->fooBar
// more code …
} else {
throw new CollaboratorException('This has no foo');
}
}
}
With this code, the decision whether collaborator can or cannot do something is up to the collaborator itself. It's appropriate to do so, because the collaborator is an Information Expert. It knows best what it can and cannot do.
Following Tell Don't Ask will usually reduce the number of Getters and Setters drastically, as you focus on what the object responsibilities. This will give you much smaller interfaces for object to object communication, e.g. it will narrow the contracts between collaborators, benefiting changes.
However, following Tell Don't Ask will often lead to object's having more than one responsibility, which violates the SingleResponsibilityPrinciple so in practise you will have a mix of Commands and Queries and Mediators, e.g. objects handling communication between other objects.
This gets us to your blog example. You can do pretty much everything from and through the user:
class User
…
public function authenticate($authenticator, $password)
{
$this->isAuthenticated = $authenticator->isValidUser(
$this->username,
$password
);
}
You can also handle all the BlogPost modifications through it:
class User
…
public function createNewBlogPost()
{
$blogPost = new BlogPost;
$blogPost->setAuthor($this);
return $blogPost;
}
public function readBlogPost($id)
{
return $this->blogPostRepository->findById($id);
}
public function updateBlogPost(BlogPost $blogPost, $title, $body, $tags)
{
$blogPost->setTitle($title);
$blogPost->setBody($body);
$blogPost->setTags($tags);
}
public function deleteBlogPost(BlogPost $blogPost)
{
$blogPost->setDeleted(true);
}
But then you'll end up with a User object that has very little cohesion. It will likely also know quite a bit about all the other classes in your application. It might grow pretty big which is usually a sign of too many responsibilities. However, in Domain Driven Design it is common to have a single entry point to a larger object graph. This is the called the Aggregate Root. I'm just not so sure whether the User is the best choice for that. Maybe you should introduce a Blog class as the Aggregate Root.
Note that it's not necessarily a BlogPost object concern to create, update or publish itself. The BlogPost pretty much depends on being told what it's state is. In that regard it's just a data container. Think of it as a glorified array. Putting Getters and Setters for collaborators to manipulate it is okay.
Another option would be to split all the CRUD actions into separate Service classes. This is easy to maintain and has the added benefit that you'll know immediately what your app can do by looking at the files in the UseCase folder
Example:
class CreateNewBlogPostService()
…
public function createNewBlogPost(User $user)
{
$blogPost = new BlogPost;
$blogPost->setAuthor($user);
$this->blogPostsRepository->add($blogPost);
return $blogPost;
}
}
This is easy to read and understand because everything happens inside that single method. There is just a single responsibility here. Obviously there isn't much cohesion but Services orchestrate other collaborators so it's normal for them not to have any state at all.
If you don't like this approach, you could also experiment with Role Objects and DCI, but this is out of scope for this answer. Likewise, given that a Blog is mainly just CRUD of blog posts, it might make more sense to skip the DomainModel and just approach it with a TableDataGateway and TableModule approach instead.
Anyways, hope that sheds some light.