I'm trying to make a custom User Provider which uses a custom Doctrine-like bundle. This bundles uses entities, pretty much like Doctrine does :
/**
* #EntityMeta(table="MY_TABLE")
*/
class MyTable extends AbstractEntity
{
/**
* #var int
* #EntityColumnMeta(column="Code", isKey=true)
*/
protected $code;
/**
* #var int
* #EntityColumnMeta(column="Name")
*/
protected $name;
Those annotations work well when I use the doctrine-like manager provided by my bundle. This code works well :
public function indexAction(DoctrineLikeManager $manager)
{
$lines = $manager->getRepository('MyTable')->findBy(array(
'email' => 'test#test.com'
));
// do something with these
}
So I know annotations work. But when I use the same code, with the same entity, in the User Provider Class, I get the following error :
[Semantical Error] The annotation "#NameSpace\DoctrineLikeBundle\EntityColumnMeta" in property AppBundle\MyTable::$code does not exist, or could not be auto-loaded.
The UserProvider :
class HanaUserProvider implements UserProviderInterface
{
private $manager;
public function __construct(DoctrineLikeManager $manager)
{
$this->manager = $manager;
}
public function loadUserByUsername($username)
{
// this is where it fails :(
$lines = $this->manager->getRepository('MyTable')->findBy(array(
'email' => 'test#test.com'
));
// return user or throw UsernameNotFoundException
}
}
Is it possible to use custom annotations in that context ? Maybe I should do something in particular so custom annotations can be successfully loaded ?
Thanks in advance !
Ok, I found a solution, which might not be the best but works.
The thing is that annotations don't seem to use classic autoloading, as explained here.
I had to register a loader in my User Provider :
public function loadUserByUsername($username)
{
AnnotationRegistry::registerLoader('class_exists'); // THIS LINE HERE
$lines = $this->manager->getRepository('MyTable')->findBy(array(
'email' => 'test#test.com'
));
// return user or throw UsernameNotFoundException
}
However, the problem is that this method is deprecated and will be removed in doctrine/annotations 2.0.
Related
I am having problems with a composer package I am dealing with. It implements a trait Billable.
trait Billable
{
/**
* Update the payment method token for all of the user's subscriptions.
*
* #param string $token
* #return void
*/
protected function updateSubscriptionsToPaymentMethod($token)
{
foreach ($this->subscriptions as $subscription) {
if ($subscription->active()) {
BraintreeSubscription::update($subscription->braintree_id, [
'paymentMethodToken' => $token,
]);
}
}
}
}
I am trying to override this method in my class
class Organisation extends Model
{
use Billable;
/**
* Update the payment method token for all of the user's subscriptions.
*
* #param string $token
* #return void
*/
protected function updateSubscriptionsToPaymentMethod($token)
{
foreach ($this->subscriptions as $subscription) {
if ($subscription->active()) {
BrntreeSubscription::update($subscription->braintree_id, [
'paymentMethodToken' => $token,
]);
}
}
}
}
But the method is not overridden. As a test I overrode some of the public functions and they work fine, it this a limitation of traits? I have tried to find the answer online but have come up short.
I am trying to override this function because I need to customize the behaviour of the BraintreeSubscription class.
Any help would be greatly appreciated.
in your class you could do the following notice the T before the function name you may change this to be aliased as anything really.
use billable {
updateSubscriptionsToPaymentMethod as tUpdateSubscriptionsToPaymentMethod;
}
then simply in the class add the desired function:
public function updateSubscriptionsToPaymentMethod(){
...
}
I have a problem getting a unit test to run for my IndexController class.
The unit test just does the following (inspired from the unit-test tutorial of zf3):
IndexControllerTest.php:
public function testIndexActionCanBeAccessed()
{
$this->dispatch('/', 'GET');
$this->assertResponseStatusCode(200);
$this->assertModuleName('main');
$this->assertControllerName(IndexController::class); // as specified in router's controller name alias
$this->assertControllerClass('IndexController');
$this->assertMatchedRouteName('main');
}
In the Module.php I've some functionality to check if there is a user logged in, else he will be redirected to a login route.
Module.php:
public function onBootstrap(MvcEvent $mvcEvent)
{
/** #var AuthService $authService */
$authService = $mvcEvent->getApplication()->getServiceManager()->get(AuthService::class);
$this->auth = $authService->getAuth(); // returns the Zend AuthenticationService object
// store user and role in global viewmodel
if ($this->auth->hasIdentity()) {
$curUser = $this->auth->getIdentity();
$mvcEvent->getViewModel()->setVariable('curUser', $curUser['system_name']);
$mvcEvent->getViewModel()->setVariable('role', $curUser['role']);
$mvcEvent->getApplication()->getEventManager()->attach(MvcEvent::EVENT_ROUTE, [$this, 'checkPermission']);
} else {
$mvcEvent->getApplication()->getEventManager()->attach(MvcEvent::EVENT_DISPATCH, [$this, 'authRedirect'], 1000);
}
}
The checkPermission method just checks if the user role and the matched route are in the acl storage.
If this fails I will redirect a status code of 404.
Problem: The unit test fails: "Failed asserting response code "200", actual status code is "302"
Therefore the unit test jumps into the else case from my onBootstrap method in the Module.php where the redirect happen.
I did the following setUp in the TestCase but it doesn't work:
public function setUp()
{
// override default configuration values
$configOverrides = [];
$this->setApplicationConfig(ArrayUtils::merge(
include __DIR__ . '/../../../../config/application.config.php',
$configOverrides
));
$user = new Employee();
$user->id = 1;
$user->system_name = 'admin';
$user->role = 'Admin';
$this->authService = $this->prophesize(AuthService::class);
$auth = $this->prophesize(AuthenticationService::class);
$auth->hasIdentity()->willReturn(true);
$auth->getIdentity()->willReturn($user);
$this->authService->getAuth()->willReturn($auth->reveal());
$this->getApplicationServiceLocator()->setAllowOverride(true);
$this->getApplicationServiceLocator()->setService(AuthService::class, $this->authService->reveal());
$this->getApplicationServiceLocator()->setAllowOverride(false);
parent::setUp();
}
Hints are very appreciated
The code might differ a bit from Zend Framework 2 but If you have a simple working example in zf2 maybe I can transform it into zf3 style.
I don't use ZfcUser - just the zend-acl / zend-authentication stuff
After several days of headache I've got a working solution.
First I moved all the code within the onBootstrap to a Listener, because the phpunit mocks are generated after the zf bootstrapping and therefore are non existent in my unit tests.
The key is, that the services are generated in my callable listener method, which is called after zf finished bootstrapping.
Then PHPUnit can override the service with the provided mock.
AuthenticationListener
class AuthenticationListener implements ListenerAggregateInterface
{
use ListenerAggregateTrait;
/**
* #var AuthenticationService
*/
private $auth;
/**
* #var Acl
*/
private $acl;
/**
* Attach one or more listeners
*
* Implementors may add an optional $priority argument; the EventManager
* implementation will pass this to the aggregate.
*
* #param EventManagerInterface $events
* #param int $priority
*
* #return void
*/
public function attach(EventManagerInterface $events, $priority = 1)
{
$this->listeners[] = $events->attach(MvcEvent::EVENT_ROUTE, [$this, 'checkAuthentication']);
}
/**
* #param MvcEvent $event
*/
public function checkAuthentication($event)
{
$this->auth = $event->getApplication()->getServiceManager()->get(AuthenticationService::class);
$aclService = $event->getApplication()->getServiceManager()->get(AclService::class);
$this->acl = $aclService->init();
$event->getViewModel()->setVariable('acl', $this->acl);
if ($this->auth->hasIdentity()) {
$this->checkPermission($event);
} else {
$this->authRedirect($event);
}
}
// checkPermission & authRedirect method
}
Now my onBootstrap got really small, just like ZF wants it. documentation reference
Module.php
public function onBootstrap(MvcEvent $event)
{
$authListener = new AuthenticationListener();
$authListener->attach($event->getApplication()->getEventManager());
}
Finally my mocking in the unit test looks like this:
IndexControllerTest
private function authMock()
{
$mockAuth = $this->getMockBuilder(AuthenticationService::class)->disableOriginalConstructor()->getMock();
$mockAuth->expects($this->any())->method('hasIdentity')->willReturn(true);
$mockAuth->expects($this->any())->method('getIdentity')->willReturn(['id' => 1, 'systemName' => 'admin', 'role' => 'Admin']);
$this->getApplicationServiceLocator()->setAllowOverride(true);
$this->getApplicationServiceLocator()->setService(AuthenticationService::class, $mockAuth);
$this->getApplicationServiceLocator()->setAllowOverride(false);
}
I have several routes that start with /experiment/{id}/... and I'm tired of rewriting the same logic to retrieve the signed in user's experiment. I guess I could refactor my code but I'm guessing #ParamConverter would be a better solution.
How would I rewrite the following code to take advantage of Symfony's #ParamConverter functionality?
/**
* Displays details about an Experiment entity, including stats.
*
* #Route("/experiment/{id}/report", requirements={"id" = "\d+"}, name="experiment_report")
* #Method("GET")
* #Template()
* #Security("has_role('ROLE_USER')")
*/
public function reportAction(Request $request, $id)
{
$em = $this->getDoctrine()->getManager();
$experiment = $em->getRepository('AppBundle:Experiment')
->findOneBy(array(
'id' => $id,
'user' => $this->getUser(),
));
if (!$experiment) {
throw $this->createNotFoundException('Unable to find Experiment entity.');
}
// ...
}
Experiment entities have composite primary keys as follows:
class Experiment
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
*/
protected $id;
/**
* #var integer
*
* #ORM\Column(name="user_id", type="integer")
* #ORM\Id
*/
protected $userId;
/**
* #ORM\ManyToOne(targetEntity="UserBundle\Entity\User", inversedBy="experiments", cascade={"persist"})
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
// ..
}
I want to retrieve a signed in user's experiment using their user id and an experiment id in the route.
You can achieve that by using custom ParamConverter. For example something like that:
namespace AppBundle\Request\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterInterface;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use AppBundle\Entity\Experiment;
class ExperimentConverter implements ParamConverterInterface
{
protected $em;
protected $user;
public function __construct(EntityManager $em, TokenStorage $tokenStorage)
{
$this->em = $em;
$this->user = $tokenStorage->getToken()->getUser();
}
public function apply(Request $request, ParamConverter $configuration)
{
$object = $this->em->getRepository(Experiment::class)->findOneBy([
'id' => $request->attributes->get('id'),
'user' => $this->user
]);
if (null === $object) {
throw new NotFoundHttpException(
sprintf('%s object not found.', $configuration->getClass())
);
}
$request->attributes->set($configuration->getName(), $object);
return true;
}
public function supports(ParamConverter $configuration)
{
return Experiment::class === $configuration->getClass();
}
}
You need to register your converter service and add a tag to it:
# app/config/config.yml
services:
experiment_converter:
class: AppBundle\Request\ParamConverter\ExperimentConverter
arguments:
- "#doctrine.orm.default_entity_manager"
- "#security.token_storage"
tags:
- { name: request.param_converter, priority: 1, converter: experiment_converter }
Unfortunately, you can't inject the currently logged in users id into the param converter, unless you actually pass it as a parameter in the url.
You could create your own converter, but I think your best bet would be to just create a protected method for fetching the experiment. It will be just as easy to use and maintain as an annotation:
protected function getCurrentUsersExperiment($experimentId)
{
return $this->getDoctrine()->getManager()->getRepository('AppBundle:Experiment')
->findOneBy(array(
'id' => $experimentId,
'user' => $this->getUser()
));
}
public function reportAction(Request $request, $id)
{
$experiment = $this->getCurrentUsersExperiment($id);
...
}
As it's noted in the symfony best practices: use ParamFetcher whenever appropriate, but don't overthink it.
While the question already has an answer, I'd like to add my two cents to it:
It looks like you may be solving a wrong problem: is your objective to prevent access to other user's experiments? If so, just load the Experiment object, and use a Security Voter to determine whether the current user can access the experiment or not.
Using the param converter the way you want will throw a 404 error instead of 401, with the same ultimate result: access to other people's experiments is denied. Unless you're trying to conceal the existence of a particular experiment ID, you might as well return a HTTP response reflecting the actual situation - 401, not 404. I would argue that obscuring the existence of some ID has probably no real benefit.
If nothing else, then using the security voter to deny access sounds like more fit-for-purpose and reusable approach in Symfony context, comparing to a homebrew param converter.
If you don't want to use a security voter, you could also get away with using an expression: if your Experiment's repository class was autowiring the Security object, you would be able to fetch the current user from the repository class. If that repository class has a method findCurrentUserExperiment(int $id), the usage would be close to:
/**
* #Route("/experiment/{experiment}/report", requirements={"experiment" = "\d+"}, name="experiment_report")
* #Entity("experiment", expr="repository.findCurrentUserExperiment(experiment)")
*/
public function reportAction(Request $request, ?Experiment $experiment)
I still think that a security voter is a better fit, but at least with the expression you wouldn't have to manage a narrow-use-case ParamConverter.
so I decided to use the KnpMenuBundle in my Symfony project, but in order for the menu to work as I intend to, I added 2 lines to the /vendor/knplabs/knp-menu/src/Knp/Menu/Matcher/Voter/RouteVoter.php.
So I know it's a bad practice to change the contents of the vendor folder. My question is, how to I apply these changes? I'm guessing I have to create my own Voter class, extend the RouteVoter and somehow register it with Symfony. Nowhere on the internet could I find how to do that.
Any ideas? Thanks, Mike.
To register a custom voter you must create a customVoter in your project and register it as a service.
Your voter should look something like this
class RegexVoter implements VoterInterface
{
/**
* #var RequestStack
*/
private $requestStack;
/**
* #param RequestStack $requestStack
*/
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}
/**
* {#inheritdoc}
*/
public function matchItem(ItemInterface $item)
{
$childRegex = $item->getExtra('regex');
if ($childRegex !== null && preg_match($childRegex, $this->requestStack->getCurrentRequest()->getPathInfo())) {
return true;
}
return;
}
}
Register it as a service like this
menu.voter.regex:
class: AppBundle\Menu\Matcher\Voter\RegexVoter
arguments: [ '#request_stack' ]
tags:
- { name: knp_menu.voter }
Then you have to instantiate your voter in your menuBuilder
private $regexVoter;
public function __construct(RegexVoter $regexVoter)
{
$this->regexVoter = $regexVoter;
}
In my example my voter get the item extra regex to work.
I think you must modify and use your own logic.
I hope this will help you
Let's say I have a view table. And I want to get data from it to an entity. Can I (and how) create entity class to do that (no save operation needed)? I just want to display them.
The accepted answer is correct, but I'd like to offer some additional suggestions that you might want to consider:
Mark your entity as read-only.
Make the constructor private so that only Doctrine can create instances.
/**
* #ORM\Entity(readOnly=true)
* #ORM\Table(name="your_view_table")
*/
class YourEntity {
private function __construct() {}
}
There is nothing special in querying a view — it's just a virtual table. Set the table of your entity this way and enjoy:
/**
* #ORM\Entity
* #ORM\Table(name="your_view_table")
*/
class YourEntity {
// ...
}
Both the previous answers are correct, but if you use the doctrine migration tool and do a schema:update it will fail...
So, in addition to marking the entity as read only and making the constructor private (explained in Ian Phillips answer):
/**
* #ORM\Entity(readOnly=true)
* #ORM\Table(name="your_view_table")
*/
class YourEntity {
private function __construct() {}
}
You would need to set the schema tool to ignore the entity when doing a schema:update...
In order to do that you just need to create this command in your bundle, and set yout entity in the ignoredEntity list:
src/Acme/CoreBundle/Command/DoctrineUpdateCommand.php:
<?php
namespace Acme\CoreBundle\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Doctrine\ORM\Tools\SchemaTool;
class DoctrineUpdateCommand extends \Doctrine\Bundle\DoctrineBundle\Command\Proxy\UpdateSchemaDoctrineCommand {
protected $ignoredEntities = array(
'Acme\CoreBundle\Entity\EntityToIgnore'
);
protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas) {
/** #var $metadata \Doctrine\ORM\Mapping\ClassMetadata */
$newMetadatas = array();
foreach ($metadatas as $metadata) {
if (!in_array($metadata->getName(), $this->ignoredEntities)) {
array_push($newMetadatas, $metadata);
}
}
parent::executeSchemaCommand($input, $output, $schemaTool, $newMetadatas);
}
}
(credit to Alexandru Trandafir Catalin: obtained from here: https://stackoverflow.com/a/25948910/1442457)
BTW, this is the only way I found to work with views from doctrine... I know it is a workaround... If there is a better way I am open or suggestions)
In addition to above anwers if you are using doctrine migrations for schema update the following configuration works perfectly.
/**
* #ORM\Entity(readOnly=true)
* #ORM\Table(name="view_table_name")
*/
class YourEntity {
private function __construct() {}
}
Till here is te same as above answers. Here you need to configure doctrine not to bind schemas;
doctrine:
dbal:
schema_filter: ~^(?!view_)~
The above filter definition filters all 'view_' prefixed tables as well as views an could be extended using regex. Just make sure you have named your views with 'view_' prefix.
But doctrine:schema:update --dump-sql still shows the views, I hope they will integrate the same filter to schema update too.
I hope this solution would help some others.
Source: http://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html#manual-tables
In addition to above answer, You must have a naming Strategy of your view' entities and the virtual tables, for example: view_your_table, and then you must add the following code to the doctrine.yaml, to disable creating new migration file to the view:
schema_filter: ~^(?!view_)~
In addition to above answer, I have mixed some of your example code to extend the DoctrineUpdateCommand
This is my DoctrineUpdateCommand:
class DoctrineUpdateCommand extends UpdateSchemaDoctrineCommand{
protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas) {
$container = $this->getApplication()->getKernel()->getContainer();
$filterExpr = $container->get('doctrine')->getEntityManager()->getConnection()->getConfiguration()->getFilterSchemaAssetsExpression();
$emptyFilterExpression = empty($filterExpr);
/** #var $newMetadatas \Doctrine\ORM\Mapping\ClassMetadata */
$newMetadatas = array();
foreach ($metadatas as $metadata) {
if(($emptyFilterExpression||preg_match($filterExpr, $metadata->getTableName()))){
array_push($newMetadatas, $metadata);
}
}
parent::executeSchemaCommand($input, $output, $schemaTool, $newMetadatas);
}
}
Thanks for the right way
I spend a day on that due to the necessity to introduce a view in my database on the Zend implementation.
As all previously said, you should create an entity, and this entity must have Id() annotation:
/**
* #Doctrine\ORM\Mapping\Table(name="your_view")
* #Doctrine\ORM\Mapping\Entity(readOnly=true)
*/
class YourViewEntity
{
/**
* #var SomeEntityInterface
* #Doctrine\ORM\Mapping\Id()
* #Doctrine\ORM\Mapping\OneToOne(targetEntity="SomeMainEntity", fetch="LAZY")
* #Doctrine\ORM\Mapping\JoinColumn(nullable=false, referencedColumnName="id")
*/
protected $some;
/**
* #var AnotherEntityInterface
* #Doctrine\ORM\Mapping\ManyToOne(targetEntity="AnotherEntity", fetch="LAZY")
* #Doctrine\ORM\Mapping\JoinColumn(nullable=false, referencedColumnName="id")
*/
protected $another;
// Make the constructor private so that only Doctrine can create instances.
private function __construct() {}
}
also with private constructor as described in Ian Phillips answer. Yet this does not prevent orm:schema-tool:update to create a table based on new entity, trying to override our view... Despite the fact that using orm:schema-tool:update should be avoided on production in favor of migration scripts, for development purposes this is extremely useful.
As schema_filter: ~^(?!view_)~ is both seem to not working, also deprecated, I managed to find a trick on Kamil Adryjanek page that presents option to add an EventListener or Subscriber to entity manager, that will prevent creating table for us. Mine implementation below:
class SkipAutogenerateTableSubscriber implements EventSubscriber
{
public const CONFIG_KEY = "skip_autogenerate_entities";
private $ignoredEntities = [];
public function __construct($config)
{
if (array_key_exists(self::CONFIG_KEY, $config)) {
$this->ignoredEntities = (array) $config[self::CONFIG_KEY];
}
}
public function getSubscribedEvents()
{
return [
ToolEvents::postGenerateSchema
];
}
public function postGenerateSchema(GenerateSchemaEventArgs $args)
{
$schema = $args->getSchema();
$em = $args->getEntityManager();
$ignoredTables = [];
foreach ($this->ignoredEntities as $entityName) {
$ignoredTables[] = $em->getClassMetadata($entityName)->getTableName();
}
foreach ($schema->getTables() as $table) {
if (in_array($table->getName(), $ignoredTables)) {
$schema->dropTable($table->getName());
}
}
}
}
And this solves problem not only for orm:schema-tool, but also for migrations:diff of doctrine/migrations module.