tag:blogger.com,1999:blog-58425449916106182142024-03-04T23:22:12.165-08:00michael.smith.onlineMichaelhttp://www.blogger.com/profile/00120635852338138179noreply@blogger.comBlogger2125tag:blogger.com,1999:blog-5842544991610618214.post-62823844102589018702013-09-05T09:16:00.000-07:002013-11-11T19:52:27.433-08:00Symfony2 Behat and Emails<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUD2VXdd080DJef8fib5fC9FnPT5lnzCpUvWxyq1aCKIAbm7KDdflpoEITGJ_RmRzd7b3PXvZfU67PL9cGYk7sfC_cDbP0mZExySiOJqpibz0dAIKfuZZ1gpTEQKHpJejfic6GAuNI11JI/s1600/behat_email.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUD2VXdd080DJef8fib5fC9FnPT5lnzCpUvWxyq1aCKIAbm7KDdflpoEITGJ_RmRzd7b3PXvZfU67PL9cGYk7sfC_cDbP0mZExySiOJqpibz0dAIKfuZZ1gpTEQKHpJejfic6GAuNI11JI/s320/behat_email.png" width="315" /></a>I've been using <a href="http://behat.org/">Behat</a> with my <a href="http://symfony.com/">Symfony</a> apps for some time now but recently I needed to include emails in my features. I found tutorials at <a href="http://docs.behat.org/cookbook/using_the_profiler_with_minkbundle.html">http://docs.behat.org/cookbook/using_the_profiler_with_minkbundle.html</a> and <a href="http://docs.behat.org/cookbook/intercepting_the_redirections.html">http://docs.behat.org/cookbook/intercepting_the_redirections.html</a> but they did not work out of the box for me. I guess they are now slightly out of date. After some time searching the docs and source code. I found that I needed to enable the profiler in the test environment:<br />
<pre><code>
# app/config/config_test.yml
framework:
profiler:
enabled: true
</code></pre>
<br />
I tried leaving this set to false and enabling the the profiler for the next request with a step but it always said the profiler was diabled<br />
<pre><code>
# src/.../...Bundle/Features/Context/FeatureContext.php
/**
* @When /^(?:|I )enable the profiler$/
*/
public function iEnableProfiler()
{
$driver = $this->getSession()->getDriver();
$driver->getClient()->enableProfiler();
}
</code></pre>
<br />
Also I needed to change the instance of checks from GoutteDriver and SymfonyDriver to KernelDriver. Also I did not need to tag the scenario @mink:symfony as the default session in behat.yml is set to symfony2. Below is what I ended up with. Hope this helps someone else.<br />
<pre><code>
# src/.../...Bundle/Features/Context/FeatureContext.php
public function getSymfonyProfile()
{
$driver = $this->getSession()->getDriver();
if (!$driver instanceof KernelDriver) {
throw new UnsupportedDriverActionException(
'You need to tag the scenario with '.
'"@mink:symfony". Using the profiler is not '.
'supported by %s', $driver
);
}
$profile = $driver->getClient()->getProfile();
if (false === $profile) {
throw new \RuntimeException(
'Emails cannot be tested as the profiler is '.
'disabled.'
);
}
return $profile;
}
public function canIntercept()
{
$driver = $this->getSession()->getDriver();
if (!$driver instanceof KernelDriver) {
throw new UnsupportedDriverActionException(
'You need to tag the scenario with '.
'"@mink:goutte" or "@mink:symfony". '.
'Intercepting the redirections is not '.
'supported by %s', $driver
);
}
}
</code></pre>
<br />
<pre><code>
# src/.../...Bundle/Features/....feature
Scenario: Send an email
Given I am on the homepage
When I fill in ...
And I press "Send message" without redirection
Then I should get an email to "..." with "..."
And I should be redirected
</code></pre>
Michaelhttp://www.blogger.com/profile/00120635852338138179noreply@blogger.com1tag:blogger.com,1999:blog-5842544991610618214.post-72853835655509712492012-03-28T19:25:00.000-07:002013-11-11T19:52:50.500-08:00Geocoding in Symfony2 and Doctrine2<div>
<img align="left" border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEil31P3yWLCawgNkAvkAPrrywnx2Sd5pq52qeNh_DkksorRItv9o553lzmEvqU-BCPpa4H-kj5OE9UJeMhlppw8eQWtjQ5wfddmTZdVJfAYKrBP4cw6xzGeX09W13JpZPYj2iNyD1VLJBhF/s400/geocode.jpg" width="300" />
<br />
I needed to add latitude and longitude fields in an entity so I could plot it to a map and figure distances etc. This is a pretty common thing but I wanted to make it as simple and elegant as possible using Symfony2 and Doctrine2.<br />
To start with I selected a geocoding library instead of trying to roll my own, remember we are keeping this simple. I choose <a href="https://github.com/willdurand/Geocoder">Geocoder</a> which has great features and is dead simple to use. First we need to install the Geocoder and an http adapter for it (I used <a href="https://github.com/kriswallsmith/Buzz">Buzz</a> a great project on its own) into our Symfony2 project.</div>
<div style="clear: both;">
</div>
<pre><code># deps
[geocoder]
git=http://github.com/willdurand/Geocoder.git
[buzz]
git=http://github.com/kriswallsmith/Buzz.git
</code></pre>
<pre><code># app/autoload.php
$loader->registerNamespaces(array(
//....
'Geocoder' => __DIR__.'/../vendor/geocoder/src',
'Buzz' => __DIR__.'/../vendor/buzz/lib',
));
</code></pre>
<div>
and then run</div>
<pre><code>php bin/vendors install
</code></pre>
<div>
That's it for install, easy right? Now to use it. You could just follow the docs and create an instance whenever you needed to use but this would require you configure it each time; not what I call elegant. Symfony2's Service Container or DIC (dependency injection container) to the rescue.</div>
<pre><code># app/config/config.yml
services:
geocoder.adapter:
class: Geocoder\HttpAdapter\BuzzHttpAdapter
geocoder.address:
class: Geocoder\Provider\GoogleMapsProvider
arguments: [@geocoder.adapter]
geocoder.ip:
class: Geocoder\Provider\FreeGeoIpProvider
arguments: [@geocoder.adapter]
</code></pre>
<div>
Symfony2's Service Container is a great feature you can find out more <a href="http://symfony.com/doc/current/book/service_container.html">here</a>. Now we can use this service from a controller action or anywhere with the service container.</div>
<pre><code>$geocoder = $this->get('geocoder.address');
var_dump($geocoder->getGeocodedData('41 East 4th Street Cookeville, TN 38501'));
var_dump($this->get('geocoder.ip')->getGeocodedData('8.8.8.8'));
</code></pre>
<div>
Now with only a couple of minutes of work, we have it working and able to geocode physical addresses or ip addresses. Now I could edit the entity's controller action and set the latitude and longitude whenever it was saved but I wanted something more reusable and elegant. Enter <a href="http://docs.doctrine-project.org/projects/doctrine-orm/en/2.0.x/reference/events.html">Doctrine2 events</a>.</div>
<pre><code># src/MS/RentrBundle/Doctrine/Event/GeocoderEventSubscriber.php
namespace MS\RentrBundle\Doctrine\Event;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Geocoder\Provider\ProviderInterface;
/**
* Subscribes to Doctrine prePersist and preUpdate to update an Apartment's latitude and longitude
*
* @author msmith
*/
class GeocoderEventSubscriber implements EventSubscriber {
protected $geocoder;
public function __construct(ProviderInterface $geocoder){
$this->geocoder = $geocoder;
}
/**
* Specifies the list of events to listen
*
* @return array
*/
public function getSubscribedEvents(){
return array(
'prePersist',
'preUpdate',
);
}
/**
* Sets a new Apartment's latitude and longitude if not present
*
* @param LifecycleEventArgs $eventArgs
*/
public function prePersist(LifecycleEventArgs $eventArgs){
if(($apartment = $eventArgs->getEntity()) instanceof \MS\RentrBundle\Entity\Apartment){
if( !$apartment->latitude || !$apartment->longitude){
$this->geocodeApartment($apartment);
}
}
}
/**
* Sets an updating Apartment's latitude and longitude if not present
* or any part of address updated
*
* @param PreUpdateEventArgs $eventArgs
*/
public function preUpdate(PreUpdateEventArgs $eventArgs){
if(($apartment = $eventArgs->getEntity()) instanceof \MS\RentrBundle\Entity\Apartment){
if( !$apartment->latitude || !$apartment->longitude
|| $eventArgs->hasChangedField('street') || $eventArgs->hasChangedField('city')
|| $eventArgs->hasChangedField('state') || $eventArgs->hasChangedField('zip')){
$this->geocodeApartment($apartment);
$em = $eventArgs->getEntityManager();
$uow = $em->getUnitOfWork();
$meta = $em->getClassMetadata(get_class($apartment));
$uow->recomputeSingleEntityChangeSet($meta, $apartment);
}
}
}
/**
* Geocode and set the Apartment's latitude and longitude
*
* @param type $apartment
*/
private function geocodeApartment($apartment){
$result = $this->geocoder->getGeocodedData($apartment->getAddress());
$apartment->latitude = $result['latitude'];
$apartment->longitude = $result['longitude'];
}
}
</code></pre>
<pre><code># app/config/config.yml
services:
# ...
geocoder.listener:
class: MS\RentrBundle\Doctrine\Listener\GeocoderEventSubscriber
arguments: [@geocoder.address]
tags:
- { name: doctrine.event_subscriber }
</code></pre>
<div>
The event subscriber is a little more verbose then I would like and you could abstract out the entity to make it reusable for multiple entity types but you would need to allow for configuration which could be accomplished with the service container. The main things to notice it that the service container passes in our configured geocoder instance, prePersist() handles the insert and preUpdate() handles all updates to the entity. To get our new subscriber working with Doctrine2 all we need to do is give it the tag "doctrine.event_subscriber". That's it now we have a full functioning geocoding solution that is simple and elegant.</div>
Michaelhttp://www.blogger.com/profile/00120635852338138179noreply@blogger.com32-16 S Dixie Ave, Cookeville, TN 38501, USA36.162839 -85.501642336.111560999999995 -85.5806063 36.214117 -85.4226783