Behat: expect to send an email - phpunit

Most of the few Behat examples I saw test for an object property, like
/**
* #Then the overall basket price should be £:price
*/
public function theOverallBasketPriceShouldBePs($price)
{
PHPUnit_Framework_Assert::assertSame(
floatval($price),
$this->basket->getTotalPrice()
);
}
But what if my user story goes something like this:
Given, a user has received pdf1
When 48 hours have passed since the download
Then the application must send pdf2 to the user
How am I supposed to test for the #then step in this case - mocking and using PHPUnit expectations? I somehow get the feeling that I completely misunderstand something here.

Since I want my domain object to not have any dependencies, I opted to purely test the domain logic that involves sending messages, nothing more. As far as I understand it, this is what BDD is about. In my Lead entity:
public function isEligibleForNextMessage( int $interval ) {
if ( /* Do Logic */ ) :
return TRUE;
endif;
return FALSE;
}
Then in my LeadContext:
/**
* #Then the application must send pdf2 to the user
*/
public function sendNextMessage() {
Assert::assertTrue( $this->Lead->isEligibleForNextMessage( 172800 ) );
}
This should be be sufficient to test the above user story regarding the time interval.

Related

Symfony-ux LiveComponent

I wanted to try Symfony-ux LIveComponent for a project, a small classic collectionType form with a delete button for each card, an add button to add cards as you go, fill in the generate fields and save everything with a save button.
I used a lot of help from symfony documentation, everything works except data saving, it's a bit annoying, the example I'm going to put below uses symfony documentation but it's not taken into account, maybe I forgot something, but in the current state of things I'm a bit lost.
If someone who has already used the LIve Component of symfony and had this problem would have some time, or just someone available for help it would be great :)
Note that I have no error, the ajax that is triggered is correct.
Here is the creation of my form and its save function:
public Campaign $campaign;
protected function instantiateForm(): FormInterface
{
return $this->createForm(ListMissionFormType::class, $this->campaign);
}
#[LiveAction]
public function save(Request $request, EntityManagerInterface $entityManager): Response
{
$this->submitForm();
$this->validate();
$mission = $this->getFormInstance()->getData();
$entityManager->persist($mission);
$entityManager->flush();
$this->addFlash('success', 'Modification enregistré');
return $this->redirect($request->headers->get('referer'), Response::HTTP_SEE_OTHER);
}

Akeneo 2.2.8 : How can I get the original attribute data in the akeneo.storage.pre_save event?

I'm using Akeneo 2.2.8 and I'm trying to use the akeneo.storage.pre_save-event to compare the original product data with the new data provided. I do this by subscribing to the akeneo.storage.pre_save-event:
In event_subscribers.yml:
parameters:
vendor.bundle.event_subscriber.product_save.class: Vendor\Bundle\CustomBundle\EventSubscriber\ProductSaveSubscriber
services:
vendor.bundle.event_subscriber.product_save:
class: '%vendor.bundle.event_subscriber.product_save.class%'
arguments:
- '#pim_catalog.repository.product'
tags:
- { name: kernel.event_listener, event: akeneo.storage.pre_save, method: onPreSave, priority: 255 }
In ProductSaveSubscriber.php:
/**
* #var ProductRepositoryInterface
*/
protected $productRepository;
public function __construct(ProductRepositoryInterface $productRepository)
{
$this->productRepository = $productRepository;
}
public function onPreSave(GenericEvent $event)
{
/** #var Product $subject */
$subject = $event->getSubject();
if ($subject instanceof Product) {
$originalProduct = $this->productRepository->findOneByIdentifier($subject->getIdentifier());
foreach ($subject->getAttributes() as $attribute) {
if ($attribute->getReadOnly()) {
echo "{$attribute->getCode()} : {$subject->getValue($attribute->getCode())}\n";
echo "{$attribute->getCode()} : {$originalProduct->getValue($attribute->getCode())}\n";
}
}
}
}
Now when I run this code, I expect the second echo-statement to give the original data (since I've loaded that anew). However, the original product I load from the repository also has the new data.
Another thing to note here is that if I add a die()-statement, the data is not stored in the database. So it seems that the repository returns the in-memory model or something like that.
Can anyone point me in the right direction? Or am I using the wrong approach to compare newly-entered data with already existing data?
Now when I run this code, I expect the second echo-statement to give
the original data (since I've loaded that anew). However, the original
product I load from the repository also has the new data.
This might be because the object is referenced in doctrine's unit of work. So when you use the repository to fetch what you think is the original object, might actually be the same object you've updated.
Another thing to note here is that if I add a die()-statement, the
data is not stored in the database. So it seems that the repository
returns the in-memory model or something like that.
That's because since your subscriber is listening on the PRE_SAVE event, the updated product has not been flushed in the database yet. Saving a product goes this way:
PRE_SAVE event thrown
COMMIT / FLUSH
POST_SAVE event thrown
So if you call die during the PRE_SAVE event, the COMMIT / FLUSH won't be called.
Can anyone point me in the right direction? Or am I using the wrong
approach to compare newly-entered data with already existing data?
I don't know your particular use case but you might want to use a Query function (see https://github.com/akeneo/pim-community-dev/blob/2.2/src/Pim/Bundle/CatalogBundle/Doctrine/ORM/Query/FindAttributesForFamily.php). It's purpose is to directly fetch in the database the data you need (it will be the original value since the product hasn't been flushed in DB on PRE_SAVE)
I hope this helps.

FOSUserBundle, EventListener registration user

I'm working on the FOSUserBundle, on EventListener for RegistrationUser.
In this bundle, when I create a user, I use a method updateUser() (in Vendor...Model/UserManagerInterface). This method seems to be subject to an EventListener that triggers at least two actions. Registering the information entered in the database. And sending an email to the user to send them login credentials.
I found the method that sends the mail. By cons, I didn't find the one who makes the recording. I also didn't find where to set the two events.
First for all (and my personal information), I try to find these two points still unknown. If anyone could guide me?
Then, depending on what we decide with our client, I may proceed to a surcharge (which I still don't really know how to do), but I imagine that I would find a little better once my two strangers found
Thanks for your attention and help
This is the function wich handles the email confirmation on registrationSucces
FOS\UserBundle\EventListener\EmailConfirmationListener
public function onRegistrationSuccess(FormEvent $event)
{
/** #var $user \FOS\UserBundle\Model\UserInterface */
$user = $event->getForm()->getData();
$user->setEnabled(false);
if (null === $user->getConfirmationToken()) {
$user->setConfirmationToken($this->tokenGenerator->generateToken());
}
$this->mailer->sendConfirmationEmailMessage($user);
$this->session->set('fos_user_send_confirmation_email/email', $user->getEmail());
$url = $this->router->generate('fos_user_registration_check_email');
$event->setResponse(new RedirectResponse($url));
}
But I tell you that what you are trying to do is a bad practice. The recommended way is the following.
Step 1: Select one of the following events to listen(depending on when you want to catch the process)
/**
* The REGISTRATION_SUCCESS event occurs when the registration form is submitted successfully.
*
* This event allows you to set the response instead of using the default one.
*
* #Event("FOS\UserBundle\Event\FormEvent")
*/
const REGISTRATION_SUCCESS = 'fos_user.registration.success';
/**
* The REGISTRATION_COMPLETED event occurs after saving the user in the registration process.
*
* This event allows you to access the response which will be sent.
*
* #Event("FOS\UserBundle\Event\FilterUserResponseEvent")
*/
const REGISTRATION_COMPLETED = 'fos_user.registration.completed';
Step 2 Implement the Event Subscriber with a priority
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_SUCCESS => [
'onRegistrationSuccess', 100 //The priority is higher than the FOSuser so it will be called first
],
);
}
Step 3 Implement your function
public function onRegistrationSuccess(FormEvent $event)
{
//do your logic here
$event->stopPropagation();//the Fos User method shall never be called!!
$event->setResponse(new RedirectResponse($url));
}
You never should modify the third party libraries in this case the Event Dispatcher System is made for this to earlier process the event and if its needed stop the propagation and avoid the "re-processing" of the event.
Hope it helps!!!!

Symfony getUser type hinting

I find it somewhat annoying to have to constantly use #var on getUser. It seems sloppy.
So I was thinking about starting to use this instead
<?php
// in the controller
$user = Customer::isCustomer($this->getUser());
// in the entity
/**
* #param Customer $user
*
* #return Customer
*/
public static function isCustomer(Customer $user)
{
return $user;
}
Is this a good idea? Bad idea? Horrible idea?
A type hint is the better option in this case.
Why would you write more code by adding checks manually rather than adding a simple type hint to your param.
Your four lines of codes representing two conditions give exactly the same result as:
/**
* #param Customer|null $user
*
* #return Customer|null
*/
public static function isCustomer(Customer $user = null)
{
// If $user is null, it works
// If $user is a Customer instance, it works
// If it's other, an exception is thrown
return $user;
}
Type hinting optimises and give more readability to a code.
It's a convention in symfony2, php and more.
It's commonly used as a constraint (or contract) with you and your method.
Also, it's the only alternative for an interface or an abstract class to add requirement to a parameter, because they don't have a body, and so cannot write conditions.
Update
In SensioLabs Insight, Object type hinting represents a warning using the following message :
The parameter user, which is an object, should be typehinted.
Because the verb should is used, I consider it's not a mandatory requirement, just a very good practice in case of it doesn't cause any problem.
Also, you can use the example you given without making your code horrible.

Can I implement my own Symfony2 annotations easily?

Is there anything in the Symfony annotations modules that allow me to use them for other uses?
I know for #Route and #Method you need to extend existing libraries, so its just not that easy i'm guessing.
Currently, i'm working with the JS History API, and would LOVE to put the popState data for my JS files in the annotations. So they are already available when the routing generates the URL.
Q Doesn't this makes sense to have a, HTML5 annotated title, or some attribute here? It would be great to have the ability to define this data, as annotated, right next to the already existing route name and stuff.
Q Is there anybody that has tweaked with the annotations before?
I wanted to clarify my intentions here as I think I left out some crucial details (the mention of History API) for understanding my use case.
There is a few SPA front ends that have been integrated through a front-end bundle, and this connected via AJAX calls to a backend bundle which was a straight RESTful API, with the addition of a very fun-to-develop PHP API class I made that intereprets and processes (routes) the AJAX in a fashion that directly executes other PHP class controller `methods.
I use a lot of ajax for this Symfony 2 app (fosjsrouter) to handle routing. So instead of URLs triggering the routes and actions, the SPA click event fires off AJAX to the back end router, with a large JSON payload, not limited to PHP control parameter's (class/method/var names), and data sets.
OK, so getting back on track; Given the above scenario; In the JS class object end of the router, inside this I thought it was the best place to add some JS History API functionality to it, (state, back button, etc.)
The above class can be called if a history flag was called, which could become responsible for assigning initial state data. Primarily, this is because the JSON data object that's being around in this JS method contains already a lot of the crucial route data, and param information for that route needed in the backend PHP, which comes from the annotations.
So the idea is if I add accessibility for a history state title and URL to the annotations, then I will have access to that information right there available to define the initial state, if flagged, right inside the an ajax.done(), inside this main JS routing method.
Now we can pass state back and forth two ways between the db and realtime client-side async. You can use an observer, or anything, from there front-end, and jobs/queues on the backend to keep it fully reactive. (use React too :-))
EDIT I'm not so sure that's what I was thinking, it looks like its making me set the values of the title and url for this inside the return statement of the PHP function, where I want it set in the annotation (see return 'Matthias Noback';)
So I'm trying this, but where do I set these titles at?
<?php
namespace Blah\CoreBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
/**
* #Annotation
*/
class HistoryAnnotationController
{
//history state params are out properties here..
/**
* #var
*/
private $url;
/**
* #var
*/
private $title;
/**
*
*/
public function __construct()
{
}
/**
* #return mixed
*/
public function getTitle()
{
return $this->title;
}
/**
* #return mixed
*/
public function getUrl()
{
return $this->url;
}
}
I want to set it WAY back here, so the ajax that calls this route has access to it.. (look for #historyApiTitle in this code, etc..)
<?php
namespace Blah\Bundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller,
Symfony\Component\HttpFoundation\JsonResponse,
Sensio\Bundle\FrameworkExtraBundle\Configuration\Method,
Sensio\Bundle\FrameworkExtraBundle\Configuration\Route,
Sensio\Bundle\FrameworkExtraBundle\Configuration\Template,
Blah\Bundle\Entity\Test,
Doctrine\ORM\Query; //for hydration
class StuffController
{
/**
* #Route("/some/route/name/{test}", name="some_route_name", options={"expose"=true})
* #param $test
* #return mixed
* #historyApiTitle('This is the get something page')
* #historyApiUrl('/get_something')
*/
public function getSomethingAction($test)
{
$em = $this->getDoctrine()->getManager();
$dql = "
SELECT s
FROM BlahBundle:Stuff s
WHERE s.test = :test";
$query = $em->createQuery($dql);
$query->setParameter('test', $test);
$paginator = $this->get('knp_paginator');
$pagination = $paginator->paginate($query,
$this->get('request')->query->get('page', 1), 1000);
return $this->render('BlahBundle:Stuff:get_something.html.twig', array('pagination' => $pagination));
}
}
Q So looking at these TWO code examples, how do I connect the dots between the two to get this to work?
Yes you can annotations classes you can follow the following tutorial Creating Custom annotations Classes
Basic rules are the follows:
Your class should have the #Annotation -phpdoc comment
/**
* #Annotation
*/
class CustomAnnotation
{
public function __construct($options) {}
}
In Your Needed class just use it in standard way;
class Person
{
/**
* #CustomAnnotation("option")
*/
public function getName()
{
return 'some stuff';
}
}
You should looks at the AOPBundle, it allows you to do treatement from your personnals annotations. But I don't thinks trying to do annotations in the view is a good idea. You need to parse the javascript with php, and it sounds bad.

Resources