Cloning proxied Doctrine 2 entities
Simply using $cloned = clone $proxy; and persisting does not work.
To get round this, you can do the following (contrived example ahoy):
$user = $em->find('User', 1);
$clonedUser = clone $user;
$addressRef = $user->getAddress()->getRef();
// remove address proxy from identity map to ensure we get actual entity
$em->getUnitOfWork()->removeFromIdentityMap($user->getAddress());
$address = $em->find('Address', $addressRef);
$clonedAddress = clone $address;
$clonedUser->setAddress($clonedAddress);
There is definitely a better way of doing this, after trying different approaches and a fair amount of googling I haven't found one though.
Any comments, criticisms or alternative solutions welcomed!
Integrating Zend Framework 1 and Pimple
This post will describe a way to integrate Zend Framework 1 and Pimple. A complete working version of the code is available on github.
Thankfully, Zend Framework 2 features its own Dependency Injection Container. Happy days. Still, if you're not prepared to wait, you may find this useful.
What is Pimple?
Pimple is a small Dependency Injection Container for PHP 5.3 that consists of just one file and one class (about 50 lines of code).
If you aren't too familiar with Dependency Injection and why it's useful, check out these slides by Fabien Potencier.
Why bother?
It allows you to mock dependencies in your controllers within your controller tests. In this example, the Doctrine 2 entity manager is added into the container. If you're just after Zend Framework 1 and Doctrine 2 integration, you will certainly find this post from the Mayflower blog helpful.
Add a bootstrap resource plugin for the container
The bootstrap resource plugin in this example adds the entity manager closure into Pimple. The init method returns the container, thus it can be retrieved from within a controller.
More information on resource plugins
// library/Example/Application/Resource/Container.php
namespace Example\Application\Resource;
use Zend_Application_Resource_ResourceAbstract,
Doctrine\ORM\EntityManager,
Doctrine\ORM\Tools\Setup;
class Container extends Zend_Application_Resource_ResourceAbstract
{
public $_explicitType = 'container';
protected $container;
public function __construct($options = null, $container = null)
{
parent::__construct($options);
$this->container = $container;
}
public function init()
{
$options = $this->getBootstrap()->getOptions();
$db = $options['db'];
$this->container['entityManager'] = $this->container->share(function ($c) use ($db) {
$isDevMode = APPLICATION_ENV == 'development';
$config = Setup::createAnnotationMetadataConfiguration(array(APPLICATION_PATH . '/models'), $isDevMode);
return EntityManager::create($db, $config);
});
return $this->container;
}
}
Resource plugins can be added from the application configuration. However, it's important that it is instantiated outside of the configuration to allow injection of the Pimple container. This is an extremely important step for testing.
// public/index.php
// ...
$resource = new \Example\Application\Resource\Container(null, new Pimple);
$application->getBootstrap()->registerPluginResource($resource);
$application->bootstrap()
->run();
Create the controller
Within the action, the container is retrieved from the bootstrap.
// application/controllers/IndexController.php
class IndexController extends Zend_Controller_Action
{
public function indexAction()
{
$bootstrap = $this->getInvokeArg('bootstrap');
$container = $bootstrap->getResource('container');
$em = $container['entityManager'];
$this->view->items = $em->getRepository('Application_Model_Item')->findAll();
}
}
Add a test for the controller
I've used the PHPUnit mock library for the example. Mockery is better though. Didn't want to throw another library into the mix!
// tests/application/controllers/IndexController.php
class IndexControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{
protected $container;
protected function setUp()
{
$this->bootstrap = new Zend_Application(APPLICATION_ENV, APPLICATION_PATH . '/configs/application.ini');
$this->container = new Pimple;
$resource = new Example\Application\Resource\Container(null, $this->container);
$this->bootstrap->getBootstrap()->registerPluginResource($resource);
parent::setUp();
}
public function testIndexAction()
{
$items = array(
$this->createItem('test1', 'test1value'),
$this->createItem('test2', 'test2value'),
);
// mock the entity repository
$repo = $this->getMockBuilder('Doctrine\ORM\EntityRepository')
->disableOriginalConstructor()
->getMock();
$repo->expects($this->once())
->method('findAll')
->will($this->returnValue($items));
// mock the entity manager
$em = $this->getMockBuilder('Doctrine\ORM\EntityManager')
->disableOriginalConstructor()
->getMock();
$em->expects($this->once())
->method('getRepository')
->with($this->equalTo('Application_Model_Item'))
->will($this->returnValue($repo));
// replace the entity manager closure in the container
$this->container['entityManager'] = $em;
$this->dispatch('/');
$this->assertQueryContentContains("h1", "Items");
$this->assertQueryContentContains('ul li', 'test1: test1value');
$this->assertQueryContentContains('ul li', 'test2: test2value');
}
protected function createItem($name, $value)
{
$item = new Application_Model_Item;
$item->setName($name);
$item->setValue($value);
return $item;
}
}
Improvements
- Populate Pimple outside of the bootstrap resource plugin. This step allows you to have an empty container during testing (no forgetting to replace objects in the container).
- Allow easier access to the container via an action helper or by extending Zend_Controller_Action.
Implementing Twitter sign-in with Silex and PHP
Set-up
- Install pecl oauth
- Grab the Silex
.phararchive - Create a Twitter application
pecl oauth
Install the pecl oauth extension
For example:
$ pecl install oauth
Silex
For those not acquainted with Silex:
Silex is a PHP microframework for PHP 5.3
A microframework provides the guts for building simple single-file apps.
It's awesome.
All you need is the silex.phar file and you're away. Download it.
For the example, I've setup a virtual host of example.local on my development machine. The apache module mod_rewrite and a .htaccess file are used to rewrite requests to index.php.
# .htaccess
<IfModule mod_rewrite.c>
Options -MultiViews
RewriteEngine On
#RewriteBase /path/to/app
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [QSA,L]
</IfModule>
Create a Twitter application
Once you've created your application, you'll need your consumer key and consumer secret. These are used to sign every request made to the API.
Implementation
A complete summary along with a pretty diagram of what we're trying to achieve can be found here.
Basic OAuth request cycle:
- Retrieve a request token
- Request authorization
- Exchange the request token for an access token
More information - https://dev.twitter.com/docs/auth/oauth
Basic Silex application
// index.php
define('CONS_KEY', 'Application consumer key');
define('CONS_SECRET', 'Application consumer secret');
require_once __DIR__.'/silex.phar';
$app = new Silex\Application();
// register the session extension
$app->register(new Silex\Extension\SessionExtension());
$app->get('/', function() use($app) {
$username = $app['session']->get('username');
if ($username == null) {
return 'Welcome Guest. <a href="/login">Login</a>';
} else {
return 'Welcome ' . $app->escape($username);
}
});
$app->run();
Add the /login route
The first part of the process requires the retrieval of a request token. Authorization is requested by redirecting to https://twitter.com/oauth/authenticate.
// index.php
$app->get('/login', function () use ($app) {
// check if the user is already logged-in
if (null !== ($username = $app['session']->get('username'))) {
return $app->redirect('/');
}
$oauth = new OAuth(CONS_KEY, CONS_SECRET, OAUTH_SIG_METHOD_HMACSHA1, OAUTH_AUTH_TYPE_URI);
$request_token = $oauth->getRequestToken('https://twitter.com/oauth/request_token');
$app['session']->set('secret', $request_token['oauth_token_secret']);
return $app->redirect('https://twitter.com/oauth/authenticate?oauth_token=' . $request_token['oauth_token']);
});
Add the /auth route
In my example setup the URL would be http://example.local/auth. Set your Twitter application callback URL in the app settings to be the auth route.
Within this action, the request token is exchanged with the access token which allows usage of the Twitter API on behalf of the user.
// index.php
$app->get('/auth', function() use ($app) {
// check if the user is already logged-in
if (null !== ($username = $app['session']->get('username'))) {
return $app->redirect('/');
}
$oauth_token = $app['request']->get('oauth_token');
if ($oauth_token == null) {
$app->abort(400, 'Invalid token');
}
$secret = $app['session']->get('secret');
$oauth = new OAuth(CONS_KEY, CONS_SECRET, OAUTH_SIG_METHOD_HMACSHA1, OAUTH_AUTH_TYPE_URI);
$oauth->setToken($oauth_token, $secret);
try {
$oauth_token_info = $oauth->getAccessToken('https://twitter.com/oauth/access_token');
} catch (OAuthException $e) {
$app->abort(401, $e->getMessage());
}
// retrieve Twitter user details
$oauth->setToken($oauth_token_info['oauth_token'], $oauth_token_info['oauth_token_secret']);
$oauth->fetch('https://twitter.com/account/verify_credentials.json');
$json = json_decode($oauth->getLastResponse());
$app['session']->set('username', $json->screen_name);
return $app->redirect('/');
});
Resources
DbUnit talk at PHPSW in Bristol
I had the pleasure of talking to the PHPSW group in Bristol on Wednesday (13/04/2011). Considering it was my first talk, I felt it went quite well. Had some good comments :) and only small amounts of heckling. Overall a really good experience and something I'd definitely like to do again.
The talk was about the PHPUnit database code testing extension, DbUnit. I'm really fond of this extension and it seems to have a lack of decent documentation on the 'net. Ever since Benjamin Eberlei penned a comprehensive tutorial on it, I've been hooked.
Resources:
Testing LAMP applications by Sebastian Bergmann
Testing PHP/MySQL Applications with PHPUnit/DbUnit by Sebastian Bergmann
Database Testing with PHPUnit and MySQL by Matthew Turland
Logging queries in Doctrine 1.2
The developers of Doctrine have made the task of logging queries trivial, thanks to their event listener architecture.
One just needs to create a class which extends Doctrine_EventListener:
class QueryDebuggerListener extends Doctrine_EventListener
{
protected $queries;
public function preStmtExecute(Doctrine_Event $event)
{
$query = $event->getQuery();
$params = $event->getParams();
//the below makes some naive assumptions about the queries being logged
while (sizeof($params) > 0) {
$param = array_shift($params);
if (!is_numeric($param)) {
$param = sprintf("'%s'", $param);
}
$query = substr_replace($query, $param, strpos($query, '?'), 1);
}
$this->queries[] = $query;
}
public function getQueries()
{
return $this->queries;
}
}
And add the event listener:
$c = Doctrine_Manager::connection($conn);
$queryDbg = new QueryDebuggerListener();
$c->addListener($queryDbg);
I like to use the above to get a feel for how many queries are being performed.
For example, I was using $foo->Bar['field'] to retrieve a field value from a relation at one point. The code can result in another query, which could be avoided by using a join on the original query for the record. See the Doctrine documentation for a clearer example.
Redirecting from an old route in Zend Framework
Firstly ensure your old route has been created:
routes.new.route = "new"
routes.new.defaults.controller = index
routes.new.defaults.action = new
routes.new.defaults.module = default
routes.new.defaults.id = ""
routes.new.reqs.id = \d+
routes.old.route = "old"
routes.old.defaults.controller = index
routes.old.defaults.action = old
routes.old.defaults.module = default
routes.old.defaults.id = ""
routes.old.reqs.id = \d+
In the controller:
class IndexController extends Zend_Controller_Action
{
...
public function oldAction()
{
$redirector = $this->_helper->getHelper('Redirector');
$redirector->setCode(301);
return $redirector->gotoRoute($this->_getAllParams(), 'new');
}
}
For reference, examples developed with Zend Framework 1.11.2
Setting a default page title in Zend Framework and modifying per action
There may be a better way of doing this, I'm open to suggestions. Still, my goal was to have a default page title, manipulating when required in an action.
I initially tried to just use $this->headTitle('foobar'); in my layout and prepend in the controller action. This didn't work out too well, due to the order in which they're executed - append and prepend are reversed. Not too intuitive.
Therefore, I created an _init method in the Bootstrap class:
public function _initTitle()
{
$view = $this->bootstrap('view')->getResource('view');
$view->headTitle('foobar');
}
Modifying the title in the action is very simple:
public function fooAction()
{
$this->view->headTitle()->prepend('foo | ');
}
Accessing the foo action will yield:
<title>foo | foobar</title>
For reference example was written with Zend Framework 1.11.2.
Using Zend_Form_Decorators to compose a form as table rows
Ahh Zend form decorators, they certainly have a reputation of being quite difficult to pick up. I suffered similar problems but persevered to get the table layout I wanted.
$form->setElementDecorators(array(
'ViewHelper',
array('Description', array('tag' => 'span')),
array('Errors'),
array('HtmlTag', array('tag' => 'td')),
array('Label', array('tag' => 'th', 'requiredSuffix' => ' *')),
array(array('row' => 'HtmlTag'), array('tag' => 'tr')),
));
The above code will result in the following (albeit not quite as neatly):
<tr>
<th>Label *</th>
<td>
<input type="text" /><span class="hint">Description</span>
<ul class="errors"><li>Error</li></ul>
</td>
</tr>
For reference example was written with Zend Framework 1.11.2.
Exporting a git tag in a Phing target and subsequently processing the files
Composing releases with git archive is very useful, especially when making use of the .gitattributes file to select what is archived.
One way to make use of this command in Phing, is to do the following:
<target name="build" depends="prepare">
<exec command="git archive ${git_tag} | tar -x -C ./_build" />
</target>
This allows processing of the archived files. For example, the target could be expanded to strip PHP comments:
<target name="build" depends="prepare">
<exec command="git archive ${git_tag} | tar -x -C ./_build" />
<reflexive>
<fileset dir="./_build">
<include pattern="**/*.php" />
</fileset>
<filterchain>
<stripphpcomments />
</filterchain>
</reflexive>
</target>
PHPUnit code coverage output in Windows including PHPUnit classes
Having just to got to the point where I was happy with my distributed project builds on Windows and Linux, I discovered my code coverage reports generated by PHPUnit were different between the two.
On the Linux machine, just the classes I expected were included in the report. On Windows however, it included some of the PHPUnit classes.
Turns out I had to create a phpunit.xml configuration file and include a whitelist of files.
For example:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit>
<filter>
<whitelist>
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
<logging>
<log type="junit" target="./build/logs/testsuites.xml"/>
<log type="coverage-clover" target="./build/logs/coverage-clover.xml"/>
</logging>
</phpunit>