One meaning of the word functor is a function object. This is an object which may be invoked as if it were a function.
The following code will create a functor:
function makeFunctor(data, fn)
{
var newFn = function(){return fn.apply(newFn, arguments);};
for(var k in data){newFn[k] = data[k];}
return newFn;
}
Basic Functor Demo
The following code is a basic demonstration of a functor:
function basicFunctorDemo()
{
var adder = makeFunctor({a: 0, b: 0}, function(){return this.a + this.b;});
alert('The result of adder() is: ' + adder());
adder.a = 2;
adder.b = 3;
alert('The new result of adder() is: ' + adder());
}
Using Functors to Solve Problems with Iteratively Creating Event Handler Functions
The Problem
These three buttons are initialized with the following code.
There is an error because each event handler function
uses the variable i, however i is declared in the scope of initButtonArea1,
and will therefore have the same value within each of the event handler functions.
Each button will say "Performing action #3" instead of performing separate actions as desired.
function initButtonArea1()
{
var buttonAreaElt = document.getElementsByClassName('functorsArea')[0].getElementsByClassName('buttonArea')[0];
var btnElts = buttonAreaElt.getElementsByTagName('button');
for(var i=0; i < 3; ++i)
{
btnElts[i].addEventListener('click', function(){alert('Performing action #' + i);});
}
}
The Solution with Functors
Using a functor solves this issue, because each functor has its own copy of i.
These buttons work as expected.
function initButtonArea2()
{
var buttonAreaElt = document.getElementsByClassName('functorsArea')[0].getElementsByClassName('buttonArea')[1];
var btnElts = buttonAreaElt.getElementsByTagName('button');
for(var i=0; i < 3; ++i)
{
btnElts[i].addEventListener('click',
makeFunctor(
{i: i},
function(){alert('Performing action #' + this.i);}
)
);
}
}
JavaScript "Watcher" Demo
A Watcher is a JavaScript object which was invented for callback based programming.
Suppose you have three processes, P1, P2, and P3, and you want to begin running
P1 and P2, then run P3 only after P1 and P2 have completed.
A Watcher can accomplish this task by "watching" P1 and P2.
When they are complete, the Watcher will begin P3.
In application, P1 and P2 might represent AJAX calls, or sound and animation sequences.
In this demo, P1 and P2 will take a random amount of time to complete.
When complete, their boxes will reach the far right side of the view area near the word "Done".
P3 should begin moving to the right only after P1 and P2 have reached the far right.
Done
P1
P2
P3
Console output:
A Watcher is used as follows:
function WatcherDemo()
{
this.running = false;
}
WatcherDemo.prototype.startProcess = function(processNum, callback)
{
var elt = document.getElementById('watcher-div' + processNum);
var durationMs = 1250 + Math.round(Math.random() * 3000);
elt.style.transitionProperty = 'left';
elt.style.transitionDuration = durationMs + 'ms';
elt.style.left = '200px';
var result = 'P' + processNum + ' result in ' + durationMs + 'ms time';
if(callback){
setTimeout(function(){callback(result);}, durationMs);
}
}
WatcherDemo.prototype.step1 = function()
{
var cbThis = this;
this.watcher = new Watcher();
this.startProcess(1, this.watcher.makeCallback('myP1Key')); // 'myP1Key' is an arbitrary key to identify the callback
this.startProcess(2, this.watcher.makeCallback('myP2Key'));
this.watcher.start(function(){cbThis.step2();});
}
WatcherDemo.prototype.step2 = function()
{
var cbThis = this;
var conElt = document.getElementById('watcher-consoleArea');
conElt.innerHTML += 'Step 2:<br>';
conElt.innerHTML += this.watcher.getCallbackArgs('myP1Key')[0] + '<br>'; // log the result of P1
conElt.innerHTML += this.watcher.getCallbackArgs('myP2Key')[0] + '<br>'; // log the result of P2
conElt.innerHTML += 'Starting P3<br>';
this.startProcess(3, function(){cbThis.step3();});
}
WatcherDemo.prototype.step3 = function()
{
var conElt = document.getElementById('watcher-consoleArea');
conElt.innerHTML += 'Done';
this.running = false;
document.getElementById('watcher-startBtn').disabled = false;
}
Above, in step1, a watcher object is created, and used to create two callbacks.
Each callback is assigned a key string to identify it later.
step2 is invoked by the watcher after processes 1 and 2 are completed.
Within step2, the arguments of the callbacks are retrieved
through the watcher's getCallbackArgs(callbackKeyStr) method which
returns the arguments array.
Below is the source code for the Watcher class:
/*
events: calls event doneEvent(callbackArgs), where callbackArgs
is an array of argument arrays corresponding to each watched callback's arguments.
*/
function Watcher()
{
this._callbacks = [];
this._callbacksDone = [];
this._callbackArgs = []; // array of argument arrays which are returned by callbacks
this._callbackArgsByKey = {}; // map of callbackKeyStr to callback arguments array
this._started = false;
}
/*
Create a watched callback.
callbackKeyStr - an optional arbitrary string used to identify the newly created callback.
*/
Watcher.prototype.makeCallback = function(callbackKeyStr)
{
if(arguments.length < 1){callbackKeyStr = '' + this._callbacks.length;}
var callback = Utils.makeFunctor(
{watcher: this, index: this._callbacks.length, callbackKeyStr: callbackKeyStr},
function(){this.watcher._onCallback(this.index, this.callbackKeyStr, arguments);}
);
this._callbacks.push(callback);
this._callbacksDone.push(false);
this._callbackArgs.push([]);
return callback;
}
/*
Begin waiting for all watched callbacks to be called.
callback will receive an array of argument arrays corresponding to each watched callback's arguments.
*/
Watcher.prototype.start = function(callback)
{
this._started = true;
Utils.addListener(this, 'doneEvent', callback);
this._checkIfDone();
}
/*
After all watched callbacks are complete, this method will retrieve the arguments for a watched callback.
callbackKeyStr - the key string of the watched callback for which to retrieve arguments.
*/
Watcher.prototype.getCallbackArgs = function(callbackKeyStr)
{
return this._callbackArgsByKey[callbackKeyStr];
}
/*
returns true if all watched callbacks have been called
*/
Watcher.prototype.isCallbacksDone = function()
{
for(var i=0; i < this._callbacksDone.length; ++i)
{
if(!this._callbacksDone[i]){return false;}
}
return true;
}
Watcher.prototype._onCallback = function(index, callbackKeyStr, args)
{
this._callbacksDone[index] = true;
this._callbackArgs[index] = args;
this._callbackArgsByKey[callbackKeyStr] = args;
this._checkIfDone();
}
Watcher.prototype._checkIfDone = function()
{
if(!this._started || !this.isCallbacksDone()){return false;}
if(this.doneEvent){this.doneEvent.apply(null, [this._callbackArgs]);}
return true;
}
PHP MVC Unit Testing Code Sample
This code sample is an illustration of a controller that fits within an imaginary mini MVC framework, and a unit test for that controller.
The MyController class is a simple controller which displays a "welcome" view to users who are not logged in,
and a "dashboard" to users who are logged in. This controller is intended to be an illustration
for the purpose of demonstrating unit testing, and fits within an imaginary MVC framework. The unit test for this controller is further below.
MyController uses Dependency Injection and Factory design patterns to allow unit testing.
MyController.php
<?php
require_once 'MySession.php';
require_once 'UserDAO.php';
require_once 'ViewFactory.php';
require_once 'WelcomeView.php';
require_once 'DashboardView.php';
/**
this class illustrates a controller within a mini mvc system.
*/
class MyController
{
protected $userDAO; // used to access user data
protected $mySession; // used to access data associated with the current logged in session
protected $view; // the view which should be rendered after the controller finishes execution
/**
returns the controller's desired view object
(for example, so that the view may be rendered by the ioc container that instantiated this controller)
*/
public function getView()
{
return $this->view;
}
/**
call this to inject the UserDAO dependency
*/
public function setUserDAO($userDAO)
{
$this->userDAO = $userDAO;
}
/**
call this to inject the MySession dependency
*/
public function setMySession($mySession)
{
$this->mySession = $mySession;
}
/**
call this to inject the ViewFactory dependency
*/
public function setViewFactory($viewFactory)
{
$this->viewFactory = $viewFactory;
}
/**
this is the controller's main logic.
a requirement of this mini mvc framework is that main() must set $this->view to the desired view,
and initialize that view with any needed variables.
all dependencies must be injected before main() may be called.
*/
public function main()
{
// check the session to see if the user is logged in
$loggedInUserId = $this->mySession->getUserId();
if($loggedInUserId === null)
{ // nobody is logged in, so show the WelcomeView
$this->view = $this->viewFactory->getView('WelcomeView');
$this->view->init();
}
else
{ // a user is logged in, so show the DashboardView
$userData = $this->userDAO->getUser($loggedInUserId);
$this->view = $this->viewFactory->getView('DashboardView');
$this->view->init(array(
'userName' => $userData->userName,
'userAge' => $userData->userAge
));
}
}
}
Class MyControllerTest: The Unit Test for MyController
The following unit test for PHPUnit will test MyController for the case of no user logged in, and then for the cases of two different users logged in.
MyControllerTest.php
<?php
/**
unit tests for MyController.
this class illustrates how unit testing may be done
in an mvc framework using dependency injection and factory design patterns
*/
class MyControllerTest extends PHPUnit_Framework_TestCase
{
private $myController;
/**
perform any operations needed to prepare for a test
*/
protected function setUp()
{
$this->myController = new MyController();
}
/**
provides an array of data for each test to run with testMain
*/
public function testMainProvider()
{
// userId, userName, userAge
return array(
array(null, null, null), // test with nobody logged in
array(1111, 'Mr. Smith', 31), // test with a user logged in
array(1112, 'Ms. Jones', 27), // test with a different user logged in
);
}
/**
test the main() method of the controller
@dataProvider testMainProvider
*/
public function testMain($userId, $userName, $userAge)
{
//// mock MySession
$mySessionMock = $this->getMockBuilder('MySession')
->disableOriginalConstructor()
->getMock();
// the MySession object will provide the userId for a logged in user,
// or null if nobody is logged in
$mySessionMock->expects($this->once())
->method('getUserId')
->will($this->returnValue($userId));
//// mock UserDAO
// the data object to pass to the controller
$userData = (object) array(
'userName' => $userName,
'userAge' => $userAge
);
$userDAOMock = $this->getMockBuilder('UserDAO')
->disableOriginalConstructor()
->getMock();
if($userId === null)
{
$userDAOMock->expects($this->exactly(0))
->method('getUser');
}
else
{
$userDAOMock->expects($this->once())
->method('getUser')
->will($this->returnValue($userData));
}
//// mock ViewFactory
$viewFactoryMock = $this->getMockBuilder('ViewFactory')
->disableOriginalConstructor()
->getMock();
// the ViewFactory expects a different view to be requested
// by the controller depending on the value of userId
if($userId === null)
{ // userId is null, so we expect a WelcomeView to be requested by the controller
$expectedViewClassName = 'WelcomeView';
$expectedViewMock = $this->getMockBuilder('WelcomeView')
->disableOriginalConstructor()
->getMock();
$expectedViewMock->expects($this->once())
->method('init');
}
else
{ // userId is not null, so we expect a DashboardView to be requested by the conroller
$expectedViewClassName = 'DashboardView';
$expectedViewMock = $this->getMockBuilder('DashboardView')
->disableOriginalConstructor()
->getMock();
// check the view was passed the correct user information
$expectedViewMock->expects($this->once())
->method('init')
->with($this->callback(
function($subject) use ($userName, $userAge) {
return $subject['userName'] === $userName
&& $subject['userAge'] === $userAge;
}
));
}
$viewFactoryMock->expects($this->once())
->method('getView')
->with($this->equalTo($expectedViewClassName))
->will($this->returnValue($expectedViewMock));
//// Dependency Inject
$this->myController->setMySession($mySessionMock);
$this->myController->setUserDAO($userDAOMock);
$this->myController->setViewFactory($viewFactoryMock);
//// Run Functionality
$this->myController->main();
//// Assertions
// check the controller responds with the correct view
$this->assertEquals($expectedViewMock, $this->myController->getView());
}
}