Mock call to external API - symfony

Today, I've tried to upgrade my project to the new version of Symfony (3.3), and I'm encountering a problem with my mocks.
Until today, I was doing my mocks like this:
$client = $this->makeClient();
$mockObject = new \stdClass();
$mock = $this->getMockBuilder('SomeClass')
->disableOriginalConstructor()
->setMethods(['method1', 'method2'])
->getMock();
$mock->expects($this->once())
->method('method1')
->will($this->returnValue($mockObject));
$client->getContainer()->set('my_service', $mock);
Here, method1 is just a Guzzle post, nothing else.
Now, I'm getting the following error:
Setting the "my_service" pre-defined service is deprecated since Symfony 3.3 and won't be supported anymore in Symfony 4.0: 1x
After some research, it seems that I cannot use the last line of my code.
Problem is, I can't see nor find any solution to solve this deprecation.

There's few ways of solving your problem.
TestDoubleBundle
TestDoubleBundle makes it easier to create test doubles. You can use dependency injection tags to automatically replace a service with either a stub or a fake.
Override the container
Another way is to extend the container in the test environment, so it allows stubbing. Here's a draft of the idea:
<?php
namespace Zalas\Test\DependencyInjection;
use Symfony\Component\DependencyInjection\Container;
class MockerContainer extends Container
{
/**
* #var object[] $stubs
*/
private $stubs = array();
public function stub($serviceId, $stub)
{
if (!$this->has($serviceId)) {
throw new \InvalidArgumentException(sprintf('Cannot stub a non-existent service: "%s"', $serviceId));
}
$this->stubs[$serviceId] = $stub;
}
/**
* #param string $id
* #param integer $invalidBehavior
*
* #return object
*/
public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE)
{
if (array_key_exists($id, $this->stubs)) {
return $this->stubs[$id];
}
return parent::get($id, $invalidBehavior);
}
/**
* #param string $id
*
* #return boolean
*/
public function has($id)
{
if (array_key_exists($id, $this->stubs)) {
return true;
}
return parent::has($id);
}
}
To enable this container you'll need to override the getContainerBaseClass method in your AppKernel:
/**
* #return string
*/
protected function getContainerBaseClass()
{
if ('test' == $this->environment) {
return '\Zalas\Test\DependencyInjection\MockerContainer';
}
return parent::getContainerBaseClass();
}
You might need to tweak the code a bit, perhaps declare MockerContainer::$stubs as static (although if your previous approach worked it shouldn't be needed - it might be needed if you need to stub for multiple requests).
Now you should be able to use the container to stub services like this:
$client->getContainer()->stub('my_service', $myServiceStub);
Use a synthetic service
Another way of working around your issue is defining your service as synthetic. You could write a compiler pass that would only mark the service as synthetic in a test environment.

Your question is also discussed here.
You can take a look at this osservation:
Note that you don't need to fix the deprecations when moving to 3.3.
But you will when moving to 4.0.
And this workaround:
Well, you can mark these tests as #legacy to avoid making them fail
temporarily, to give you time to migrate the tests, if that takes
time. This is the whole point of deprecations: you can migrate
progressively (I also prefer removing deprecations as fast as
possible, but for some of them, it may require more time)

Related

"make:entity --regenerate" creates an incorrect (?) function

I'm currently following a Symfony tutorial, and I've gotten to the part of Doctrine bidirectional relations (sorry if the terms I'm using are wrong, I'm not an native English speaker). My model is based on an Advert (One-To-Many) that displays an array of Applications (Many-To-One) made to this Advert. So an Application has to be linked to an Advert, hence the nullable false :
class Application
{
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Advert", inversedBy="applications")
* #ORM\JoinColumn(nullable=false)
*/
private $advert;
//
}
And I added an $applications attribute to my Advert class:
class Advert
{
/**
* #ORM\OneToMany(targetEntity="App\Entity\Application", mappedBy="advert")
*/
private $applications;
//
}
But when I use php bin/console make:entity --regenerate, to get the removeApplication() function, the code I'm getting is the following:
public function removeApplication(Application $application): self
{
if ($this->applications->contains($application)) {
$this->applications->removeElement($application);
// set the owning side to null (unless already changed)
if ($application->getAdvert() === $this) {
$application->setAdvert(null);
}
}
return $this;
}
The function sets the $advert of the Application to null while this attribute is explicitly set to nullable = false. I noticed this inconsistency because I'm using Symfony 4 whereas the tutorial I'm following is based on an older version, so the functions generated in the tutorial were much simpler and did not handle the $advert attribute.
Any idea why this is happening and if it might cause an error later in my project? Let me know if you need more pieces of code to understand the problem.
That really looks like a bug to me, they probably do not handle nullable cases inside of the generator.
Maybe try orphanRemoval on the Advert side of the relation, would be interesting what would happen then:
class Advert
{
/**
* #ORM\OneToMany(targetEntity="App\Entity\Application", mappedBy="advert", orphanRemoval=true)
*/
private $applications;
}

Single position to restrict access to a Doctrine entity

I've just started working with Doctrine and built a simple blog project. One of my requirements is that a blog post should not be visible to anybody (for simpleness, skip an editor's interface) until the publish date is reached.
As far as I see, it's obvious to do so using a custom repository. Let's extend the find method the following way:
public function find($id, $lockMode = null, $lockVersion = null)
{
/** #var Post $post */
$post = parent::find($id, $lockMode, $lockVersion);
if($post->getCreatedAt() > new \DateTime()) {
return null;
}
return $post;
}
This restricts the access for a page showing a single Post entity. For an overview page, the same can be done using a custom method:
public function findForOverview()
{
$query = $this->createQueryBuilder('p')
->where('p.createdAt < CURRENT_TIMESTAMP()')
->orderBy('p.createdAt', 'DESC')
->getQuery();
return $query->getResult();
}
So, even for this simple requirement, I've already written two custom methods. If I continue to work on my project, other restriction limitations might occur and additional ways to load that entity might arise. And as far as I see, for each case I have to implement the logic for all access guards.
Is there no simpler way to do that? I'm thinking of something like an annotation or an "entity load listener" that makes it simple to write one single entry point for all such checks - making it impossible to forget such checks...
Such restrictions are usually implemented by using mechanism of SQL filters in Doctrine. Implementation of this filter works on lower level then DQL and allows you to apply modifications for SQL query being constructed. In your case it may look like this:
namespace App\ORM\Filter;
use App\Entity\Post;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\Filter\SQLFilter;
class PostVisibilityFilter extends SQLFilter
{
/**
* Gets the SQL query part to add to a query.
*
* #param ClassMetadata $targetEntity
* #param string $targetTableAlias
* #return string The constraint SQL if there is available, empty string otherwise
*/
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias): string
{
if ($targetEntity->name !== Post::class) {
return '';
}
return sprintf('%s.%s >= now()', $targetTableAlias, $targetEntity->getColumnName('createdAt'));
}
}

Idea of multiple theme on a single channel in Sylius

For my project I was in need of multiple theme designs for different devices, but in one channel now, can select just one theme.
For example,
If I have channel "Japan" which sell in Japan, I want multiple themes for each devices : mobile, tablet and PC. So users will see the theme depend on their device.
I need some ideas about creating multiple theme/style for different devices with single channel.
So, Any ideas?
Okay, so the service that is responsible for the current theme is the sylius.context.theme service. If you use Sylius full stack, then the ChannelBasedThemeContext will be used (see: https://github.com/Sylius/SyliusCoreBundle/blob/master/Theme/ChannelBasedThemeContext.php). As you can see in it’s source code, it simply finds a theme you have installed by theme themeName property on the current channel. Knowing this, we can implement our own by implementing ThemeContextInterface. Because in your case you probably want to fall back to the default behaviour when the theme japan_mobile does not exist, we are gonna decorate the sylius.context.theme service, instead of replacing it!
So let’s start by creating Acme\AppBundle\Theme\DeviceBasedThemeContext:
namespace Acme\AppBundle\Theme\DeviceBasedThemeContext;
use Sylius\Bundle\ThemeBundle\Context\ThemeContextInterface;
use Sylius\Bundle\ThemeBundle\Repository\ThemeRepositoryInterface;
use Sylius\Component\Channel\Context\ChannelContextInterface;
use Sylius\Component\Channel\Context\ChannelNotFoundException;
use Sylius\Component\Core\Model\ChannelInterface;
final class DeviceBasedThemeContext implements ThemeContextInterface
{
/**
* #var ThemeContextInterface
*/
private $decoratedThemeContext;
/**
* #var ChannelContextInterface
*/
private $channelContext;
/**
* #var ThemeRepositoryInterface
*/
private $themeRepository;
/**
* #param ThemeContextInterface $decoratedThemeContext
* #param ChannelContextInterface $channelContext
* #param ThemeRepositoryInterface $themeRepository
*/
public function __construct(
ThemeContextInterface decoratedThemeContext,
ChannelContextInterface $channelContext,
ThemeRepositoryInterface $themeRepository
) {
$this->decoratedThemeContext = $decoratedThemeContext;
$this->channelContext = $channelContext;
$this->themeRepository = $themeRepository;
}
/**
* {#inheritdoc}
*/
public function getTheme()
{
try {
/** #var ChannelInterface $channel */
$channel = $this->channelContext->getChannel();
$deviceThemeName = $channel->getThemeName().’_’.$this->getDeviceName();
// try to find a device specific version of this theme, if so, it will use that one
if ($theme = $this->themeRepository->findOneByName($deviceThemeName) {
return $theme;
}
// fallback to use the default theme resolving logic
return $this->decoratedThemeContext->getTheme();
} catch (ChannelNotFoundException $exception) {
return null;
} catch (\Exception $exception) {
return null;
}
}
private function getDeviceName()
{
// here you should return the proper device name
return ‘mobile’;
}
}
Now that we have the theme context finished, we have to make the service container recognise it, we are doing that by decorating the existing sylius.theme.context service (a.k.a. the ChannelBasedThemeContext).
So in your services.xml add the following service:
<service id="acme.context.theme.device_based" class="Acme\AppBundle\Theme\DeviceBasedThemeContext"
decorates=“sylius.theme.context”>
<argument type=“service” id=“acme.context.theme.device_based.inner”/>
<argument type=“service” id=“sylius.context.channel”/>
<argument type=“service” id=“sylius.repository.theme”/>
</service>
And you are done! If you clear your caches it should now try to load japan_mobile first, if it does not exist it will simply load the japan theme (giving that the current channel has japan as its theme name).
I hope this is a clear enough instruction to help get you going, for injecting a service that can detect a proper device name is not so hard to do I guess, but if you can’t figure it out let me know and I will extend this by implementing that too.

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.

association mapping when one entity isn't managed by Doctrine

I have 2 entities in a one-to-one association. The first, Person, is stored in a MySQL database and handled by Doctrine. The second, AdUserRecord, describes an ActiveDirectory user record. It is read-only. It does not need to know about Person. Also, AdUserRecord properties should never be stored in the MySQL db for privacy reasons.
An AdUserRecord is retrieved using a service, AdSearcher, which can search by samaccountname or objectGUID. Whenever a search is successful, the service checks to see if there is a corresponding Person record and creates one if there is not. That works fine.
My problem occurs when I start with a Person object. Mostly, I don't need to access a Person's AdUserRecord so I'd prefer not to query Active Directory unless it's required. That means, I think, that Person::getAdrecord() needs to have access to the AdSearcher service. Something like this:
public function getAdrecord(){
if($this->adrecord) return $this->adrecord;
$searcher = ???; //get AdSearcher service somehow
$record = $search->getRecordByUserGuid($this->ad_guid);
if(!$record) throw new \Exception('this person no longer exists');
$this->adrecord = $record;
return $this->adrecord;
}
I've been reading the Symfony docs pretty assiduously, but I'm still stumped.
Questions
how do I get a service into an entity? Should it be injected via the constructor, or just where it's needed, in the getter? If it only occurs in the getter, do I have to inject it or is there a way to import it?
is adding a service to an entity the canonical way of handling these types of situations? Would it be preferable to build an entity manager for AdUserRecords?
what interfaces do I need to implement if I have to build an entity manager?
Person class
namespace ACRD\DefaultBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use ACRD\DefaultBundle\Entity\AdUserRecord;
/**
* #ORM\Entity
* #Orm\Table(name="person")
*
*/
class Person {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(name="AD_guid", type="string", length=36, unique=true)
*/
protected $ad_guid;
/**
* #var AdUserRecord
*/
protected $adrecord;
//usual getters and setters
}
It looks like Doctrine's postLoad event is the best solution.
// src/Acme/DemoBundle/EventListener/ActiveDirectorySubscriber.php
namespace Acme\DemoBundle\EventListener;
use Acme\DemoBundle\Model\AdAwareInterface;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
// for doctrine 2.4: Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use Symfony\Component\DependencyInjection\ContainerAware
class ActiveDirectorySubscriber extends ContainerAware implements EventSubscriber
{
public function getSubscribedEvents()
{
return array(
'postLoad',
);
}
public function postLoad(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if (!($entity instanceof AdAwareInterface)) {
return:
}
$adSearcher = $this->getContainer()->get('acme_demo.ad_searcher');
if ($adPerson = $adSearcher->find($entity->getAdGuid())) {
$entity->setAdPerson($adPerson);
}
}
}
You also mentioned that most of the time you don't need to use the active directory stuff. Before optimizing I highly suggest you actually measure how much of a performance impact there is. If, however, you do notice a performance problem, consider using a proxy object to mitigate the AdPerson searching right to the point where you actually need something from it.
public function postLoad(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if (!($entity instanceof AdAwareInterface)) {
return:
}
$adSearcher = $this->getContainer()->get('acme_demo.ad_searcher');
$entity->setAdPerson(new AdPersonProxy($adSearcher));
}
The AdPersonProxy would basically extend from your AdPerson class, wrap each and every public method with a call to load the actual AdPerson object and then act as a facade between the two. Consider the following implications before you start coding though:
it adds complexity to your codebase (the more code, the more there is to maintain);
it will be a pain to debug - for example you might get an exception inside your
template that will leave you scratching your head for a long time (been there,
done that);
The bottom line is that in theory services should (mostly) not be injected inside entities.
Regarding your third question:
EntityManagers implement Doctrine/Common/Persistence/ObjectManager - have a look at the interface on github.
Further:
a somewhat clean implementation would be similar to the Document<->Entity mapping (called references) provided by gedmo/doctrine-extensions.
Take a glimpse at the documentation to see how it works here and here.
If that's what you want start diving into the code of the ReferenceListener :)

Resources