I've got a problem with JMSSerializerBundle.
I have my entity AGVote there :
<?php
namespace K\AGBundle\Entity;
use JMS\SerializerBundle\Annotation\Type;
use JMS\SerializerBundle\Annotation\Accessor;
use JMS\SerializerBundle\Annotation\AccessType;
use JMS\SerializerBundle\Annotation\Exclude;
use JMS\SerializerBundle\Annotation\ExclusionPolicy;
use Doctrine\ORM\Mapping as ORM;
/**
* K\AGBundle\Entity\AGVote
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
*
*/
/*
*
/** #AccessType("public_method") */
class AGVote
{
/**
* #Type("integer")
* #Accessor(getter="getId")
*/
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
public $id;
/**
* #ORM\Column(type="text")
* #Accessor(getter="getQuestion")
* #Type("text")
*/
public $question;
/**
* #ORM\Column(type="smallint")
* #Type("integer")
* #Accessor(getter="getActif")
*/
public $actif;
/**
* #ORM\ManyToOne(targetEntity="\K\KBundle\Entity\Users", cascade={"all"})
* #Exclude
*/
protected $users;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set question
* Nb : Only AG admin can set a question
* #param text $question
*/
public function setQuestion($question)
{
$this->question = $question;
}
/**
* Get question
*
* #return text
*/
public function getquestion()
{
return $this->question;
}
/**
* Set actif
*
* #param smallint $actif
*/
public function setActif($actif)
{
$this->actif = $actif;
}
/**
* Get actif
*
* #return smallint
*/
public function getActif()
{
return $this->actif;
}
/**
* Set Users
*
* #param K\KBundle\Entity\Province $Users
*/
public function setUsers(\K\KBundle\Entity\Users $users)
{
$this->users = $users;
}
/**
* Get Users
*
* #return K\KBundle\Entity\Users
*/
public function getUsers()
{
return $this->users;
}
public function __toString()
{
return $this->getquestion();
}
}
I have made a controller that juste return me an AGVote Entity in Json :
public function jsonvoteAction($id) {
$em = $this->getDoctrine()->getEntityManager();
$entity = $em->getRepository('KAGBundle:AGVote')->findOneById($id);
if ($entity->getActif() == 1) {
$serializer = $this->container->get('serializer');
$serializer->serialize($entity, 'json');
$response = new Response($serializer);
return $reponse;
}
}
I have a response in Json but it is a error saying :
[{"message":"The Response content must be a string or object implementing __toString(), \"object\" given.","class":"UnexpectedValueException","trace":
In fact I have already implement a __toString() method inside of all my entities.
Does anyone have an idea ?
Thanks you :)
When you call the serialize method on the $serializer, it returns the serialized data (a string).
The problem is that you do not use this returned value, and create the response with the $serializer itself, which makes no sense.
First, store the serialized $entity:
$serializedEntity = $serializer->serialize($entity, 'json');
Then, you can return a new response using with string:
return new Response($serializedEntity, 200, array('Content-Type' => 'application/json'));
Related
I would like to serialize my objects into text fields in order to store a representation of them for traceability.
What I really want is a JSON representation of the entity's properties, and whenever there is an object, I would like a JSON representation of that as well, but only on that first level, I don't want it to dig deeper into what possible objects and relations there is below that, I'm happy with the object ID's
Many objects reference other objects, and from this documentation https://symfony.com/doc/current/components/serializer.html#handling-circular-references it appears this can be easily handled by just storing the object's ID rather than serializing the entire object (again). But in my case it doesn't work =) Am I missing something critical here?
Entity
<?php
namespace App\Entity;
use App\Repository\RegularServiceHoursRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass=RegularServiceHoursRepository::class)
*/
class RegularServiceHours
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="datetime")
*/
private $open;
/**
* #ORM\Column(type="datetime")
*/
private $close;
/**
* #var object \App\Entity\ACRGroup
*
* #ORM\ManyToOne(targetEntity="\App\Entity\ACRGroup", inversedBy="regularServiceHours")
* #ORM\JoinColumn(name="acr_group", referencedColumnName="id", nullable=false)
*/
protected $ACRGroup;
public function getId(): ?int
{
return $this->id;
}
public function getOpen(): ?\DateTimeInterface
{
return $this->open;
}
public function setOpen(\DateTimeInterface $open): self
{
$this->open = $open;
return $this;
}
public function getClose(): ?\DateTimeInterface
{
return $this->close;
}
public function setClose(\DateTimeInterface $close): self
{
$this->close = $close;
return $this;
}
/**
* Set aCRGroup
*
* #param \App\Entity\ACRGroup $aCRGroup
*
* #return DebitPeriod
*/
public function setACRGroup(\App\Entity\ACRGroup $aCRGroup)
{
$this->ACRGroup = $aCRGroup;
return $this;
}
/**
* Get aCRGroup
*
* #return \App\Entity\ACRGroup
*/
public function getACRGroup()
{
return $this->ACRGroup;
}
/**
* Get debitTimeSeconds
*
* #return int
*/
public function getTimeSeconds()
{
$open = $this->getOpen();
$close = $this->getClose();
$r = $close->format('U') - $open->format('U');
return $r;
}
}
Controller
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Serializer;
public function log($type,$message,$unit=null,$previous=null,$current=null) {
//We only log successful operations.
//If you want to log also errors, be very careful to not have anything persisted already, in que to be flushed. The flush below will store possible entities under conflict check and entities that sent you here due to a failed validation/conflict check, entities that weren't meant to be saved but rejected and forgotten.
if ($type == 'success') {
$encoder = new JsonEncoder();
$defaultContext = [
AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function ($object, $format, $context) {
return $object->getId();
},
];
$normalizer = new ObjectNormalizer(null, null, null, null, null, null, $defaultContext);
$serializer = new Serializer([$normalizer], [$encoder]);
dd($serializer->serialize($current, 'json'));
It never gets to the dump action, it chews for 30 seconds until memory is exhausted, and then it says:
Error: Maximum execution time of 30 seconds exceeded
Have I somehow missed where to set a default depth of 1? (I understood depth 1 to be the default when nothing was set).
Pay attention to Serialization Groups Attributes. With this attribute you can select the desired data when serializing an object.
Specify groups to the desired object properties:
class RegularServiceHours
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
* #Groups({"default"})
*/
private $id;
/**
* #ORM\Column(type="datetime")
*/
private $open;
/**
* #ORM\Column(type="datetime")
* #Groups({"default"})
*/
private $close;
/**
* #var object \App\Entity\ACRGroup
*
* #ORM\ManyToOne(targetEntity="\App\Entity\ACRGroup", inversedBy="regularServiceHours")
* #ORM\JoinColumn(name="acr_group", referencedColumnName="id", nullable=false)
* #Groups({"default"})
*/
private $ACRGroup;
}
Also specify a group for the ACRGroup identifier
class ACRGroup
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
* #Groups({"default"})
*/
private $id;
}
And just specify the group when serializing the object
$serializer->serialize($current, 'json', ['groups' => ['default']]);
I think that should solve your problem
I have below a query build with eloquent on laravel, it is very slow (50 points it take to show around 10 second), i can't understand if it a problem of the query construct or some missing index on table, some advise?
thanks
Points model (around 5000 records on the table)
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use OwenIt\Auditing\Contracts\Auditable;
class Points extends Model implements Auditable
{
use \OwenIt\Auditing\Auditable;
use SoftDeletes;
/**
* Indicates if the model should be timestamped.
*
* #var bool
*/
public $timestamps = false;
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'points';
/**
* The database primary key value.
*
* #var string
*/
protected $primaryKey = 'id';
/**
* Attributes that should be mass-assignable.
*
* #var array
*/
protected $fillable = [
'schedule_id',
'customer_id',
'cart_zone_id',
'indirizzo',
'latitudine',
'longitudine',
'tipo',
'enabled'
];
/**
* The attributes that should be mutated to dates.
*
* #var array
*/
protected $dates = [];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [];
/**
* Get the Schedule for this model.
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function Schedule()
{
return $this->belongsTo('App\Models\Schedules', 'schedule_id', 'id')->withTrashed();
}
/**
* Get the Customer for this model.
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function Customer()
{
return $this->belongsTo('App\Models\Customers', 'customer_id', 'id')->withTrashed();
}
/**
* Get the CartZone for this model.
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function CartZone()
{
return $this->belongsTo('App\Models\CartZones', 'cart_zone_id', 'id')->withTrashed();
}
}
Events model (around 400k records on the table)
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
class RecordEvents extends Model
{
/**
* Indicates if the model should be timestamped.
*
* #var bool
*/
public $timestamps = false;
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'events';
/**
* The database primary key value.
*
* #var string
*/
protected $primaryKey = 'id';
/**
* Attributes that should be mass-assignable.
*
* #var array
*/
protected $fillable = [
'code',
'description',
'device_id',
'schedule_id',
'boa_id',
'parent_id',
'user_id',
'skip_unique'
];
/**
* The attributes that should be mutated to dates.
*
* #var array
*/
protected $dates = [];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [];
/**
* Get the device for this model.
*
* #return App\Models\Device
*/
public function device()
{
return $this->belongsTo('App\Models\Devices', 'device_id')->withTrashed();
}
/**
* Get the schedule for this model.
*
* #return App\Models\Schedule
*/
public function schedule()
{
return $this->belongsTo('App\Models\Schedules', 'schedule_id')->withTrashed();
}
/**
* Get the boa for this model.
*
* #return App\Models\Boa
*/
public function boa()
{
return $this->belongsTo('App\Models\Points', 'boa_id')->withTrashed();
}
/**
* Get the boa for this model.
*
* #return App\Models\Boa
*/
public function pvd()
{
return $this->belongsTo('App\Models\CustomersPvds', 'pvd_id')->withTrashed();
}
/**
* Get the boa for this model.
*
* #return App\Models\Boa
*/
public function cartzone()
{
return $this->belongsTo('App\Models\CartZones', 'cartzone_id')->withTrashed();
}
/**
* Get the parent for this model.
*
* #return App\Models\Parent
*/
public function parent()
{
return $this->belongsTo('App\Models\Parent', 'parent_id')->withTrashed();
}
/**
* Get the user for this model.
*
* #return App\Models\User
*/
public function user()
{
return $this->belongsTo('App\Models\Users', 'user_id')->withTrashed();
}
/**
* Get created_at in array format
*
* #param string $value
* #return array
*/
public function getCreatedAtAttribute($value)
{
return \DateTime::createFromFormat($this->getDateFormat(), $value)->format('d/m/Y H:i:s');
}
/**
* Get data in array format
*
* #param string $value
* #return string
*/
public function getDataRawAttribute($value)
{
return Carbon::createFromFormat('d/m/Y H:is', $this->created_at)->format('Y-m-d H:i:s');
}
}
Query
$pointsCartZone = Points::where('cart_zone_id', $trip->cart_zone_id)
->where('enabled', 1)
->select('*', DB::raw('(select COUNT(*) as ch from events where boa_id = points.id and schedule_id = ' . $id . ') as passed'))
->get();
try to put on events table the index on boa_id and schedule_id and on points table on cart_zone_id.
let me know if it works :-)
The PHP code for Sonata Admin using "with" is formatted by the auto formatter like this:
$formMapper->with('User')
->add('firstName')
->add('lastName')
->end()
->with('Additional Information')
->add('gender')
->end()
To get a more human readable code style I currently disable and enable the formatter and format the code like this:
// #formatter:off
$formMapper
->with('User')
->add('firstName')
->add('lastName')
->end()
->with('Additional Information')
->add('gender')
->end()
;
// #formatter:on
Is there a way to define a code style for a specific code phrase? E.g with regex to format the code automatically to the second style?
I just found a solution especially for the case in sonata admin by extending the php code to make it "autoformattable". Here is what I have done:
ObjectTypeCaster.php (from https://stackoverflow.com/a/53081037/301277):
class ObjectTypeCaster
{
/**
* This method is able to recast an object by copying all properties
*/
public static function castAs($sourceObject, $newClass)
{
$castedObject = new $newClass();
$reflectedSourceObject = new \ReflectionClass($sourceObject);
$reflectedSourceObjectProperties = $reflectedSourceObject->getProperties();
foreach ($reflectedSourceObjectProperties as $reflectedSourceObjectProperty) {
$propertyName = $reflectedSourceObjectProperty->getName();
$reflectedSourceObjectProperty->setAccessible(true);
$castedObject->$propertyName = $reflectedSourceObjectProperty->getValue($sourceObject);
}
return $castedObject;
}
}
Extend the FormMapper and ShowMapper like this:
IndentedFormMapper.php:
<?php
namespace App\Admin\Form;
use App\Helper\ObjectTypeCaster;
use Sonata\AdminBundle\Admin\AdminInterface;
use Sonata\AdminBundle\Builder\BuilderInterface;
use Sonata\AdminBundle\Builder\FormContractorInterface;
use Sonata\AdminBundle\Form\FormMapper;
use Symfony\Component\Form\FormBuilderInterface;
class IndentedFormMapper extends FormMapper
{
/**
* Makes protected property from BaseMapper public
*
* #var AdminInterface
*/
public $admin;
/**
* Makes protected property from BaseMapper public
*
* #var BuilderInterface
*/
public $builder;
/**
* Makes protected property from FormMapper public
*
* #var FormBuilderInterface
*/
public $formBuilder;
/**
* Makes protected property from BaseGroupedMapper public
*
* #var string|null
*/
public $currentGroup;
/**
* Makes protected property from BaseGroupedMapper public
*
* #var string|null
*/
public $currentTab;
/**
* Makes protected property from BaseGroupedMapper public
*
* #var bool|null
*/
public $apply;
/**
* We overwrite the constructor from FormMapper and BaseMapper to be able to create
* this object without any parameter. Its properties are copied by ObjectTypeCaster.
*
* IndentedFormMapper constructor.
*
* #see ObjectTypeCaster
*/
public function __construct()
{
}
/**
* This makes it possible to chain this object in a way that allows
* autoformatting in IntelliJ IDEs. Usage:
*
* $formMapper
* ->tab('General')
* ->chain(function ($formMapper) {
* $formMapper
* ->add('...')
* // Default usage here, you can also chain again
* ;
* })
* ->end()
* ;
*
* #param $callback
* #return $this
*/
public function chain($callback)
{
$callback($this);
return $this;
}
}
IndentedShowMapper.php:
<?php
namespace App\Admin\Form;
use App\Helper\ObjectTypeCaster;
use Sonata\AdminBundle\Admin\AdminInterface;
use Sonata\AdminBundle\Builder\BuilderInterface;
use Sonata\AdminBundle\Builder\FormContractorInterface;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Show\ShowMapper;
use Symfony\Component\Form\FormBuilderInterface;
class IndentedShowMapper extends ShowMapper
{
/**
* Makes protected property from ShowMapper public
*
* #var AdminInterface
*/
public $list;
/**
* Makes protected property from BaseGroupedMapper public
*
* #var string|null
*/
public $currentGroup;
/**
* Makes protected property from BaseGroupedMapper public
*
* #var string|null
*/
public $currentTab;
/**
* Makes protected property from BaseGroupedMapper public
*
* #var bool|null
*/
public $apply;
/**
* Makes protected property from BaseMapper public
*
* #var AdminInterface
*/
public $admin;
/**
* Makes protected property from BaseMapper public
*
* #var BuilderInterface
*/
public $builder;
/**
* We overwrite the constructor from ShowMapper and BaseMapper to be able to create
* this object without any parameter. Its properties are copied by ObjectTypeCaster.
*
* IndentedFormMapper constructor.
*
* #see ObjectTypeCaster
*/
public function __construct()
{
}
/**
* This makes it possible to chain this object in a way that allows
* autoformatting in IntelliJ IDEs. Usage:
*
* $showMapper
* ->tab('General')
* ->chain(function ($showMapper) {
* $showMapper
* ->add('...')
* // Default usage here, you can also chain again
* ;
* })
* ->end()
* ;
*
* #param $callback
* #return $this
*/
public function chain($callback)
{
$callback($this);
return $this;
}
}
Add a BaseAdmin class from that you inherit in all your admin classes and add the following methods:
<?php
use App\Admin\Form\IndentedFormMapper;
use App\Admin\Form\IndentedShowMapper;
use App\Entity\User;
use App\Helper\ObjectTypeCaster;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Mapper\BaseMapper;
use Sonata\AdminBundle\Show\ShowMapper;
class BaseAdmin extends AbstractAdmin
{
/**
* #param FormMapper $formMapper
* #throws \ReflectionException
*/
protected function configureFormFields(FormMapper $formMapper)
{
$indentFormMapper = ObjectTypeCaster::castAs($formMapper, IndentedFormMapper::class);
$this->configureFormFieldsIndention($indentFormMapper);
}
/**
* #param IndentedFormMapper $formMapper
*/
protected function configureFormFieldsIndention(IndentedFormMapper $formMapper)
{
}
/**
* #param ShowMapper $showMapper
* #throws \ReflectionException
*/
protected function configureShowFields(ShowMapper $showMapper)
{
$indentShowMapper = ObjectTypeCaster::castAs($showMapper, IndentedShowMapper::class);
$this->configureShowFieldsIndention($indentShowMapper);
}
/**
* #param IndentedShowMapper $showMapper
*/
protected function configureShowFieldsIndention(IndentedShowMapper $showMapper)
{
}
}
Use it in your BaseAdmin child classes like this:
ExampleAdmin.php:
<?php
namespace App\Admin;
use App\Admin\Form\IndentedFormMapper;
use App\Admin\Form\IndentedShowMapper;
use App\Entity\Example;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Show\ShowMapper;
class ExampleAdmin extends BaseAdmin
{
// ...
/**
* #param IndentedFormMapper $formMapper
*/
protected function configureFormFieldsIndention(IndentedFormMapper $formMapper)
{
$formMapper
->tab('General')
->chain(function ($formMapper) {
$formMapper
->with(
'Example',
[
'class' => AdminLayout::HALF,
]
)
->chain(function ($formMapper) {
$formMapper
->add(
'name',
Types::TEXT
)
;
})
->end()
;
})
->end()
;
}
/**
* #param IndentedShowMapper $showMapper
*/
protected function configureShowFieldsIndention(IndentedShowMapper $showMapper)
{
$mapData = $this->getMapData();
$showMapper
->with('Internal')
->chain(function ($showMapper) {
$showMapper
->add('id')
->add('createdAt')
->add('updatedAt')
;
})
->end()
;
}
}
I'm currently working on a project for which i have to deal with a lot of users fields (+/- 80). My first approach was (and still is) to vertically partition the user table in 4 tables : main informations, administrative informations, banking informations, statistics informations. The ids of the 3 extra tables rows refer to the auto incremented id of the main table.
My questions are :
Is it relevant to do so ?
It seems like, with the way i did the
association, Doctrine isn't able to flush one object into database
in one shot :
Entity of type MyVendor\User\UserBundle\Entity\UsersInfosBank is missing an
assigned ID for field 'id_user'. The identifier generation strategy
for this entity requires the ID field to be populated before
EntityManager#persist() is called. If you want automatically generated
identifiers instead you need to adjust the metadata mapping
accordingly.
The project will be pushed into production in 3 weeks, so we're still able to go back to a more traditionnal way to store these datas into one huge table…
****************UPDATE*****************
WHOLE CODE LOGIC BELOW*****************
I just removed most of the properties and methods which are not concerned by my problem…
And yes, i'm using FOSUserBundle ;)
UsersMain
<?php
namespace LCP\User\UserBundle\Entity;
use FOS\UserBundle\Model\GroupableInterface;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
/**
* UsersMain
*
* #ORM\Table(name="lcp_users_main")
* #ORM\Entity
*/
class UsersMain extends BaseUser
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
*
* #ORM\OneToOne(targetEntity="LCP\User\UserBundle\Entity\UsersInfosBank", mappedBy="id_user", cascade={"persist"})
*/
private $infosBank;
/**
*
* #ORM\OneToOne(targetEntity="LCP\User\UserBundle\Entity\UsersApodis", mappedBy="id_user", cascade={"persist"})
*/
private $apodis;
/**
*
* #ORM\OneToOne(targetEntity="LCP\User\UserBundle\Entity\UsersInfosAdmin", mappedBy="id_user", cascade={"persist"})
*/
private $infosAdmin;
/**
*
* #ORM\OneToOne(targetEntity="LCP\User\UserBundle\Entity\UsersInfosStats", mappedBy="id_user", cascade={"persist"})
*/
private $infosStats;
/**
* Constructor
*/
public function __construct()
{
$this->groups = new \Doctrine\Common\Collections\ArrayCollection();
$this->creationDate = new \Datetime();
}
/**
* Set apodis
*
* #param \LCP\User\UserBundle\Entity\UsersApodis $apodis
* #return UsersMain
*/
public function setApodis(\LCP\User\UserBundle\Entity\UsersApodis $apodis = null)
{
if(is_null($this->apodis) && is_null($apodis)){
$this->apodis = new \LCP\User\UserBundle\Entity\UsersApodis();
} else {
$this->apodis = $infosBank;
}
$this->apodis->setIdUser($this);
return $this;
}
/**
* Get apodis
*
* #return \LCP\User\UserBundle\Entity\UsersApodis
*/
public function getApodis()
{
if(is_null($this->infosAdmin)){
$this->infosAdmin = new \LCP\User\UserBundle\Entity\UsersApodis();
}
return $this->apodis;
}
/**
* Set infosAdmin
*
* #param \LCP\User\UserBundle\Entity\UsersInfosAdmin $infosAdmin
* #return UsersMain
*/
public function setInfosAdmin(\LCP\User\UserBundle\Entity\UsersInfosAdmin $infosAdmin = null)
{
if(is_null($this->infosAdmin) && is_null($infosAdmin)){
$this->infosAdmin = new \LCP\User\UserBundle\Entity\UsersInfosAdmin();
} else {
$this->infosAdmin = $infosAdmin;
}
$this->infosAdmin->setIdUser($this);
return $this;
}
/**
* Get infosAdmin
*
* #return \LCP\User\UserBundle\Entity\UsersInfosAdmin
*/
public function getInfosAdmin()
{
if(is_null($this->infosAdmin)){
$this->infosAdmin = new \LCP\User\UserBundle\Entity\UsersInfosAdmin();
}
return $this->infosAdmin;
}
/**
* Set infosStats
*
* #param \LCP\User\UserBundle\Entity\UsersInfosStats $infosStats
* #return UsersMain
*/
public function setInfosStats(\LCP\User\UserBundle\Entity\UsersInfosStats $infosStats = null)
{
if(is_null($this->infosStats) && is_null($infosStats)){
$this->infosStats = new \LCP\User\UserBundle\Entity\UsersInfosStats();
} else {
$this->infosStats = $infosAdmin;
}
$this->infosStats->setIdUser($this);
return $this;
}
/**
* Get infosStats
*
* #return \LCP\User\UserBundle\Entity\UsersInfosStats
*/
public function getInfosStats()
{
if(is_null($this->infosStats)){
$this->infosStats = new \LCP\User\UserBundle\Entity\UsersInfosStats();
}
return $this->infosStats;
}
}
UsersInfosAdmin
<?php
namespace LCP\User\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* UsersInfosAdmin
*
* #ORM\Table(name="lcp_users_infos_admin", indexes={#ORM\Index(name="pharmacyCip_idx", columns={"pharmacy_cip"})})
* #ORM\Entity
*/
class UsersInfosAdmin
{
/**
* #var integer
* #ORM\Id
* #ORM\OneToOne(targetEntity="LCP\User\UserBundle\Entity\UsersMain", cascade={"persist"}, inversedBy="infosAdmin")
*/
private $id_user;
/**
* Set id_user
*
* #param \LCP\User\UserBundle\Entity\UsersMain $idUser
* #return UsersInfosAdmin
*/
public function setIdUser(\LCP\User\UserBundle\Entity\UsersMain $idUser)
{
$this->id_user = $idUser;
return $this;
}
/**
* Get id_user
*
* #return \LCP\User\UserBundle\Entity\UsersMain
*/
public function getIdUser()
{
return $this->id_user;
}
}
UsersInfosStats
<?php
namespace LCP\User\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* UsersInfosStats
*
* #ORM\Table(name="lcp_users_infos_stats")
* #ORM\Entity
*/
class UsersInfosStats
{
/**
* #var integer
* #ORM\Id
* #ORM\OneToOne(targetEntity="LCP\User\UserBundle\Entity\UsersMain", cascade={"persist"}, inversedBy="infosStats")
*/
private $id_user;
/**
* Set id_user
*
* #param \LCP\User\UserBundle\Entity\UsersMain $idUser
* #return UsersInfosStats
*/
public function setIdUser(\LCP\User\UserBundle\Entity\UsersMain $idUser)
{
$this->id_user = $idUser;
return $this;
}
/**
* Get id_user
*
* #return \LCP\User\UserBundle\Entity\UsersMain
*/
public function getIdUser()
{
return $this->id_user;
}
}
Here is how i create a user (it is through a secured admin area form) :
/**
* This action get the field from the form
*/
public function userSaveAction(Request $request)
{
$userManager = $this->get('fos_user.user_manager');
$this->request = $request;
$userValues = $this->getRequest()->request->all();
$this->em = $this->getDoctrine()->getManager();
$user = $this->processUser($userValues);
$userManager->updateUser($user, false);
return $this->redirect($this->generateUrl('lcp_admin_user_edit', ['id'=>$user->getId()]));
}
/**
* This method process user fields and uses setters (or getters for joined entities) to set datas of user
*/
private function processUser($userValues)
{
if($userValues['userId']=="") {
$user = $this->get('fos_user.user_manager')->createUser();
} else {
$user = $this->em->getRepository('LCPUserBundle:UsersMain')
->findOneBy(['id' => $userId]);
}
//construction of related entities
$user->setInfosAdmin();
$user->setInfosBank();
$user->setInfosStats();
$user->setApodis();
unset($userValues['userId']);
//this method inspect submitted field to check if they are mandatory or if current user is allowed to modify them
$this->fields = $this->getInfosFields();
foreach($userValues as $input=>$value){
list($entity,$input)=explode('-',$input);
if($input=='plainPassword' && $value==''){
continue;
}
if($entity=='Main'){
$setter = 'set'.ucfirst($input);
$user->$setter($value);
} else {
$setter = 'set'.ucfirst($input);
$join = 'get'.$entity;
$user->$join()->$setter($value);
}
}
return $user;
}
According to the comments your UsersMain property need to be like this:
/**
* Join administrative informations
* #ORM\OneToOne(targetEntity="MyVendor\User\UserBundle\Entity\UsersInfosAdmin", mappedBy="userMain", cascade={"persist"}, orphanRemoval=true)
*/
private $infosAdmin;
/**
* Setter InfosAdmin
*/
public function setInfosAdmin(UsersInfosAdmin $infosAdmin)
{
$this->infosAdmin = $infosAdmin;
$this->infosAdmin->setUserMain($this); <--- add this to set userMain on associated object
return $this;
}
then your UsersInfosAdmin properties need to be like this:
/**
* #var integer
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToOne(targetEntity="MyVendor\User\UserBundle\Entity\UsersMain", inversedBy="userMain")
*/
private $userMain;
Issue
I've been trying to follow the tutorial to embed a form in another one. What I'm trying to do here is add a task, and add multiple categories to it. I'm using the example at http://symfony.com/doc/current/book/forms.html#embedding-a-single-object, but I added some ORM annotations to make the relation many-to-many. As such, here is my code for the Task & Category entities:
Code
Task entity
<?php
namespace Acme\TaskBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Task
*
* #ORM\Table()
* #ORM\Entity
*/
class Task
{
/**
* #ORM\ManyToMany(targetEntity="Category", inversedBy="tasks")
* #ORM\JoinTable(name="tasks_categories")
*
* #Assert\Type(type="Acme\TaskBundle\Entity\Category")
*/
protected $categories;
// ...
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Task
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Constructor
*/
public function __construct()
{
$this->categories = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add categories
*
* #param \Acme\TaskBundle\Entity\Category $categories
* #return Task
*/
public function addCategorie(\Acme\TaskBundle\Entity\Category $categories)
{
$this->categories[] = $categories;
return $this;
}
/**
* Remove categories
*
* #param \Acme\TaskBundle\Entity\Category $categories
*/
public function removeCategorie(\Acme\TaskBundle\Entity\Category $categories)
{
$this->categories->removeElement($categories);
}
/**
* Get categories
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getCategories()
{
return $this->categories;
}
}
Category entity
<?php
namespace Acme\TaskBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\ORM\Mapping as ORM;
/**
* Category
*
* #ORM\Table()
* #ORM\Entity
*/
class Category
{
/**
* #ORM\ManyToMany(targetEntity="Task", mappedBy="categories")
*/
private $tasks;
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Category
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Constructor
*/
public function __construct()
{
$this->tasks = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add tasks
*
* #param \Acme\TaskBundle\Entity\Task $tasks
* #return Category
*/
public function addTask(\Acme\TaskBundle\Entity\Task $tasks)
{
$this->tasks[] = $tasks;
return $this;
}
/**
* Remove tasks
*
* #param \Acme\TaskBundle\Entity\Task $tasks
*/
public function removeTask(\Acme\TaskBundle\Entity\Task $tasks)
{
$this->tasks->removeElement($tasks);
}
/**
* Get tasks
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getTasks()
{
return $this->tasks;
}
}
Both forms have been auto-generated by using the doctrine:generate:form command. I changed the TaskType form to include the categories:
TaskType form
<?php
namespace Acme\TaskBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('categories', new CategoryType())
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\TaskBundle\Entity\Task',
'cascade_validation' => true,
));
}
public function getName()
{
return 'task';
}
}
Now when I go to the create page for tasks, I get this error:
The form's view data is expected to be an instance of class
Acme\TaskBundle\Entity\Category, but is an instance of class
Doctrine\Common\Collections\ArrayCollection. You can avoid this error
by setting the "data_class" option to null or by adding a view
transformer that transforms an instance of class
Doctrine\Common\Collections\ArrayCollection to an instance of
Acme\TaskBundle\Entity\Category.
I honestly have no idea how to fix it since this seemed a pretty straight-forward thing but apparently it isn't. Could someone help me out here please?
in your Task Entity remove the validation for categories.
Symfony tries to validate a ArrayCollection as one Category!(hence the error)
* #Assert\Type(type="Acme\TaskBundle\Entity\Category")
*/
$categories;
It isn't necessary since it is a collection. (validation will be based on what type of objects are in the collection)
If you created a CategoryType form then this form should return Acme\TaskBundle\Entity\Category for it's data class.
class CategoryType {
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\TaskBundle\Entity\Category',
.....
));
}
}
Also, in your TaskType
$builder
->add('name')
->add('categories', new CategoryType()) // new CategoryType()
// is not really needed here,
// symfony will automatically detect
// it's relation and create a new
// CategoryType if necessary.