phalconphp Access controllerName that triggered a event - php

How could i reference the controllerName and actionName that triggered the beforeExecuteRoute from the event itself?
<?php
use Phalcon\Events\Manager as EventsManager;
//Create a events manager
$eventManager = new EventsManager();
//Listen all the application events
$eventManager->attach('micro', function($event, $app) {
if ($event->getType() == 'beforeExecuteRoute') {
//how to get controller name to handle acl stuff
}
});

From the documentation - http://docs.phalconphp.com/en/latest/api/Phalcon_Mvc_Dispatcher.html
getModuleName () - Gets the module where the controller class is
getControllerName () - Gets last dispatched controller name
getActionName () - Gets the lastest dispatched action name
Example:
<?php
use Phalcon\Events\Manager as EventsManager;
//Create a events manager
$eventManager = new EventsManager();
//Listen all the application events
$eventManager->attach('micro', function($event, $app) {
if ($event->getType() == 'beforeExecuteRoute') {
$controllerName = $app->getControllerName();
$moduleName = $app->getModuleName();
$actionName = $app->getActionName();
}
});

This way you can get the route in string format to parse it:
$router->getMatchedRoute()->getPattern();
Hope this help. I found no other way to do this.

If you don't have a dispatcher you must be getting those values from the router. I'm not very familiar with specifics of micro apps, but from looking at the docs it must be something like that.
<?php
use Phalcon\Events\Manager as EventsManager;
//Create a events manager
$eventManager = new EventsManager();
//Listen all the application events
$eventManager->attach('micro', function($event, $app) {
if ($event->getType() == 'beforeExecuteRoute') {
//how to get controller name to handle acl stuff
DI::getDefault()->get('router')->getControllerName();
DI::getDefault()->get('router')->getActionName();
// or
$app->getRouter()->getControllerName();
$app->getRouter()->getActionName();
}
});
Does this work?

I actually ran into a similar confusion, but the other answers didn't help, and Phalcon's official documentation didn't find the right way to get it, so if you're using Micro, it's hard to find an effective answer on the web.So I used my reflection to understand Phalcon's before, and finally came up with the following answer, which I hope will help you. Good luck!
<?php
$eventsManager = new \Phalcon\Events\Manager();
$eventsManager->attach(
'micro:beforeExecuteRoute',
function (\Phalcon\Events\Event $event, $app) {
$controllerName = $event->getSource()->getActiveHandler()[0]->getDefinition();
$actionName = $event->getSource()->getActiveHandler()[1];
}
);
$app = new \Phalcon\Mvc\Micro($di);
$app->setEventsManager($eventsManager);
If you want to implement the ACL with the before event, the following AclAnnotation code may help:
<?php
class AclAnnotation
{
protected $private = true;
protected $roles = [];
protected $component = [];
protected $access = [];
public function __construct($event, $app)
{
$controllerName = $event->getSource()->getActiveHandler()[0]->getDefinition();
$actionName = $event->getSource()->getActiveHandler()[1];
$reflector = $app->annotations->get($controllerName);
$annotations = $reflector->getClassAnnotations();
if (!$annotations) {
throw new ErrorException('The permission configuration is abnormal. Please check the resource permission comments.');
return;
}
if (
$annotations->has('Private')
&& ($annotation = $annotations->get('Private'))->numberArguments() > 0
) {
$this->private = $annotation->getArguments('Private')[0];
}
if (
$annotations->has('Roles')
&& ($annotation = $annotations->get('Roles'))->numberArguments() > 0
) {
$this->roles = $annotation->getArguments('Roles');
}
if (
$annotations->has('Components')
&& ($annotation = $annotations->get('Components'))->numberArguments() > 0
) {
$this->components = $annotation->getArguments('Components');
}
$annotations = $app->annotations->getMethod($controllerName, $actionName);
if (
$annotations->has('Access')
&& ($annotation = $annotations->get('Access'))->numberArguments() > 0
) {
$this->access = $annotation->getArguments('Access');
}
}
public function isPrivate()
{
return $this->private;
}
public function getRoles()
{
return $this->roles;
}
public function getComponents()
{
return $this->components;
}
public function getAccess()
{
return $this->access;
}
}
Create an event listener in index.php:
<?php
$eventsManager = new \Phalcon\Events\Manager();
$eventsManager->attach(
'micro:beforeExecuteRoute',
function (\Phalcon\Events\Event $event, $app) {
$aclAnnotation = new AclAnnotation($event, $app);
if (!$aclAnnotation->isPrivate()) {
return true;
}
$acl = $app->currentACL;
// Verify that you have access permission for the interface
if (!$acl->isAllowed($aclAnnotation->getRoles(), $aclAnnotation->getComponents(), $aclAnnotation->getAccess())) {
throw new \ErrorException('You do not have access to the resource', 40001);
return false;
}
return true;
}
);
$app = new \Phalcon\Mvc\Micro($di);
$app->setEventsManager($eventsManager);
As for the use of annotations, you can refer to here:
https://docs.phalcon.io/5.0/en/annotations

Related

DDD in PHP -> DomainEventPublisher -> Where to use the subscribe method?

The flow:
CreateNewTaskRequest -> CreateNewTaskService -> Task::writeFromNew() -> NewTaskWasCreated(domain event) -> DomainEventPublisher calls handle on subscribers.
Following the flow above, I'm wondering where do you add subscribers for domain events?
I'm currently reading the book DDD in PHP, but I'm unable to grasp where this should be done?
This is the code I have but feels wrong to me
public static function writeNewFrom($title)
{
$taskId = new TaskId(1);
$task = new static($taskId, new TaskTitle($title));
DomainEventPublisher::instance()->subscribe(new MyEventSubscriber());
$task->recordApplyAndPublishThat(
new TaskWasCreated($taskId, new TaskTitle($title))
);
return $task;
}
Task extends Aggregate root:
class AggregateRoot
{
private $recordedEvents = [];
protected function recordApplyAndPublishThat(DomainEvent $domainEvent)
{
$this->recordThat($domainEvent);
$this->applyThat($domainEvent);
$this->publishThat($domainEvent);
}
protected function recordThat(DomainEvent $domainEvent)
{
$this->recordedEvents[] = $domainEvent;
}
protected function applyThat(DomainEvent $domainEvent)
{
$modifier = 'apply' . $this->getClassName($domainEvent);
$this->$modifier($domainEvent);
}
protected function publishThat(DomainEvent $domainEvent)
{
DomainEventPublisher::instance()->publish($domainEvent);
}
private function getClassName($class)
{
$class = get_class($class);
$class = explode('\\', $class);
$class = end($class);
return $class;
}
public function recordedEvents()
{
return $this->recordedEvents;
}
public function clearEvents()
{
$this->recordedEvents = [];
}
}
The DomainEventPublisher class is a singleton, and you can add a subscriber with
DomainEventPublisher::instance()->subscribe(new YourSubscriber());
where YourSubscriber implements DomainEventSubscriber.

Review the route dynamically Symfony2

Hello I need get the route and review if the have access to this, reviewing on the database but a level really abstract and automatic. By now i am making that on this form:
$accesos = MenuQuery::create()
->useAccesoMenuQuery()
->usePerfilQuery()
->usePerfilUsuarioQuery()
->filterByUsuarioId($this->getUser()->getId())
->endUse()
->endUse()
->endUse()
->orderBy('menu.orden')
->groupBy('menu.id')
->find();
$permiso = false;
foreach ($accesos as $acceso) {
if (($acceso->getDireccion() == $ruta) || ($permiso)) {
$permiso = true;
break;
}
}
return $permiso;
}
I am using this php function for make that. But i need to make that on the firewalls of symfony2 or another form but abstract.
One way, and if the routes are generic, set up ACL in the security.yml where you can specify that for example ^/admin/.* require ROLE_ADMIN.
If you have special roles, for example, ACCOUNT_ADMIN where it's depending on what is the selected account, then you need to write your own VOTER, where you can decide that at a specific point, is the rights are enough or not.
The form that I did it was using a Event Listener of Symfon2 Here I leave to yours my own code.
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
class SoporteListener {
private $container;
private $acceso = 0;
public function __construct(ContainerInterface $container) {
$this->container = $container;
}
public function onKernelRequest(GetResponseEvent $event) {
$request = $this->container->get('request');
$routeName = $request->get('_route');
$securityContext = $this->container->get('security.context');
if (($securityContext->isGranted('ROLE_USUARIO')) &&
($routeName != 'done_login') &&
($routeName != 'done_logout')) {
$usuario = $this->container->get('security.context')->getToken()->getUser();
$permisos = MenuQuery::create()
->useAccesoMenuQuery()
->usePerfilQuery()
->usePerfilUsuarioQuery()
->filterByUsuarioId($usuario->getId())
->endUse()
->endUse()
->endUse()
->groupBy('menu.id')
->find();
foreach ($permisos as $permiso) {
if (($permiso->getDireccion() == $routeName)) {
$this->acceso = 1;
break;
}
}
if ($this->acceso == 0) {
$event->setResponse($this->container->get('templating')->renderResponse('::error.html.twig', array('error' => 'Permiso denegado')));
} else {
return;
}
} else {
return;
}
}
}

Dependency Injection Pattern seems extreme in Control Class

My Control Class basically picks which object / class to instantiate. Because this is basically what it does it naturally has many objects / classes it calls.
If I use dependency injection I will be injecting all of these objects. This seems bad for two reasons.
I've heard that about 3 dependent objects / classes is normal to KISS ( Keep it Simple Smarty)
Only one of the objects / classes will be used. So in a sense the others are instantiated for no reason.
How do I resolve these design considerations to satisfy - decoupled code, but simple and used code?
What you do is that you actually map some parameter onto some functionality, a so called script or action.
So in the end you only need a convention how to map that parameter or name onto some function. As functions can be somewhere (in some other object, in the global space, anonymous), you don't really need to inject many objects into your control class, but functions and the mapping.
If you would than even add some more differentiation with function name and parameters (or "modules" and "actions"), well then, you could drastically reduce your code and you can actually make the request inject the dependency:
Script or Action
Mapping: Actions:
"*" "_.Exception.Invalid ajax_type"
"signin_control" "A.SignIn.invoke"
"signup_control" "A.SignUp.invoke"
"tweet_control" "A.Tweet.add"
"ControlBookmark_add" "A.Bookmark.add"
"ControlBookmark_delete" "A.Bookmark.delete"
"ControlTryIt" "B.ControlTryIt"
"ControlSignOut" "C.SignOut"
Implementation:
$action = $map[isset($map[$ajax_type]) ? $ajax_type : '*'];
Session::start();
call_user_func_array(
'call_user_func_array',
explode('.', $action) + array(NULL, NULL, NULL)
);
function _($a, $b) {
throw new $a($b);
}
function A($a, $b) {
$maker = new ObjectMaker();
$maker->$a()->$b();
}
function B($a) {
new $a();
}
function C($a) {
Session::finish();
B($a);
}
This pseudo-code shows the actual business of your control class: Call some functions based on it's input. The concrete dependencies are:
ObjectMaker
Session
$map
As session is static, you should replace it with something that actually can be injected.
As $map is an array, it can be injected, but the logic of the mapping might need to become something more internal, so if $map is an ArrayAccess, this can happen already.
The non-concrete dependencies are hidden inside the actual $ajax_type, so these dependencies are dependent on that parameter through mapping, which is already a dependency. So the last dependency is:
$ajax_type
This dependency is related to both the control class and the mapping. So the control class itself could be made a dependency to the ajax type as well. But as you use a static global function, I'll simplify this inside a class function so actually dependencies can be passed into it. I put the factory into a global function and the ajax types are loaded from an ini-file:
function ajax_control_factory($inifile)
{
$maker = new ObjectMaker();
$session = new SessionWrap();
$types = new AjaxTypesIni($inifile);
return new AjaxControl($maker, $session, $types);
}
$control = ajax_control_factory($inifile);
printf("Call an nonexistent ajax type: ");
try {
$control->invokeByType('blurb');
printf(" - I so failed!\n");
} catch (Exception $e) {
printf("Exception caught! All good!\n");
}
printf("Add me a bookmark: ");
$control->invokeByType("ControlBookmark_add");
printf("Done! Fine! Superb this works!\n");
printf("Do the two control functions: ");
$control->invokeByType("ControlTryIt");
$control->invokeByType("ControlSignOut");
printf("Done! Fine! Superb this works!\n");
Ini file:
* = _.Exception.Invalid ajax_type
signin_control = A.SignIn.invoke
signup_control = A.SignUp.invoke
tweet_control = A.Tweet.add
ControlBookmark_add = A.Bookmark.add
ControlBookmark_delete = A.Bookmark.delete
ControlTryIt = B.ControlTryIt
ControlSignOut = C.SignOut
To have this work, this needs some stubs for mocking, which is easy with your example:
class Mock
{
public $stub;
public function __call($name, $args)
{
return class_exists($this->stub) ? new $this->stub() : $this->stub;
}
}
class ObjectMaker extends Mock
{
public $stub = 'Mock';
}
class ControlTryIt {}
class SignOut {}
class SessionWrap
{
public function start()
{
// session::start();
}
public function stop()
{
// session::finish();
}
}
Those are enough to run the code above which will give:
Call an nonexistent ajax type: Exception caught! All good!
Add me a bookmark: Done! Fine! Superb this works!
Do the two control functions: Done! Fine! Superb this works!
The ajax types:
class AjaxTypes extends ArrayObject
{
private $default;
private $types;
public function __construct(array $types, $default)
{
parent::__construct($types);
$this->default = $default;
}
public function offsetGet($index)
{
return parent::offsetExists($index) ? parent::offsetGet($index) : $this->default;
}
}
class AjaxTypesIni extends AjaxTypes
{
public function __construct($inifile)
{
$map = parse_ini_file($inifile);
if (!isset($map['*'])) throw new UnexpectedValueException('No * entry found.');
$default = $map['*'];
unset($map['*']);
parent::__construct($map, $default);
}
}
And the ajax controler:
class AjaxControl
{
private $types;
private $maker;
private $session;
public function __construct(ObjectMaker $maker, SessionWrap $session, AjaxTypes $types)
{
$this->types = $types;
$this->maker = $maker;
$this->session = $session;
}
public function invokeByType($type)
{
$session = $this->session;
$maker = $this->maker;
$invoke = function($action) use ($session, $maker)
{
$_ = function($a, $b)
{
throw new $a($b);
};
$A = function($a, $b) use ($maker)
{
$maker->$a()->$b();
};
$B = function ($a)
{
new $a();
};
$C = function ($a) use ($B, $session)
{
$session->stop();
$B($a);
};
$args = explode('.', $action) + array(NULL, NULL, NULL);
$func = array_shift($args);
call_user_func_array(${$func}, $args);
};
$invoke($this->types[$type]);
$this->session->start();
}
}
This is just exemplary. There is no guarantee that this fits as a design for your needs, just for demonstrating purposes. What it shows is that your actual controller function is not normalized / modular enough. When you better analyze the dependencies that exist and you inject them instead that you hardencode them, you will automatically find the best way to design your system.
What this example shows as well is that you have a tons of hidden dependencies for the request and response. You really need to draw lines somewhere and define what you pass through and in which direction. Say goodbye to global static state. Always inject. You can even start with function that need everything as parameters if it helps.
Solved:
By placing the dependency injection in the factory pattern ( Object Maker ), I can pull out all of the dependencies into one dependency - Object Maker - note below.
PHP Control
class Control
{
public static function ajax($ajax_type)
{
Session::start();
switch($ajax_type)
{
case 'signin_control': // uses Message, Text, Database
$Object = new ObjectMaker();
$ObjectSignIn=$Object->makeSignIn();
$ObjectSignIn->invoke();
break;
case 'signup_control':// uses Message, Text, Database
$Object = new ObjectMaker();
$ObjectSignUp=$Object->makeSignUp();
$ObjectSignUp->invoke();
break;
case 'tweet_control':// uses Message, Text, Database
$Object = new ObjectMaker();
$ObjectTweet=$Object->makeTweet();
$ObjectTweet->add();
break;
case 'ControlBookmark_add': // uses Message, Text, Database
$Object = new ObjectMaker();
$ObjectBookmark = $Object->makeBookmark();
$ObjectBookmark->add();
break;
case 'ControlBookmark_delete':// uses Database
$Object = new ObjectMaker();
$ObjectBookmark=$Object->makeBookmark();
$ObjectBookmark->delete();
break;
case 'ControlTryIt': // Why Not Session
new ControlTryIt();
break;
case 'ControlSignOut':
Session::finish();
new ControlSignOut();
break;
default:
throw new Exception('Invalid ajax_type');
}
}
ObjecMaker
class ObjectMaker
{
public function makeSignUp()
{
$DatabaseObject = new Database();
$TextObject = new Text();
$MessageObject = new Message();
$SignUpObject = new ControlSignUp();
$SignUpObject->setObjects($DatabaseObject, $TextObject, $MessageObject);
return $SignUpObject;
}
public function makeSignIn()
{
$DatabaseObject = new Database();
$TextObject = new Text();
$MessageObject = new Message();
$SignInObject = new ControlSignIn();
$SignInObject->setObjects($DatabaseObject, $TextObject, $MessageObject);
return $SignInObject;
}
public function makeTweet( $DatabaseObject = NULL, $TextObject = NULL, $MessageObject = NULL )
{
if( $DatabaseObject == 'small' )
{
$DatabaseObject = new Database();
}
else if( $DatabaseObject == NULL )
{
$DatabaseObject = new Database();
$TextObject = new Text();
$MessageObject = new Message();
}
$TweetObject = new ControlTweet();
$TweetObject->setObjects($DatabaseObject, $TextObject, $MessageObject);
return $TweetObject;
}
public function makeBookmark( $DatabaseObject = NULL, $TextObject = NULL, $MessageObject = NULL )
{
if( $DatabaseObject == 'small' )
{
$DatabaseObject = new Database();
}
else if( $DatabaseObject == NULL )
{
$DatabaseObject = new Database();
$TextObject = new Text();
$MessageObject = new Message();
}
$BookmarkObject = new ControlBookmark();
$BookmarkObject->setObjects($DatabaseObject,$TextObject,$MessageObject);
return $BookmarkObject;
}
}

Obtain instance of Zend_Session_Namespace via DI container

I want to retrieve an instance of Zend_Session_Namespace within my models but I don't want them to have a concrete dependency on Zend's implementation (so I can mock it for it testing).
The session instance needs some configuration passed to it at call time. My other dependencies do not and can be configured during the bootstap process.
I have a very basic DI container, borrowed from Fabien Potencier:
class Lib_Container {
protected $services = array();
function __set($id, $service) {
$this->services[$id] = $service;
}
function __get($id) {
if (!isset($this->services[$id])) {
throw new ServiceNotRegisteredException(
"Service '$id' has not been registered"
);
}
if (is_callable($this->services[$id])) {
return $this->services[$id]($this);
}
return $this->services[$id];
}
}
I'm using this to wire up my dependencies:
$container = new Lib_Container;
$container->session = function($c) {
return new Zend_Session_Namespace($c->sessionName);
};
...
I'm using these dependencies within my base model (I don't want my model to know so much about my container configuration):
class Lib_Model {
protected $_container;
protected $_sessionName = 'default';
protected $_sessionInstance;
public function __construct($container) {
$this->_container = $container;
}
public function getDB() {
return $this->_container->database;
}
public function getRequest() {
return $this->_container->request;
}
public function getSession($ns = null) {
$ns = ($ns == null) ? $this->_sessionName : $ns;
if (!isset($this->_sessionInstance[$ns])) {
$this->_container->sessionName = $ns;
$this->_sessionInstance[$ns] = $this->_container->session;
}
return $this->_sessionInstance[$ns];
}
}
This enables my subclasses to retrieve a session instance reasonably conveniently:
class Model_User extends Lib_Model {
protected $_sessionName = 'user';
public function loggedIn() {
$session = $this->getSession();
return ($session && $session->loggedIn) ? true : false;
}
}
Or by passing the session namespace as an argument:
$session = $this->getSession('admin');
However, my Lib_Model::getSession() method is more complex than I would like, and knows too much about my DI container. Ideally want to obtain an instance of Zend_Session_Namespace by calling:
class Lib_Model {
protected $_sessionName = 'default';
protected $_sessionFactory;
...
public function __construct($container) {
$this->_sessionFactory = $container->session;
}
...
public function getSession($ns = null) {
$ns = ($ns == null) ? $this->_sessionName : $ns;
if (!isset($this->_sessionInstance[$ns])) {
$this->_sessionInstance[$ns] = $this->_sessionFactory($ns);
}
return $this->_sessionInstance[$ns];
}
}
I appreciate my DI container is checking if it's services are callable (e.g. anonymous functions) and executing them. If I remove this behaviour the auto-wiring element will crumble?
Any ideas how I can achieve $container->session('my_namespace') to return the equivalent of new Zend_Session_Namespace('my_namespace')?
Update: I thought I was on to something by changing the configuration of my container:
$container->session = function($c) {
$s = function($namespace) {
return new Zend_Session_Namespace($namespace);
};
return $s;
};
So that $container->session would return a function. Updating my Lib_Model class:
Lib_Model {
private $_sessionFactory;
...
public function __construct($container) {
...
$this->_sessionFactory = $container->session;
}
...
public function getSession($ns = null) {
$ns = ($ns == null) ? $this->_sessionName : $ns;
if (!isset($this->_sessionInstance[$ns]))
$this->_sessionInstance[$ns] = $this->_sessionFactory($ns);
return $this->_sessionInstance[$ns];
}
}
Unfortunately this gives me a 500 internal server error :(
I resolved the 500 internal server error by adjusting Lib_Model::getSession() slightly:
public function getSession($ns = null) {
$ns = ($ns == null) ? $this->_sessionName : $ns;
if (!isset($this->_sessionInstance[$ns])) {
$sessionFactory = $this->_session;
$this->_sessionInstance[$ns] = $sessionFactory($ns);
}
return $this->_sessionInstance[$ns];
}
I put together a simple script slowly building up it's complexity until it dawned on me I was calling an undefined method on Lib_Model, though no error message was displayed by PHP running under apache.
$f = function() {
return function($name) {
echo "Hello " . $name . PHP_EOL;
};
};
$hello = $f();
$hello("World");
unset($hello);
// second test
class Container {
protected $services = array();
function __set($id, $service) {
$this->services[$id] = $service;
}
function __get($id) {
if (!isset($this->services[$id])) {
throw new ServiceNotRegisteredException(
"Service '$id' has not been registered"
);
}
if (is_callable($this->services[$id])) {
return $this->services[$id]($this);
}
return $this->services[$id];
}
}
$c = new Container;
$c->h = function() {
return function($name) {
echo "Hello " . $name . PHP_EOL;
};
};
$hello = $c->h;
$hello("Bert");
// third test
class MyTest {
public $attr;
}
$test = new MyTest;
$test->attr = $c->h;
$test->attr("Ernie");
Test output:
$ php -f test.php
Hello World
Hello Bert
PHP Fatal error: Call to undefined method MyTest::attr() in /home/greg/test.php on line 53

PHPUnit how to mock newly created model

How I'm stuck with writing a test for the following code. I want to mock the $userModel but how can I add this to the test?
class PC_Validate_UserEmailDoesNotExist extends Zend_Validate_Abstract
{
public function isValid($email, $context = NULL)
{
$userModel = new Application_Model_User();
$user = $userModel->findByEmailReseller($email, $context['reseller']);
if ($user == NULL) {
return TRUE;
} else {
return FALSE;
}
}
}
Update: the Solution
I did change my class to the following to get it testable, it now uses dependency injection. More information about dependency injection you van find out here
I now call the class like this:
new PC_Validate_UserEmailDoesNotExist(new Application_Model_User()
The refactored class
class PC_Validate_UserEmailDoesNotExist extends Zend_Validate_Abstract
{
protected $_userModel;
public function __construct($model)
{
$this->_userModel = $model;
}
public function isValid($email, $context = NULL)
{
if ($this->_userModel->findByEmailReseller($email, $context['reseller']) == NULL) {
return TRUE;
} else {
return FALSE;
}
}
}
The unit test
class PC_Validate_UserEmailDoesNotExistTest extends BaseControllerTestCase
{
protected $_userModelMock;
public function setUp()
{
parent::setUp();
$this->_userModelMock = $this->getMock('Application_Model_User', array('findByEmailReseller'));
}
public function testIsValid()
{
$this->_userModelMock->expects($this->once())
->method('findByEmailReseller')
->will($this->returnValue(NULL));
$validate = new PC_Validate_UserEmailDoesNotExist($this->_userModelMock);
$this->assertTrue(
$validate->isValid('jef#test.com', NULL),
'The email shouldn\'t exist'
);
}
public function testIsNotValid()
{
$userStub = new \Entities\User();
$this->_userModelMock->expects($this->once())
->method('findByEmailReseller')
->will($this->returnValue($userStub));
$validate = new PC_Validate_UserEmailDoesNotExist($this->_userModelMock);
$this->assertFalse(
$validate->isValid('jef#test.com', NULL),
'The email should exist'
);
}
}
Short answer: you cant, because you hardcoded the dependency into the method.
There is three workarounds for this:
1) Make the used classname configurable, so you can do something like:
$className = $this->userModelClassName;
$userModel = new $className();
or 2) Add a third param to the method signature that allows passing in the dependency
public function isValid($email, $context = NULL, $userModel = NULL)
{
if($userModel === NULL)
{
$userModel = new Application_Model_User();
}
// ...
}
or 3) use set_new_overload() as described in
http://sebastian-bergmann.de/archives/885-Stubbing-Hard-Coded-Dependencies.html
http://github.com/sebastianbergmann/php-test-helpers
Note: the Test-Helper extension is superseded by https://github.com/krakjoe/uopz

Categories