I am currently refactoring bits of a medium sized project and encountered the following. (Bit of pseudo code to simplify the example)
class PostRepository {
}
class PostManager {
/**
* #var PostRepository
*/
private $repository;
public function __construct(PostRepository $repository)
{
$this->repository = $repository;
}
}
class DeletePostCommand {
/**
* #var PostManager
*/
private $postManager;
/**
* #var PostRepository
*/
private $postRepository;
public function __construct(PostManager $postManager, PostRepository $postRepository)
{
$this->postManager = $postManager;
$this->postRepository = $postRepository;
}
}
Should this be refactored? Or is it fine as it is?
Or should I create a getPostRepository function in my PostManager class? Wouldn't that go against the idea of the Single Responsibility Principle?
Related
based on the example LessThan.html#propertypath I would like to write a FunctionalTest for my own validator #Assert\Amount. $amount is to be validated depending on $currency.
My question is, how can I set the value for $currency in the FunctionalTest for the Amount Validator? I have looked at the tests of the package symfony/validator but can't find a clue. I have already written FunctionalTests for validators. But I am not getting anywhere with this requirement.
Could someone give me a hint or show me an example.
Example:
class Entity
{
/**
* #var \string
* #Assert\Currency()
*/
protected $currency;
/**
* #var \float
* #Assert\Amount(propertyPath="currency")
*/
protected $amount;
}
I have found a solution. I validated the complete entity and looked at the object in the validator and found the complete entity.
When I thought about how to test it, I did some research and found other tests that did the same. I don't know if this is the best solution, but I can now write my tests.
ConstraintValidatorTestCase
<?php
namespace App\Tests\Validator\Constraint;
use App\Entity\Entity;
use App\Validator\MinimumAmount;
use App\Validator\MinimumAmountValidator;
use Symfony\Component\Validator\Context\ExecutionContext;
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\ConstraintValidatorInterface;
class MinimumAmountValidatorTest extends ConstraintValidatorTestCase
{
/**
* #var ExecutionContextInterface
*/
protected $context;
/**
* #var ConstraintValidatorInterface
*/
protected $validator;
/**
* #var Constraint
*/
protected $constraint;
protected function setUp(): void
{
$this->constraint = new Entity();
$this->context = $this->createContext();
$this->validator = $this->createValidator();
$this->validator->initialize($this->context);
}
public function createValidator(): MinimumAmountValidator
{
return new MinimumAmountValidator();
}
public function createContext(): ExecutionContext
{
$myEntity = new Entity();
$myEntity->setCurrency('EUR');
$translator = $this->createMock(TranslatorInterface::class);
$translator->expects($this->any())->method('trans')->willReturnArgument(0);
$validator = $this->createMock(ValidatorInterface::class);
$executionContext = new ExecutionContext($validator, $myEntity, $translator);
$context->setNode('InvalidValue', null, null, 'property.path');
$context->setConstraint($this->constraint);
return $executionContext;
}
public function testValidation()
{
$this->validator->validate(40.00, new MinimumAmount());
$this->assertNoViolation();
}
}
Constraint
<?php
namespace App\Validator;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class MinimumAmount extends Constraint
{
/**
* #var string
*/
public $message = '';
/**
* #return string
*/
public function validatedBy(): string
{
return MinimumAmountValidator::class;
}
}
Validator
<?php
namespace App\Validator;
use App\Entity\Entity;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class MinimumAmountValidator extends ConstraintValidator
{
/**
* #param float $value
* #param Constraint $constraint
*
* #return void
*/
public function validate($value, Constraint $constraint)
{
/**
* #var Entity
*/
$contextRoot = $this->context->getRoot();
$currency = $contextRoot->getCurrency(); // EUR
$amount = $value; // 40.00
// Validation implementation
}
}
I'm currently using Symfony 4 with Doctrine MongoDB Bundle, following the instruction from this link:
DoctrineMongoDBBundle. So, I have a UserDocument:
src/Document/UserDocument.php
/** #MongoDB\Document(collection="user", repositoryClass="App\Repository\UserRepository") */
class UserDocument
{
/**
* #MongoDB\Id
* #var ObjectId
*/
private $id;
/**
* #MongoDB\Field(type="string", name="first_name")
* #var string
*/
private $firstName;
/**
* #MongoDB\Field(type="string", name="middle_name")
* #var string
*/
private $middleName;
/**
* #MongoDB\Field(type="string", name="last_name")
* #var string
*/
private $lastName;
}
src/Repository/UserRepository.php
use Doctrine\ODM\MongoDB\DocumentRepository;
class UserRepository extends DocumentRepository
{
}
src/Controller/Content.php
class Content extends Controller
{
/**
* #Route("/content", name="content")
* #param UserRepository $user
* #return Response
*/
public function index(UserRepository $user)
{
$user->findAll();
return new Response();
}
}
So, after running the content page, I got the following error:
Cannot autowire service "App\Repository\UserRepository": argument "$uow" of method "__construct()" references class "Doctrine\ODM\MongoDB\UnitOfWork" but no such service exists.
The DocumentRepository constructor looks like this:
public function __construct(DocumentManager $dm, UnitOfWork $uow, ClassMetadata $classMetadata)
{
parent::__construct($dm, $uow, $classMetadata);
}
Repository shouldn't be Services, but if you want to keep it that way, just Autowire the DocumentManager and get the uow and classmetdata from the Document Manager.
UnitOfWork and ClassMetadata can't be autowired
Do something like that in your UserRepository, it should work.
public function __construct(DocumentManager $dm)
{
$uow = $dm->getUnitOfWork();
$classMetaData = $dm->getClassMetadata(User::class);
parent::__construct($dm, $uow, $classMetaData);
}
Make sure to exclude your repository class from autowiring. Example here: https://symfony.com/doc/current/service_container/3.3-di-changes.html
In case you want your repository class as a service you should do it using a factory service.
I'm trying to extend the default Page class from symfony simple cms bundle.
The problem:
The custom property is not persisted.
Below is the code of the class which extends from BasePage.
use Doctrine\ODM\PHPCR\Mapping\Annotations\Document;
use Doctrine\ODM\PHPCR\Mapping\Annotations as PHPCRODM;
use Symfony\Cmf\Bundle\SimpleCmsBundle\Doctrine\Phpcr\Page as BasePage;
/**
* {#inheritDoc}
* #PHPCRODM\Document(referenceable=true)
*/
class Product extends BasePage
{
public $node;
/**
* #var string(nullable=true)
*/
private $code;
/**
* Get Code
* #return string
*/
public function getCode()
{
return $this->code;
}
/**
* Set code
* #return Product
*/
public function setCode($code)
{
$this->code = $code;
return $this;
}
}
This looks almost correct, but you miss a mapping on the $code:
/**
* #PHPCRODM\String(nullable=true)
*/
private $code;
I assume that $code is not language dependant. Otherwise you would need nullable=true,translatable=true
If you also want to have the PHPCR node mapped, you need
/**
* #PHPCRODM\Node
*/
public $node;
I've got three classes. The File-Class has a reference to Foobar and Game inherits from Foobar. There are some other Classes which also inherit from Foobar but i left them out as they aren't relevant here. I also left out some unrelevant fields and their getters and setters.
The plan is that every Game has two images, the mainImage and the secondaryImage. I've put those fields into a seperate class from which Game inherits because i need them for a few other classes too.
My problem is that if I load the games from the database as soon as i try to iterate over them I get the following exception:
Notice: Undefined index: in C:\xampp\htdocs\Symfony\vendor\doctrine\mongodb-odm\lib\Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo.php line 1293
For reference here are the lines of ClassMetadataInfo.php
public function getPHPIdentifierValue($id)
{
$idType = $this->fieldMappings[$this->identifier]['type'];
return Type::getType($idType)->convertToPHPValue($id);
}
Here are my classes
File-Class:
namespace Project\MainBundle\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
/**
* #MongoDB\Document
*/
class File
{
/**
* #MongoDB\Id(strategy="INCREMENT")
*/
protected $id;
/**
* #MongoDB\ReferenceOne(targetDocument="Foobar", inversedBy="mainImage")
*/
private $mainImage;
/**
* #MongoDB\ReferenceOne(targetDocument="Foobar", inversedBy="secondaryImage")
*/
private $secondaryImage;
/**
* Get id
*/
public function getId()
{
return $this->id;
}
public function setMainImage($mainImage)
{
$this->mainImage = $mainImage;
return $this;
}
public function getMainImage()
{
return $this->mainImage;
}
public function setSecondaryImage($secondaryImage)
{
$this->secondaryImage = $secondaryImage;
return $this;
}
public function getSecondaryImage()
{
return $this->secondaryImage;
}
}
Foobar-Class:
namespace Project\MainBundle\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
/**
* #MongoDB\MappedSuperclass
*/
abstract class Foobar
{
/**
* #MongoDB\Id(strategy="INCREMENT")
*/
protected $id;
/**
* #MongoDB\ReferenceOne(targetDocument="File", mappedBy="mainImage")
*/
protected $mainImage;
/**
* #MongoDB\ReferenceOne(targetDocument="File", mappedBy="secondaryImage")
*/
protected $secondaryImage;
/**
* Get id
*/
public function getId()
{
return $this->id;
}
/**
* Set mainImage
*/
public function setMainImage($file)
{
$file->setMainImage($this);
$this->mainImage = $file;
return $this;
}
/**
* Get mainImage
*/
public function getMainImage()
{
return $this->mainImage;
}
/**
* Set secondaryImage
*/
public function setSecondaryImage($file)
{
$file->setSecondaryImage($this);
$this->secondaryImage = $file;
return $this;
}
/**
* Get secondaryImage
*/
public function getSecondaryImage()
{
return $this->secondaryImage;
}
}
Game-Class:
namespace Project\MainBundle\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
/**
* #MongoDB\Document
*/
class Game extends Foobar
{
/**
* #MongoDB\String
*/
private $name;
/**
* Set name
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*/
public function getName()
{
return $this->name;
}
}
Though it doesn't really matter but here is my function i want to execute:
$dm = $this->get('doctrine_mongodb')->getManager();
$games_all = $dm->getRepository("ProjectMainBundle:Game")->createQueryBuilder()->sort('id', 'ASC')->getQuery()->execute();
foreach ($games_all as $singlegame) { // it breaks here
// Here i would do stuff
}
Is this a bug in Doctrine ODM or am I doing something wrong? Are the classes correct? I have tried everything but it just wont work.
I think it is too late for your question, but maybe there are other users having the same problem (as me).
The problem is related to Foobar being a MappedSuperclass. Had the same problem as described by you and at https://github.com/doctrine/mongodb-odm/issues/241.
Solution is to not reference the abstract class Foobar (=MappedSuperclass) but a concrete implementation (=Document) - as in your case - Game.
See also Doctrine ODM returns proxy object for base class instead of sub-classed document
Please help me to translate custom annotation.
I'm trying to translate #Render(title="Page"). Translate generator not found this, and title not traslate.
I try to understand how it is done in the component validation Symfony but nothing happens.
<?php
namespace Shooos\ProductBundle\Controller\Admin;
use Sensio\Bundle\FrameworkExtraBundle\Configuration as PRS;
use Shooos\CoreBundle\Controller\BaseController;
use Aft\RenderParkingBundle\Annotations as CA;
use Gedmo\Mapping\Annotation\Translatable;
/**
* #PRS\Route("/admin")
* Class CategoryController
* #package Shooos\ProductBundle\Controller\Admin
*/
class CategoryController extends BaseController
{
/**
* #CA\Render(title="Categories")
* #PRS\Route("/categories", name="admin.categories")
*/
public function indexAction()
{
}
}
<?php
namespace Aft\RenderParkingBundle\Annotations\Driver;
use Doctrine\Common\Annotations\Reader;
use Sensio\Bundle\FrameworkExtraBundle\Templating\TemplateGuesser;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Aft\RenderParkingBundle\Annotations;
use Symfony\Component\Translation\TranslatorInterface;
class AnnotationDriver
{
/**
* #var Reader
*/
private $reader;
/**
* #var TemplateGuesser
*/
private $guesser;
/**
* #var TranslatorInterface
*/
private $translator;
public function __construct(Reader $reader, TemplateGuesser $guesser, TranslatorInterface $translator)
{
$this->reader = $reader;
$this->guesser = $guesser;
$this->translator = $translator;
}
/**
* This event occurs when call any controller
*/
public function onKernelController(FilterControllerEvent $event)
{
/** Controller exists */
if (!is_array($controller = $event->getController())) {
return;
}
/**
* Controller
* #var \ReflectionObject $object
*/
$object = new \ReflectionObject($controller[0]);
$method = $object->getMethod($controller[1]);
foreach ($this->reader->getMethodAnnotations($method) as $configuration) {
if ($configuration instanceof Annotations\Render) {
$request = $event->getRequest();
$title = $this->translator->trans($configuration->getTitle());
$request->attributes->set('_page_title', $title);
if (null === $configuration->getTemplate()) {
$configuration->setTemplate(
$this->guesser->guessTemplateName(
$controller,
$request
));
}
$request->attributes->set('_page_template', $configuration->getTemplate());
}
}
}
}
On your annotation to object converter, where you inject the annotation reader, inject the translator service and translate the value at the transformation process, from annotation to object.
$description = $this->translator->trans($transformedAnnotationObject->getDescription());