Keeping Your Clean URL After A Zend_Controller_Action Redirect

If you need to redirect from within your controller all you have to do is:


class myController extends Zend_Action_Controller
{
public function indexAction()
{
// "goto/here => gotoController:hereAction
$this->_redirect("/goto/here");
}
}

This works, but leaves you with your ugly url:


http://mywebsite/index.php/goto/here

To fix this, just set the preprendBase value to false:


$this->_redirect("/goto/here", array('prependBase' => false);
// Now http://mywebsite/goto/here

Zend’s documentation wasn’t very helpful with this. The API doesn’t even mention what parameters are available to set. The reference guide does, but doesn’t tell you any of the default settings.

Calling A Zend_Controller_Action_Helper within another Zend_Controller_Action_Helper

I’m not sure if this falls under good coding practices, but if you need access to one of your helper functions from within another one just get a static instance of it:


$helper = Zend_Controller_Action_HelperBroker::getStaticHelper('myOtherHelper');

This would come in handy if you’re not using the MVC design pattern (ie. no controller access) But for an MVC example of how to do this:

Example: HelloWold Helper combines Hello Helper and World Helper.

Hello Helper:

class Custom_Controller_Action_Helper_Hello extends Zend_Controller_Action_Helper_Abstract
{
/**
* @var Zend_Loader_PluginLoader
*/
public $pluginLoader;

/**
* Constructor: initialize plugin loader
*
* @return void
*/
public function __construct()
{
$this->pluginLoader = new Zend_Loader_PluginLoader();
}

public function direct()
{
return "Hello";
}
}

World Helper:

class Custom_Controller_Action_Helper_World extends Zend_Controller_Action_Helper_Abstract
{
/**
* @var Zend_Loader_PluginLoader
*/
public $pluginLoader;

/**
* Constructor: initialize plugin loader
*
* @return void
*/
public function __construct()
{
$this->pluginLoader = new Zend_Loader_PluginLoader();
}

public function direct()
{
return "World";
}
}

HelloWorld Helper

class Custom_Controller_Action_Helper_HelloWorld extends Zend_Controller_Action_Helper_Abstract
{
/**
* @var Zend_Loader_PluginLoader
*/
public $pluginLoader;

/**
* Constructor: initialize plugin loader
*
* @return void
*/
public function __construct()
{
$this->pluginLoader = new Zend_Loader_PluginLoader();
}

public function direct()
{
$hello = Zend_Controller_Action_HelperBroker::getStaticHelper('hello');
$world= Zend_Controller_Action_HelperBroker::getStaticHelper('world');
return "$hello $world!!!";
}
}

Use the helpers to say Hello World. http://localhost/hello-world/speak

class HelloWorldController extends Zend_Controller_Action
{
public function speakAction()
{
echo $this->_helper->helloWorld(); // Hello World!!!
}
}

Zend_Mime_Decode::decodeQuotedPrintable not decoding?

I’ve set up my testing environment to save emails to a file instead of actually sending them. (I extended Zend_Mail_Transport_File to send mail to a file instead.)

Well an encoded mail file looks as follows:

=0D=0AHello Brian Strickland,

=0D=0A=0D=0Adept editor has=
requested Department - Editor access for Department of=
Art. A user with Department - Editor access will be a=
ble to do the following:=0D=0A

    =0D=0A=09

  • Update any Department=

    In my tests, I assert to make sure that the correct contents are in the email message, but before you can do that you need to decode the file. I think you’re suppose to do it like so:

    $body = "";
    Zend_Mime_Decode::splitMessage(file_get_contents($emailFile), $headers, $body);
    print Zend_Mime_Decode::decodeQuotedPrintable($body);

    But this actually doesn’t change at all, it will just print the encoded message. So I stepped back from the framework and just used PHP’s method:


    Zend_Mime_Decode::splitMessage(file_get_contents($emailFile), $headers, $body);
    print quoted_printable_decode($body);

    This removes all of the =0D=0A and =’s which is what I expected Zend_Mime_Decode::decodeQuotedPrintable to do. Anywho, maybe I’m just using Zend_Mime_Decode wrong, but I submitted the report to Zend anyhow (http://framework.zend.com/issues/browse/ZF-10236). It’s been two months and they didn’t throw it out, so maybe they will actually look into this one 😉

Can’t convert Datetime object to string

I had our system admin upgrade our MS SQL driver to the actual Sqlsrv Driver for PHP 1.1 . We were previously using PDO for mssql server. (http://us.php.net/pdo-dblib). The main reason I needed him to changed it was because my unit testing was encounter some problems while doing some database operations. (I can’t remember what they were exactly)

Once the new driver was installed all my old problems went away, but as usual new problems arose. I kept getting errors on all of my database transactions that involved storing/getting Sql Server datetime types. (Can’t convert Datetime object to string).

To fix this I updated my XML database configuation options to include the following:



true

Then when creating my database connection with Zend, I passed these driver options as a parameter:


$db = Zend_Db::factory($this->_config->database->mssql->adapter, array(
'adapterNamespace' => 'Custom_Zend_Db_Adapter',
'host' => $this->_config->database->mssql->host,
'username' => $this->_config->database->mssql->user,
'password' => $this->_config->database->mssql->password,
'dbname' => $this->_config->database->params->dbname,
'driver_options' => $this->_config->database->mssql->driver_options->toArray()
));

Wala, no more errors.

Good source:
http://msdn.microsoft.com/en-us/library/cc296208%28SQL.90%29.aspx
http://msdn.microsoft.com/en-us/library/ee376928%28SQL.90%29.aspx

Authorization Check Action Helper Plugin

NOTICE: Apparently you cannot use any redirect methods that call exit() if you plan on doing unit testing with redirects. Refer to http://zendframework.com/issues/browse/ZF-7496 for more information. I’ve updated and commented what I changed to work around this issue.

So a common thing that you are going to want to do in you Zend applications is redirect a user to the login page if they try to go to a restricted area while not logged in. The most common way I’ve seen is to just do the following in your controller’s action method:


if(!Zend_Auth::getInstance()->hasIdentity()){
$this->_redirect($url);
exit();
}

And that is what I do, but I don’t want to have to write that in every single action method in ever controller class of mine. One way to overcome duplying that code is to just create a parent class with a authorizationCheck method and have each of your restricted controllers extend this parent class. Now all you have to do is call


parent::authorizationCheck($url);

whenever you need to do a check. The only problem with this is that now all your controllers have to sub class this parent and you may start to add functions to the parent class that not all of your controllers will use, so this will just bloat your objects and/or cause a re factoring nightmare. So I’ve got another approach, just use an Action Helper Plugin.

This may not be the appropriate way to do things, but “appropriate” is not a constant in the programming world 😉

First off we need to create a new plugin that extends the Zend_Controller_Action_Helper_Abstract class:


pluginLoader = new Zend_Loader_PluginLoader();
}

/**
* Check to see if the user is authenticated. If they aren't redirect to
* the link given.
*
* @param array | Zend_Config $options
*/
public function authenticated($options = NULL)
{
if(!Zend_Auth::getInstance()->hasIdentity()){

// My config's always know the location of the login.
$redirector = Zend_Controller_Action_HelperBroker::getStaticHelper('redirector');
$redirector->gotoUrl(
Zend_Registry::get('config')->webhost.Zend_Registry::get('config')->links->login);
return false;
// $redirector->redirectAndExit(); DO NOT CALL THIS IF DOING UNIT TEST, JUST RETURN.
}
return true;
}

/**
* Allows you to do $this->_helper->authorizationCheck() in your controllers.
*
* @param array | Zend_Config $options
*/
public function direct($options = NULL)
{
$this->authenticated($options);
}
}

All that class does is create a method that you can call from your controllers that will check if the user is logged in, and if not, redirect them to the login page.

After you’ve created this class, you need to load the plugin which is typically done in the bootstap (well not in the .ini files, but I haven’t converted to that style yet.)


protected function _initFrontController(
{
//...
Zend_Controller_Action_HelperBroker::addPrefix('Custom_Zend_Controller_Action_Helper');
//...
}

And now all you have to do in an action method is call the plugin’s authorizationCheck:


public function removeuserAction()
{
if(!$this->_helper->authorizationCheck()) return; // If the user isn't authenticated yet, they will be redirected to the login page. Returning here stops any future redirects from over writing. (This is usually stopped by redirectAndExit(), but this can't be used with unit tests.
// ... do what this action is suppose to do
}

How To Disable The Layout When Doing Ajax Calls To An MVC Controller

If you want to have you ajax functions call actions from your controllers, your response will be whatever the page looks like if you visited the link. For example if you go to http://mysite/authors/update the response will serve up views/scripts/authors/update.phtml (assuming you don’t have custom config settings). This will include all content in the update.phtml AND any content from your layout script.

If you don’t want the extra layout content, just display within the controller action (updateAction())


// Do this unless you want the layout too 😛
$layout = Zend_Layout::getMvcInstance();
$layout->disableLayout();

Now you’ll just get whatever is in update.phtml.

Testing for Exception in a Zend_Test_PHPUnit_ControllerTestCase

One of the typical ways you would test to see if an exception was raised in a unit test is as follows:


try {
$this->dispatch('some/place/where/exception/is/thrown');
}
catch(Exception $e){}

$this->fail('No exception thrown...');

This won’t work however when dealing with controllers in an MVC enivornment. The reason is is that the controller captures all of the exceptions that are thrown so PHPUnit will never know about them.

To try to get around this problem I first tried


$this->getFrontController()->throwExceptions(false);

It seems like that it would do the trick, but all that I think that that did was just ignore exceptions, so still nothing will reach PHPUnit.

Thankfully the controller does keep a list of all the exceptions that are thrown AND you can access them. So looking for a certain type of except is as easy as calling the method hasExceptionOfType(). There is also another method to just check if any exception has been through (refer to Zend’s API).

Here is the solution to make the code in the beginning work:


public function testIfUserAlreadyExistsAnExceptionWillBeThrown()
{
print __METHOD__."n";

// Setup db.
$db = $this->setUpDatabase();

// Grab new user to add.
$user = $this->_getNewRegister();
$user['username'] = 'myUser'; // make username same as setup

$this->request->setMethod('POST')->setPost($user);

// Get a handle on the response to test for any exceptions.
$response = $this->getResponse();

// Attempt to add user.
$this->dispatch('/user/register');

// If exception is not thrown, fail.
if(!$response->hasExceptionOfType('User_Exception_AlreadyExists'))
$this->fail('User_Exception_AlreadyExists expected.');
}

Zend_Test_PHPUnit_Db_Operation_Truncate Fails On A MS SQL Server Table With A Foreign Key Relation

While doing some database testing with Zend_Test_PHPUnit_Db_SimpleTester I encountered a problem with the setups of a table that have one or more relations to it. The problem is that during setup I’m trying to TRUNCATE the table but SQL Server does not allow you to TRUNCATE a table that has relationships (http://msdn.microsoft.com/en-us/library/ms177570.aspx)

I submitted this problem to Zend but they said something about the order in my XML file is wrong and that I have to make sure that the related tables are truncated first… They said:

Zend_Test_PHPUnit_Db expects you to sort the tables in the XML files in an order that allows for a non-conflicting FK truncation. Since it cannot know which is the correct order without doing lots of inference.

If you don’t want that you can add your own Set Up Operation using PHPUnit’s APIs.

Here’s my XML file






So users roles gets truncated first and then users. TRUNCATE works on roles, but then the operation fails when trying to do users, why? Because of the explanation above, SQL Server DOES NOT ALLOW IT. I forgot to mention that in my original report, but left a comment (http://framework.zend.com/issues/browse/ZF-9888) They haven’t responded…

Anywho, so for the work around I did the following:

1) Extended the SimpleTester class. You need to do this because the $setUpOperation is a protected property


connection = $connection;

// Use custom truncate operation.
$this->setUpOperation = new PHPUnit_Extensions_Database_Operation_Composite(array(
new Custom_Zend_Test_PHPUnit_Db_Operation_Truncate_Mssql(),
new Zend_Test_PHPUnit_Db_Operation_Insert(),
));
$this->tearDownOperation = PHPUnit_Extensions_Database_Operation_Factory::NONE();
}

}

2) Extended the Truncate class


quoteIdentifier($tableName);

// This will wipe out all foreign key relations.
$db->query('DELETE FROM '.$tableName);

// Reset the seed (TRUNCATE would do this)
$db->query('DBCC CHECKIDENT('.$tableName.', RESEED, 0)');
}
}

3) Call my simple tester from my controller test


$config = Zend_Registry::get('config')->database;

$db = Zend_Db::factory($config->adapter, array(
'host' => $config->mssql->host,
'username' => $config->mssql->user,
'password' => $config->mssql->password,
'dbname' => $config->params->dbname,
'adapterNamespace' => 'Custom_Zend_Db_Adapter'
));

$connection = new Zend_Test_PHPUnit_Db_Connection($db, 'users');

// Create your custom tester
$dbTester = new Custom_Zend_Test_PHPUnit_Db_SimpleTester($connection);

$dbFixture =
new PHPUnit_Extensions_Database_DataSet_FlatXmlDataSet(dirname(__FILE__) . '/xml/user/seed.xml');

// Will not work because setUpOperation is a protected property.
//$dbTester->setUpOperation = new PHPUnit_Extensions_Database_Operation_Composite(array(
// new Custom_Zend_Test_PHPUnit_Db_Operation_Truncate_Mssql(),
// new Zend_Test_PHPUnit_Db_Operation_Insert(),
//));

$dbTester->setupDatabase($dbFixture);