I'll try to give an example. Bear in mind though that I haven't written any PHP in 10 years, it's semi pseudo code and I hadn't heard ot Kohana before I read this thread. Also there are many things I did not touch, some rules I broke and improvements which can be made. I'll leave those for further discussion.
So here goes:
A simple controller which declares 1 action to request for the timeline of a specific user.
The input is a username query parameter which is passed to a LoadTimeline usecase upon execution. This is the request model boundary.
You'll also find the controller implements some callback functions as part of the response model boundary. They are unauthenticated, rejected and render_timeline.
class TimelineController extends Controller {
public function action_timeline() {
LoadTimelineFactory::usecase->execute($this->request->get('username'), $this);
}
public function unauthenticated() {
// ask the user to authenticate? e.g: HTTP 401
}
public function reject($violations) {
// render violations e.g: HTTP 412
}
public function render_timeline($tweets) {
$this->response->body(View::factory('twitter/timeline')->set('tweets', $tweets));
}
}
The controller above uses a factory to create an instance of the LoadTimelineUsecase. You could technically deploy a different implementation of this factory to create a different implementation of the usecase.
class LoadTimelineFactory {
public function usecase() {
return new LoadTimelineUsecase();
}
}
So for the usecase we do the following:
- check if the user is authenticated (on fail respond with unauthenticated)
- check if the input is valid (on fail respond with rejected and a data structure describing the violations)
- load the timeline from the tweet gateway and pass it to the render_timeline callback (technically an ISP violation here)
class LoadTimelineUsecase {
$tweetGateway = TweetGatewayFactory::gateway();
$securityPass = SecurityPassFactory::pass();
public function execute($username, $response) {
if($securityPass->isAuthenticated()) {
if(is_empty($username))
$response->reject_input(array('username' => array('required')));
else
$response->render_timeline($tweetGateway->timelineFor($username))
} else
$response->unauthenticated();
}
}
A factory for a Kohana based security pass collaborator. In a different framework you would make a different implementation naturally.
class SecurityPassFactory {
public function pass() {
return new KohanaSecurityPass();
}
}
The kohana security pass collaborator implementation simply wraps the existing kohana auth API. None of the core business logic directly depends on this. They instead depend on an implicit interface which exposes an isAuthenticated function. Do note that I did not check what the kohana security API actually looks like. Still it is likely it uses different names for its methods so you want to translate those to a common vocabulary the rest of your system can depend on.
class KohanaSecurityPass {
public function isAuthenticated() {
return Auth::instance()->is_logged_in()
}
}
Once again a factory but this time for the tweet gateway.
class TweetGatewayFactory {
public function gateway() {
return new KohanaTweetGateway();
}
}
A tweet gateway implementation which uses the kohana ORM layer to load the timeline useing a named query called timelineFor which takes a username as argument.
Before returning the query results I'm mapping the array to instances of KohanaTweet which extends the Tweet business entity and brokers between the kohana ORM API and the actual entity format you decided upon.
class KohanaTweetGateway {
public function timelineFor($username) {
return array_map(
function($it) {
$tweet = new KohanaTweet();
$tweet->delegate = $it;
return $tweet;
},
ORM::factory('Tweet')->where('username', $username)->find_all()
)
}
}
class KohanaTweet extends Tweet {
$delegate
}
class Tweet {
// business rules
}
I hope this helps.
For the most part it's the same s in other languages I'd think.
Cheers,
- Sebastian