I have 2 questions regarding validation. I make a lot of use of property methods (getters) in my entities (nicer code imho). This is one such entity:
class Spec2Events implements ValueAssignable
{
private $values;
/**
* #return \Doctrine\Common\Collections\Collection
*/
public function getValues()
{
return $this->values;
}
/**
* #return \Doctrine\Common\Collections\Collection
*/
public function getCauseOfDeathValues()
{
$codPms=array();
array_push($codPms,'Cause of death::Natural');
array_push($codPms,'Cause of death::Bycatch');
array_push($codPms,'Cause of death::Ship strike');
array_push($codPms,'Cause of death::Predation');
array_push($codPms,'Cause of death::Other');
array_push($codPms,'Cause of death::Unknown');
return $this->getValues()->filter(
function($entry) use ($codPms) {
return in_array($entry->getPmdSeqno()->getName(), $codPms);
}
);
}
}
$values in this case is a collection of SpecimenValues (which implements EntityValues). ValueAssignables have a collection of EntityValues.
An EntityValuesType class is the form for any class that implements EntityValues. This form has some text or choice childs.
EntityValuesType forms are called like this:
$builder->add('causeOfDeathValues', 'collection', array('type' => new EntityValuesType($this->doctrine),
'options' => array('data_class' => 'AppBundle\Entity\SpecimenValues'),
'allow_delete' => true,
'delete_empty' => true
)); //in order to check if using a class getter as a property works (fails)
$builder->add('values', 'collection', array('type' => new EntityValuesType($this->doctrine),
'options' => array('data_class' => 'AppBundle\Entity\SpecimenValues'),
'allow_delete' => true,
'delete_empty' => true
)); //in order to check if using a class member as a property works (works)
Validation.yml for SpecimenValues looks like this:
AppBundle\Entity\SpecimenValues:
properties:
pmdSeqno:
- NotBlank: ~
- NotNull: ~
s2eScnSeqno:
- NotBlank: ~
- NotNull: ~
description:
- Length:
min: 0
max: 250
value:
- NotBlank: ~
- NotNull: ~
- Length:
min: 1
max: 50
valueFlag:
- Length:
min: 0
max: 50
The Controller looks like this:
public function newAction()
{
$observation = $this->prepareObservation();
$form = $this->createForm(new ObservationsType($this->getDoctrine()), $observation);
return $this->render('AppBundle:Page:add-observations-specimens.html.twig', array(
'form' => $form->createView()
));
}
private function prepareObservation(){
$observation = new Observations();
$event = new EventStates();
$observation->setEseSeqno($event);
$s2e = new Spec2Events();
$event->setSpec2Events($s2e);
$this->instantiateSpecimenValues('Cause of death::Natural', $s2e, false);
$this->instantiateSpecimenValues('Cause of death::Bycatch', $s2e, false);
$this->instantiateSpecimenValues('Cause of death::Ship strike', $s2e, false);
$this->instantiateSpecimenValues('Cause of death::Predation', $s2e, false);
$this->instantiateSpecimenValues('Cause of death::Other', $s2e, false);
$this->instantiateSpecimenValues('Cause of death::Unknown', $s2e, false);
//...
return $observation;
}
private function instantiateSpecimenValues($pmName, &$s2e, $mustBeFlagged)
{
$em = $this->getDoctrine()->getManager();
$pm = $em->getRepository("AppBundle:ParameterMethods")->getParameterMethodByName($pmName);
$sv = new SpecimenValues();
$sv->setPmdSeqno($pm);
$sv->setS2eScnSeqno($s2e);
$sv->setValueFlagRequired($mustBeFlagged);
return $sv;
}
Now, my problem is that empty values are not blocked by the validator (no form error message appears).
If I add a validation constraint programmatically in FormEvents::PRE_SET_DATA, like this:
$options2['constraints'] = array(new \Symfony\Component\Validator\Constraints\NotNull());
it works, but the constraints placed in the .yml file are ignored. Is it possible to combine doing this 'programmatically' AND with validation.yml? In any case I'll write a callback to add in the .yml, so I prefer validation.yml.
Using a form child with name 'values', corresponding to the pure class member variable, works as it should: all required empty fields get a message. All other validation works normally.
What could solve this? I could also use 'values' and use twig to split the collection, but I like using methods as property accessors better.
Thanks!
I've solved this simply by creating the fields both as getters and as properties. The properties themselves are set in the setters. This is necessary otherwise the validator is never called.
So:
/**
* #var \Doctrine\Common\Collections\Collection
* #ORM\OneToMany(targetEntity="AppBundle\Entity\SpecimenValues", mappedBy="s2eScnSeqno")
*/
private $values;
private $causeOfDeathValues;
/**
* #param \Doctrine\Common\Collections\Collection $values
* #return Spec2Events
*/
public function setCauseOfDeathValues(\Doctrine\Common\Collections\Collection $values)
{
$this->causeOfDeathValues=$values;
$this->values= new \Doctrine\Common\Collections\ArrayCollection(
array_merge($this->getValues()->toArray(), $values->toArray())
);
return $this;
}
Related
In my entity file Collection.php I have self referencing many to many relationship:
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Collection")
* #ORM\JoinTable(name="collection_related_collections",
* joinColumns={#ORM\JoinColumn(name="collection_source", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="collection_target", referencedColumnName="id")}
* )
* #JMS\Groups({"collection_related_collections"})
* #JMS\Expose()
* #Assert\Count(
* max = 3,
* maxMessage = "You cannot specify more than {{ limit }} related collections"
* )
* #var Collection[]|ArrayCollection $relatedCollections
*/
protected $relatedCollections;
This is processed in a controller by a form handler:
$this->get('app_bundle.form.handler.api_form_handler')
->process($form);
Finnally my CollectionFormType.php has RelatedCollection field:
->add('relatedCollections', EntityType::class, [
'required' => false,
'choice_value' => 'hashId',
'choice_label' => function (Collection $collection) {
return $collection->getName();
},
'multiple' => true,
'expanded' => true,
'class' => Collection::class,
'query_builder' => function (CollectionRepository $collectionRepository) {
return $collectionRepository->getQueryBuilder();
}
])
Everything works fine when I am trying to PATCH an empty relatedCollection field:
[PATCH] http://symfony.dev/api/collections/{collectionHashId}
{
"collection": {
"relatedCollections": ["7D68076025", "196208D03D"]
}
}
But I cannot remove/replace elements trying
[PATCH] http://symfony.dev/api/collections/{collectionHashId}
{
"collection": {
"relatedCollections": ["7D68076025"]
}
}
This action has no effect. As a result value of relatedCollection field remains the same as I set it in the previous request:
"relatedCollections": ["7D68076025", "196208D03D"]
Also, I cannot remove relatedCollections values by sending an empty array [], because it makes no changes at all.
Is there a way to replace/remove ArrayCollection values using PATCH Method?
Have you tried this with PUT as well with a similar result? I suspect this is caused by the "owner" of the relationship being the child; in this case RelatedCollection as I understand it.
You'll need to update the owning side of the relationship if that is the case. Check this out for more information: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/unitofwork-associations.html
In Symfony 2.8 I've got Movie entity with actors field, which is ArrayCollection of entity Actor (ManyToMany) and I wanted the field to be ajax-loaded Select2.
When I don't use Ajax, the form is:
->add('actors', EntityType::class, array(
'class' => Actor::class,
'label' => "Actors of the work",
'multiple' => true,
'attr' => array(
'class' => "select2-select",
),
))
It works, and this is what profiler displays after form submit: http://i.imgur.com/54iXbZy.png
Actors' amount grown up and I wanted to load them with Ajax autocompleter on Select2. I changed form to ChoiceType:
->add('actors', ChoiceType::class, array(
'multiple' => true,
'attr' => array(
'class' => "select2-ajax",
'data-entity' => "actor",
),
))
//...
$builder->get('actors')
->addModelTransformer(new ActorToNumberModelTransformer($this->manager));
I made DataTransformer:
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Persistence\ObjectManager;
use CompanyName\Common\CommonBundle\Entity\Actor;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
class ActorToNumberModelTransformer implements DataTransformerInterface
{
private $manager;
public function __construct(ObjectManager $objectManager)
{
$this->manager = $objectManager;
}
public function transform($actors)
{
if(null === $actors)
return array();
$actorIds = array();
$actorsArray = $actors->toArray();
foreach($actorsArray as $actor)
$actorIds[] = $actor->getId();
return $actorIds;
}
public function reverseTransform($actorIds)
{
if($actorIds === null)
return new ArrayCollection();
$actors = new ArrayCollection();
$actorIdArray = $actorIds->toArray();
foreach($actorIdArray as $actorId)
{
$actor = $this->manager->getRepository('CommonBundle:Actor')->find($actorId);
if(null === $actor)
throw new TransformationFailedException(sprintf('An actor with id "%s" does not exist!', $actorId));
$actors->add($actor);
}
return $actors;
}
}
And registered form:
common.form.type.movie:
class: CompanyName\Common\CommonBundle\Form\Type\MovieType
arguments: ["#doctrine.orm.entity_manager"]
tags:
- { name: form.type }
But seems like the reverseTransform() is never called. I even put die() at the beginning of it - nothing happened. This is, what profiler displays after form submit: http://i.imgur.com/qkjLLot.png
I tried to add also ViewTransformer (code here: pastebin -> 52LizvhF - I don't want to paste more and I can't post more than 2 links), with the same result, except that reverseTransform() is being called and returns what it should return.
I know that this is an old question, but I was having a very similar problem. It turned out that I had to explicitly set the compound option to false.
That is to say, for the third parameter to the add() method, you need to add 'compound => false'.
This is a wierd situation because magento is loading my backend model, its just not calling it when I load and save it. I know this because 1. I see it in my database, 2. when I rename my backend model, my test case fails. Here is my code
It saves my values just fine and completely ignores my afterload and beforesave methods.
TEST CASE
<?php
class Super_Base_Test_Controller_Test extends EcomDev_PHPUnit_Test_Case_Controller {
const DEFAULTSTORE = 1;
public function setUpMocks() {
$this->setCurrentStore(1);
$customer = Mage::getSingleton('customer/customer')
->load(1);
$customer->setCoinBalance(20)
->save();
}
public function setUp() {
$this->setUpMocks();
$data = array(
'customer_id'=>1,
'message'=>'this is a test message',
'income'=>20,
'created_at'=>'9/11/84',
'updated_at'=>'9/11/84',
'current'=>1
);
$this->getRequest()->setParams($data);
}
protected function getTearDownOperation() {
return PHPUnit_Extensions_Database_Operation_Factory::TRUNCATE();
}
}
backend model
<?php
/**
* Created by PhpStorm.
* User: numerical25
* Date: 3/8/14
* Time: 6:22 PM
*/
class Super_Coin_Model_Customer_Attribute_Coinbalance extends Mage_Eav_Model_Entity_Attribute_Backend_Abstract {
protected function _afterLoad()
{
if (!is_array($this->getValue())) {
$value = $this->getValue();
$this->setValue(empty($value) ? false : unserialize($value));
}
}
protected function _beforeSave() {
if (is_array($this->getValue())) {
$this->setValue(serialize($this->getValue()));
}
}
public function setCoinAmount($amount) {
$this->setValue($amount);
}
}
installation file
$eavsetup->addAttribute('customer', 'coin_balance', array(
'input' => 'text',
'type' => 'decimal',
'label' => 'Customer Coin Balance',
'backend' => 'coin/customer_attribute_coinbalance',
'global' => 1,
'visible' => 1,
'required' => 0,
'user_defined' => 1, ));
When I set break points, the system completly ignores my methods.
Look at abstract class Mage_Eav_Model_Entity_Attribute_Backend_Abstract. It contains the following public methods: beforeSave() and afterLoad().
There are no _afterLoad() and _beforeSave() methods in that class
I am now using the FOSRestBundle in order to build a REST API within my Symfony application. The idea for now is to list some locations(hotels, restaurants...), I managed to configure the automatic routes with FOSRestBundle like:
/api/locations , /api/locations/{id} , /api/locations/{name}/detail
with this controller:
class LocationController extends FOSRestController implements ClassResourceInterface
{
/**
* GET /locations
*
* #return Array
*
*/
public function cgetAction()
{
$locations = $this->getDoctrine()
->getManager()
->getRepository('VisitBILocationsBundle:Location')
->findAll();
if (!$locations) {
return array(
'locations' => $locations,
'status' => 1
);
}
return array(
'locations' => $locations,
'status' => 0
);
}
/**
* GET /locations/{locationId}
*
* #return Array
*
*/
public function getAction($id)
{
$location = $this->getDoctrine()
->getManager()
->getRepository('VisitBILocationsBundle:Location')
->findBy(array('id' => $id));
if (!$location) {
return array(
'location' => $location,
'status' => 1
);
}
return array(
'location' => $location,
'status' => 0
);
}
/**
* GET /locations/{name}/detail
*
* #return Array
*/
public function getDetailAction($name)
{
$detail = $this->getDoctrine()
->getManager()
->getRepository('VisitBILocationsBundle:LocationDetail')
->findBy(array('name' => $name));
if (!$detail) {
return array(
'locationDetail' => $detail,
'status' => 1
);
}
return array(
'locationDetail' => $detail,
'status' => 0
);
}
}
I've been struggling with this, but would anyone know how should I proceed to generate one custom url like this:
/api/locations/nearby/{latitude}/{longitude}
The idea is that I would provide my own latitude and longitude, and the backend will calculate and provide the locations which are the closest to me.
Of course I've looked at the documentation of FOSRestBundle for manual route configuration, but since I spent some time trying to do it, I come here to ask for some help :)
If you want to manually define a route, it should just be as simple as adding the route to the existing routing configuration. How exactly you do it depends on how you're handling the routing configuration: annotation, yaml, or xml.
Option 1: YAML
In the routing.yml file (ex: src/Vendor/MyBundle/Resources/config/routing.yml) add something like:
location_nearby:
pattern: /api/locations/nearby/{latitude}/{longitude}
defaults: { _controller: "MyBundle:Location:nearby" }
requirements:
_method: GET
which would correspond to this method in LocationController:
public function nearbyAction($latitude, $longitude) { ... }
Option 2: Annotations
Add this use statement to the Controller file:
use FOS\RestBundle\Controller\Annotations\Get;
and then define the route above the controller method:
/**
* Return a nearby location
* #Get("/api/locations/nearby/{latitude}/{longitude}")
*/
public function nearbyAction($latitude, $longitude) { ... }
OK here is how to proceed, works fine for me:
I use the annotation system to route /locations/nearby/{latitude}/{longitude}
/**
* Return a nearby location
* #Get("/locations/nearby/{latitude}/{longitude}", requirements={"latitude" = "[-+]?(\d*[.])?\d+", "longitude" = "[-+]?(\d*[.])?\d+"})
*/
public function nearbyAction($latitude, $longitude) {...}
Then I have to specify float numbers with: requirements={"latitude" = "[-+]?(\d*[.])?\d+", "longitude" = "[-+]?(\d*[.])?\d+"}
Those will still be interpreted as string by the controller: "64.1333", I just have to use this in the controller:
floatval($latitude)
to get url parameters as float and then do my calculations!
I am facing a problem with validation of new uploaded file.
I have my Product entity:
// src/Acme/DemoBundle/Entity/Product
...
/**
* #ORM\OneToMany(targetEntity="Image", mappedBy="product", cascade={"persist"})
* #Assert\Image(
* minWidth = 10,
* maxWidth = 20,
* minHeight = 10,
* maxHeight = 20
* )
*/
protected $images;
...
public function __construct()
{
$this->images= new \Doctrine\Common\Collections\ArrayCollection();
}
public function getImages(){
return $this->images;
}
public function setImages($images){
$this->images = $images;
return $this;
}
Image entity is a very simple, with name, size, mimetype.
And I have working on some custom upload listener, so I am not using form and form->isValid. I validate like this:
...
public function onUpload(PostPersistEvent $event)
{
$em= $this->doctrine->getManager();
$product = $this->doctrine->getRepository('Acme\DemoBundle\Entity\Product')->findOneById($customId);
$image = new Image();
$image->setProduct($product)
->setName($uploadInfo->name)
->setStoredName($uploadInfo->storedName)
->setUuid($uploadInfo->uuid)
->setSize($uploadInfo->size)
->setMimeType($uploadInfo->mimeType);
$validator = Validation::createValidatorBuilder()
->enableAnnotationMapping()
->getValidator();
$a = $product->getImages();
$a->add($image);
$product->setImages($a);
$errors = $validator->validate($product);
And I've got an error:
{"message":"Expected argument of type string, object given","class":"Symfony\\Component\\Validator\\Exception\\UnexpectedTypeException","trace":[{"namespace":"","short_class":"","class":"","type":"","function":"","file":".../vendor\/symfony\/symfony\/src\/Symfony\/Component\/Validator\/Constraints\/FileValidator.php","line":98,"args":[]}
If let say I do NotNull annotation Assert on enother field (like name) - it works, I can get errors. But with ArrayCollection - is not.
I am doing something wrong and can't find info in the internet.
Could the gurus help me out?
To validate collection you can use All and Valid validators.
Acme\DemoBundle\Entity\Product:
properties:
images:
- Valid: ~
- All:
- NotNull: ~
Acme\DemoBundle\Entity\Image:
properties:
file:
- Image:
minWidth: 200
maxWidth: 400
minHeight: 200
maxHeight: 400