I have a problem with Doctrine2 and two relationed entities.
There is a user-entity that can (not must) have one or a collection of social-entity referenced which contains a social network link.
I do not control Doctrine and I'm still learning relationship.
I want to add a user with/without adding social network link.
After several researches and testing, I am still unable to find a solution.
Here is my user-entity
<?php
//...
/**
* User
*
* #ORM\Table(name="admin_users")
* #ORM\Entity(repositoryClass="UserRepository")
* #ORM\HasLifecycleCallbacks()
*/
class User implements AdvancedUserInterface, \Serializable
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="username", type="string", length=25, unique=true)
*/
private $username;
/**
* #var string
*
* #ORM\Column(name="password", type="string", length=40)
*/
private $password;
/**
*
* #var string
*
* #Assert\NotBlank
*/
private $plainPassword;
//...
/**
* #var ArrayCollection
*
* #ORM\OneToMany(targetEntity="Social", mappedBy="user", cascade={"persist","remove"})
* #ORM\JoinColumn(nullable=true)
*/
private $socials;
public function __construct()
{
$this->socials = new ArrayCollection();
}
//Some getters setters
/**
* Add socials
*
* #param Social $socials
* #return User
*/
public function addSocials(Social $socials)
{
$this->socials[] = $socials;
$socials->setUser($this);
return $this;
}
/**
* Remove socials
*
* #param Social $socials
*/
public function removeSocials(Social $socials)
{
$this->socials->removeElement($socials);
}
/**
* Get socials
*
* #return Collection
*/
public function getSocials()
{
return $this->socials;
}
}
Here is the social-entity
<?php
/**
* Social
*
* #ORM\Table(name="admin_social")
* #ORM\Entity(repositoryClass="SocialRepository")
*/
class Social
{
/**
* #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=20, nullable=true)
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="url", type="string", length=255, nullable=true)
*/
private $url;
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="socials", cascade={"persist","remove"})
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=true)
*/
private $user;
//getters setters
/**
* Set user
*
* #param User $user
* #return Social
*/
public function setUser(User $user)
{
$this->user = $user;
return $this;
}
/**
* Get user
*
* #return User
*/
public function getUser()
{
return $this->user;
}
}
The userType code looks like this:
$builder
->add('username', 'text', array(
'attr'=> array('class' => 'span6',),
'label_attr' => array('class' => 'control-label'),
)
)
// ....
->add('sociaux', 'collection', array('type' => new SocialType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,))
;
Finally the controller code :
public function addAction()
{
$user = new User;
// Create the form
$form = $this->createForm( new UserType, $user );
// Gets the request
$request = $this->getRequest();
// Checks if the request have type POST
if ( $request->getMethod() == 'POST' ) {
// Links the request and the form
$form->bind( $request );
// Checks if all input values are correct
if ( $form->isValid() ) {
// Save user object in database
$em = $this->getDoctrine()->getManager();
// Persist entity user
$em->persist( $user );
$em->flush();
//...
}
}
//...
}
When I try to add a user without social-entity I have no error, but in the database I have in social table a row with null values. Please help.
UPDATE
In user-entity I added this :
if( !( $socials->getName() === null && $socials->getUrl() === null ) )
{
$this->socials[] = $socials;
$socials->setUser($this);
}
Now there is no row inserted in social table, but when I try editing the user, I have two collection field (duplicated).
See the screenshot
Here my template file (Twig) :
<div class="widget-body">
{{ form_start(form, { 'action': path('acme_admin_edit_user', {'id': userId}), 'attr': {'class': 'form-horizontal'} }) }}
<div class="control-group">
{{ form_errors(form.username) }}
{{ form_label(form.username) }}
<div class="controls">
{{ form_widget(form.username) }}
</div>
</div>
<!-- ... -->
<div id="acme_adminbundle_useredittype_socials" data-prototype="{{ form_row(form.socials.vars.prototype) | escape }}">
{% for social in form.socials %}
<div>
<label class="required text-primary lead">Lien n°{{ loop.index }}</label>
<div id="acme_adminbundle_useredittype_socials_{{ loop.index0 }}">
<div class="control-group">
{{ form_errors(social.name) }}
{{ form_label(social.name) }}
<div class="controls">
{{ form_widget(social.name) }}
</div>
</div>
<div class="control-group">
{{ form_errors(social.url) }}
{{ form_label(social.url) }}
<div class="controls">
{{ form_widget(social.url) }}
</div>
</div>
</div>
</div>
{% endfor %}
</div>
<div class="txt-center well">
<input type="submit" class="auto-margin btn btn-primary btn-large" />
</div>
{{ form_end(form) }}
</div>
Try removing:
#ORM\JoinColumn(nullable=true)
from your User class. #JoinColumn should be defined only on one side of relationship and since Social entity contains name and referencedColumnName it is unnecessary inside the User.
Related
I am checking for friends that has accepted my friend request and thus are my real friends. But when displaying my friends I get myself as friend and not the person I have for friend sometimes. This appears when for example my id is second in the table.
I have tried checking if the userid is in either of the columns. Also tried with having two way friendship like A friend with B but B friend with A too. I couldnt get the repository work for the second method so I sticked to this one.
My repository:
public function personalFriends($userId){
$em = $this->getEntityManager();
$result = $em->createQuery('SELECT friends FROM AppBundle\Entity\Friends friends
INNER JOIN AppBundle\Entity\User myuser WHERE (friends.friendsWithMe = :userId OR friends.afriendof = :userId) AND friends.friends = 1');
$result->setParameter('userId', $userId);
return $result->getResult();
}
My controller:
public function indexAction(Request $request)
{
$user = $this->get('security.token_storage')->getToken()->getUser();
$userId = $user->getId();
$posts = $this->getDoctrine()->getRepository(UserPosts::class)->findUserPosts($userId,1);
$friends = $this->getDoctrine()->getRepository(Friends::class)->personalFriends($userId);
return $this->render('default/index.html.twig',
['formed' => null,
'posts' => $posts,
'posted'=>null,
'search' => null,
'friends' => $friends
]);
}
My view :
<div class="card card-body">
{{ app.user.username|capitalize }}'s friends:
{% if friends %}
<div>
{% for friend in friends %}
{{ friend.afriendof.username }}
{% endfor %}
</div>
{% else %}
<div>
You have no friends.
</div>
{% endif %}
</div>
Friends Entity:
class Friends
{
/**
* #ORM\Id()
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User", inversedBy="myfriends", fetch="EAGER")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false)
*/
private $friendsWithMe;
/**
* #ORM\Id()
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User", inversedBy="friendof", fetch="EAGER")
* #ORM\JoinColumn(name="friend_id", referencedColumnName="id", nullable=false)
*/
private $afriendof;
/**
* #var integer
*
* #ORM\Column(name="request_sent", type="smallint")
*/
private $requestSent;
/**
* #var integer
*
* #ORM\Column(name="friends", type="smallint")
*/
private $friends;
I want to get only the friends and not myself. I get that I have to change the friend.afriendof.username with friend.friendswithme.username, but how to know when to change between those two.
Could someone explain me what i am doing wrong. I have an entity with classes Actualite and ActualiteTranslation.
I created ActualiteAdmin class with sonanta:admin:generate.
Everything is working as expected. I debug fields with dump($actualite) in the controller and i see that each fields are well displayed :
array:1 [▼
0 => Actualite {#742 ▼
-id: 8
-date: DateTime {#806 ▶}
-image: null
#translations: PersistentCollection {#709 ▼
-snapshot: array:1 [ …1]
-owner: Actualite {#742}
-association: array:16 [ …16]
-em: EntityManager {#789 …11}
-backRefFieldName: "translatable"
-typeClass: ClassMetadata {#738 …}
-isDirty: false
#collection: ArrayCollection {#662 ▼
-elements: array:1 [▼
"fr" => ActualiteTranslation {#705 ▼
#actucategorie: "categorie"
#alt: "image"
#titre: "Page Facebook"
#article: "mon article"
#id: 4
#locale: "fr"
#translatable: Actualite {#742}
#slug: "page-facebook"
}
]
}
#initialized: true
}
#newTranslations: null
#currentLocale: "fr"
#defaultLocale: "fr"
}
]
I have thir error in Twig Template when using {{actualite.translations.titre}}:
Key "translations" for array with keys "0" does not exist.
This is the code I use :
namespace BDN\ActualitesBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Model as ORMBehaviors;
/**
* Actualite
*
* #ORM\Table(name="bdn_actualite")
* #ORM\Entity(repositoryClass="BDN\ActualitesBundle\Repository\ActualiteRepository")
*
*/
class Actualite
{
use ORMBehaviors\Translatable\Translatable;
/**
* #param $method
* #param $args
*
* #return mixed
*/
public function __call($method, $args)
{
if (!method_exists(self::getTranslationEntityClass(), $method)) {
$method = 'get' . ucfirst($method);
}
return $this->proxyCurrentLocaleTranslation($method, $args);
}
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var \DateTime
*
* #ORM\Column(name="date", type="datetime")
*/
private $date;
/**
* #var string
*
* #ORM\Column(name="image", type="string", length=255, nullable=true)
*/
private $image;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set date
*
* #param \DateTime $date
*
* #return null
*/
public function setDate($date)
{
$this->date = $date;
return $this;
}
/**
* Get date
*
* #return \DateTime
*/
public function getDate()
{
return $this->date;
}
/**
* Set image
*
* #param string $image
*
* #return null
*/
public function setImage($image)
{
$this->image = $image;
return $this;
}
/**
* Get image
*
* #return string
*/
public function getImage()
{
return $this->image;
}
}
namespace BDN\ActualitesBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Model as ORMBehaviors;
/**
* ActualiteTranslation
*
* #ORM\Entity
* #ORM\Table(name="bdn_actualite_translation")
*
*/
class ActualiteTranslation
{
use ORMBehaviors\Translatable\Translation;
use ORMBehaviors\Sluggable\Sluggable;
/**
* #var string
*
* #ORM\Column(name="actucategorie", type="string", length=50)
*/
protected $actucategorie;
/**
* #var string
*
* #ORM\Column(name="alt", type="string", length=255)
*/
protected $alt;
/**
* #var string
*
* #ORM\Column(name="titre", type="string", length=255, unique=true)
*/
protected $titre;
public function getSluggableFields()
{
return ['titre'];
}
/**
* #var string
*
* #ORM\Column(name="article", type="text")
*/
protected $article;
/**
* Set actucategorie
*
* #param string $actucategorie
*
* #return ActualiteTranslation
*/
public function setActucategorie($actucategorie)
{
$this->actucategorie = $actucategorie;
return $this;
}
/**
* Get actucategorie
*
* #return string
*/
public function getActucategorie()
{
return $this->actucategorie;
}
/**
* Set alt
*
* #param string $alt
*
* #return ActualiteTranslation
*/
public function setAlt($alt)
{
$this->alt = $alt;
return $this;
}
/**
* Get alt
*
* #return string
*/
public function getAlt()
{
return $this->alt;
}
/**
* Set titre
*
* #param string $titre
*
* #return ActualiteTranslation
*/
public function setTitre($titre)
{
$this->titre = $titre;
return $this;
}
/**
* Get titre
*
* #return string
*/
public function getTitre()
{
return $this->titre;
}
/**
* Set article
*
* #param string $article
*
* #return ActualiteTranslation
*/
public function setArticle($article)
{
$this->article = $article;
return $this;
}
/**
* Get article
*
* #return string
*/
public function getArticle()
{
return $this->article;
}
}
<!-- begin snippet: js hide: false console: true babel: false -->
<?php
namespace BDN\ActualitesBundle\Admin;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Show\ShowMapper;
use A2lix\TranslationFormBundle\Form\Type\TranslationsType;
class ActualiteAdmin extends AbstractAdmin
{
/**
* #param DatagridMapper $datagridMapper
*/
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper
->add('id')
->add('date')
->add('image')
;
}
/**
* #param ListMapper $listMapper
*/
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->add('id')
->add('date')
->add('image')
->add('_action', null, array(
'actions' => array(
'show' => array(),
'edit' => array(),
'delete' => array(),
),
))
;
}
/**
* #param FormMapper $formMapper
*/
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('date')
->add('image')
->add("translations", TranslationsType::class, array(
"fields" => array(
"actucategorie" => [],
"alt" => [],
"titre" => [],
"slug" => ["display" => false,],
"article" => [ "field_type" => "text"],
),
))
;
}
/**
* #param ShowMapper $showMapper
*/
protected function configureShowFields(ShowMapper $showMapper)
{
$showMapper
->add('id')
->add('date')
->add('image')
->add("translations", TranslationsType::class, array(
"required_locales" => ['fr'],
"fields" => array(
"actucategorie" => [],
"alt" => [],
"titre" => [],
"article" => [ "field_type" => "text"],
),
))
;
}
}
{% extends "::base.html.twig" %}
{% block page %}
<section class="row jumbotron">
<div class="col-lg-12">
<div class="row">
<div class="col-xs-9">
<h3>{{ actualite.getTitre() }}</h3>
<p class="categorie">Le {{ actualite.date | date('d/m/Y') }} | Catégorie : {{ actualite.translate.actucategorie }}</p>
</div>
{% if actualite.image is not null %}
<div class="col-xs-3 hidden-xs">
<img src="{{ asset('actualite.image') }}" class="img-thumbnail img-shop" alt="{{ actualite.alt }}">
</div>
{% endif %}
</div>
<div class="row content-actualite">
<div class="col-lg-12">
{{ actualite.translate.article }}
</div>
</div>
</div>
</section>
{% endblock %}
Any help would be very appreciated!
Denis
I am using CollectionType Field in my Symfony project, to be able to generate as many similar form items as user needs. The problem I have appears while form is being submitted - it passes all the values directly to db, but the foreign key id (household_app_id) remains null, even though relations seem to be correct.
What can I do to prevent this bug? And why is this happening?
My project code is below:
Both Entities:
<?php //HouseholdApp.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* HouseholdApp
*
* #ORM\Table(name="household_application")
* #ORM\Entity(repositoryClass="AppBundle\Repository\HouseholdAppRepository")
* #Vich\Uploadable
*
*/
class HouseholdApp
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* One Application has One Household.
* #ORM\OneToOne(targetEntity="AppBundle\Entity\Household", inversedBy="householdApp")
* #ORM\JoinColumn(name="household_id", referencedColumnName="id")
*/
private $household;
/**
* #var File
*
* #Vich\UploadableField(mapping="union_register", fileNameProperty="unionRegisterName")
*/
private $unionRegister;
/**
* #var File
*
* #Vich\UploadableField(mapping="apt_owners_decision", fileNameProperty="aptOwnersDecisionName")
*/
private $aptOwnersDecision;
/**
* #var File
*
* #Vich\UploadableField(mapping="mulaptinventory", fileNameProperty="multiAptInventoryName")
*/
private $multiAptInventory;
/**
* #var File
*
* #Vich\UploadableField(mapping="buildtechsurvey", fileNameProperty="buildingTechnicalSurveyName")
*/
private $buildingTechnicalSurvey;
/**
* #var File
*
* #Vich\UploadableField(mapping="defectact", fileNameProperty="defectActName")
*/
private $defectAct;
/**
* #var File
*
* #Vich\UploadableField(mapping="activitycost", fileNameProperty="activityCostName")
*/
private $activityCost;
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\DefectAct", mappedBy="householdApp", cascade={"persist"}, orphanRemoval=true)
*/
protected $actOfDefects;
/**
* Set unionRegister
*
* #param File|UploadedFile $unionRegister
*
* #return HouseholdApp
*/
public function setUnionRegister(File $unionRegister = null)
{
$this->unionRegister = $unionRegister;
if ($unionRegister) {
// It is required that at least one field changes if you are using doctrine
// otherwise the event listeners won't be called and the file is lost
$this->uUpdatedAt = new \DateTime('now');
}
return $this;
}
/**
* Get unionRegister
*
* #return File|UploadedFile
*/
public function getUnionRegister()
{
return $this->unionRegister;
}
/**
* Set aptOwnersDecision
*
* #param File|UploadedFile $aptOwnersDecision
*
* #return HouseholdApp
*/
public function setAptOwnersDecision(File $aptOwnersDecision = null)
{
$this->aptOwnersDecision = $aptOwnersDecision;
if ($aptOwnersDecision) {
// It is required that at least one field changes if you are using doctrine
// otherwise the event listeners won't be called and the file is lost
$this->aUpdatedAt = new \DateTime('now');
}
return $this;
}
/**
* Get aptOwnersDecision
*
* #return File|UploadedFile
*/
public function getAptOwnersDecision()
{
return $this->aptOwnersDecision;
}
/**
* Set multiAptInventory
*
* #param File|UploadedFile $multiAptInventory
*
* #return HouseholdApp
*/
public function setMultiAptInventory(File $multiAptInventory = null)
{
$this->multiAptInventory = $multiAptInventory;
if ($multiAptInventory) {
// It is required that at least one field changes if you are using doctrine
// otherwise the event listeners won't be called and the file is lost
$this->mUpdatedAt = new \DateTime('now');
}
return $this;
}
/**
* Get multiAptInventory
*
* #return File|UploadedFile
*/
public function getMultiAptInventory()
{
return $this->multiAptInventory;
}
/**
* Set buildingTechnicalSurvey
*
* #param File|UploadedFile $buildingTechnicalSurvey
*
* #return HouseholdApp
*/
public function setBuildingTechnicalSurvey(File $buildingTechnicalSurvey = null)
{
$this->buildingTechnicalSurvey = $buildingTechnicalSurvey;
if ($buildingTechnicalSurvey) {
// It is required that at least one field changes if you are using doctrine
// otherwise the event listeners won't be called and the file is lost
$this->bUpdatedAt = new \DateTime('now');
}
return $this;
}
/**
* Get buildingTechnicalSurvey
*
* #return File|UploadedFile
*/
public function getBuildingTechnicalSurvey()
{
return $this->buildingTechnicalSurvey;
}
/**
* Set defectAct
*
* #param File|UploadedFile $defectAct
*
* #return HouseholdApp
*/
public function setDefectAct(File $defectAct = null)
{
$this->defectAct = $defectAct;
if ($defectAct) {
// It is required that at least one field changes if you are using doctrine
// otherwise the event listeners won't be called and the file is lost
$this->dUpdatedAt = new \DateTime('now');
}
return $this;
}
/**
* Get defectAct
*
* #return File|UploadedFile
*/
public function getDefectAct()
{
return $this->defectAct;
}
/**
* Set activityCost
*
* #param File|UploadedFile $activityCost
*
* #return HouseholdApp
*/
public function setActivityCost(File $activityCost = null)
{
$this->activityCost = $activityCost;
if ($activityCost) {
// It is required that at least one field changes if you are using doctrine
// otherwise the event listeners won't be called and the file is lost
$this->acUpdatedAt = new \DateTime('now');
}
return $this;
}
/**
* Get activityCost
*
* #return File|UploadedFile
*/
public function getActivityCost()
{
return $this->activityCost;
}
}
<?php //DefectAct.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* DefectAct
*
* #ORM\Table(name="defect_act")
* #ORM\Entity(repositoryClass="AppBundle\Repository\DefectActRepository")
*
*/
class DefectAct
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $defectWork;
/**
* #var string
*
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $defectDescription;
/**
* #var string
*
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $preventionDeadline;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\HouseholdApp", inversedBy="actOfDefects")
*/
private $householdApp;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set defectWork
*
* #param string $defectWork
*
* #return DefectAct
*/
public function setDefectWork($defectWork)
{
$this->defectWork = $defectWork;
return $this;
}
/**
* Get defectWork
*
* #return string
*/
public function getDefectWork()
{
return $this->defectWork;
}
/**
* Set defectDescription
*
* #param string $defectDescription
*
* #return DefectAct
*/
public function setDefectDescription($defectDescription)
{
$this->defectDescription = $defectDescription;
return $this;
}
/**
* Get defectDescription
*
* #return string
*/
public function getDefectDescription()
{
return $this->defectDescription;
}
/**
* Set preventionDeadline
*
* #param string $preventionDeadline
*
* #return DefectAct
*/
public function setPreventionDeadline($preventionDeadline)
{
$this->preventionDeadline = $preventionDeadline;
return $this;
}
/**
* Get preventionDeadline
*
* #return string
*/
public function getPreventionDeadline()
{
return $this->preventionDeadline;
}
/**
* Set householdApp
*
* #param \AppBundle\Entity\HouseholdApp $householdApp
*
* #return DefectAct
*/
public function setHouseholdApp(\AppBundle\Entity\HouseholdApp $householdApp = null)
{
$this->householdApp = $householdApp;
return $this;
}
/**
* Get householdApp
*
* #return \AppBundle\Entity\HouseholdApp
*/
public function getHouseholdApp()
{
return $this->householdApp;
}
}
Controller:
/**
* #Security("has_role('ROLE_HOUSEHOLD')")
* #param \Symfony\Component\HttpFoundation\Request $request
* #return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
*/
public function householdApplicationAction(Request $request) {
$application = $this->getUser()->getRhousehold()->getHouseholdApp();
$flag = true;
if(is_null($application)) {
$flag = false;
}
$form = $this->createForm(HouseholdAppType::class, $application, ['disabled' => false]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$appData = $form->getData();
$em = $this->get("doctrine.orm.default_entity_manager");
$household = $this->getUser()->getRhousehold();
$appData->setHousehold($household);
$em->persist($appData);
$em->flush();
return $this->redirectToRoute("householder_app");
}
return $this->render("#App/household_application.html.twig",
['form' => $form->createView(), 'householdapp' => $application]);
}
Forms:
<?php //HouseholdAppType
namespace AppBundle\Form;
use AppBundle\Entity\HouseholdApp;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Vich\UploaderBundle\Form\Type\VichFileType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
class HouseholdAppType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('multiapthouseaddress', TextType::class, ['label' => 'form.multiapthouseaddress'])
->add('cadastralnumberofbuilding', TextType::class, ['label' => 'form.cadastralnumberofbuilding'])
->add('totalbuildingarea', TextType::class, ['label' => 'form.totalbuildingarea'])
->add('house_constructive_solution', TextType::class, ['label' => 'form.house_constructive_solution'])
->add('exploitation_start_year', IntegerType::class, ['label' => 'form.exploitation_start_year'])
->add('non_residential_area', TextType::class, ['label' => 'form.non_residential_area'])
->add('has_heat_supply_system', CheckboxType::class, ['label' => 'form.has_heat_supply_system'])
->add('apts_with_individual_heating', TextType::class, ['label' => 'form.apts_with_individual_heating'])
->add('authorized_person_data', TextType::class, ['label' => 'form.authorized_person_data'])
->add('is_vatpayer', CheckboxType::class, ['label' => 'form.is_vatpayer'])
->add('personal_code', TextType::class, ['label' => 'form.personal_code'])
->add('declared_address', TextType::class, ['label' => 'form.declared_address'])
->add('contact_person_data', TextType::class, ['label' => 'form.contact_person_data'])
->add('unionRegister', VichFileType::class, ['required' => false, 'label' => 'form.unionRegister'])
->add('aptOwnersDecision', VichFileType::class, ['required' => false, 'label' => 'form.aptOwnersDecision'])
->add('multiAptInventory', VichFileType::class, ['required' => false, 'label' => 'form.multiAptInventory'])
->add('buildingTechnicalSurvey', VichFileType::class, ['required' => false, 'label' => 'form.buildingTechnicalSurvey'])
->add('defectAct', VichFileType::class, ['required' => false, 'label' => 'form.defectAct'])
->add('activityCost', VichFileType::class, ['required' => false, 'label' => 'form.activityCost'])
->add('actOfDefects', CollectionType::class, [
'label' => true,
'entry_type' => DefectsActCollectionType::class,
'allow_add' => true,
'prototype' => true,
'allow_delete' => true,
'by_reference' => false,
'delete_empty' => true,
'required' => false
])
->add('save', SubmitType::class, [
'label' => 'form.save',
'attr' => [
'class' => 'btn2'
]]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => HouseholdApp::class,
'translation_domain' => 'main',
));
}
}
<?php //DefectActCollectionType
namespace AppBundle\Form;
use AppBundle\Entity\DefectAct;
use AppBundle\Entity\HouseholdApp;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class DefectsActCollectionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('defectWork', TextType::class)
->add('defectDescription', TextType::class)
->add('preventionDeadline', TextType::class)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefaults([
'translation_domain' => 'main',
'data_class' => DefectAct::class
]);
}
public function getBlockPrefix()
{
return 'app_bundle_defects_act_collection_type';
}
}
Template:
{% extends '#App/templates/base.html.twig' %}
{% trans_default_domain "main" %}
{% block main %}
{% include '#App/templates/progressbar.html.twig' %}
{% include '#App/templates/tabs.html.twig' %}
{% include '#App/h_apply_menu.html.twig' %}
<section id='cabinet_household_app'>
<div class='container'>
<div class="tab-content">
<div id="h-apply" class="tab-pane fade in active form_page">
{{ form_start(form)}}
{{ form_row(form._token) }}
<h3>{{ 'form.house_data'|trans }}</h3>
<div class='field'><div class="form-group"> {{ form_row(form.multiapthouseaddress)}}</div></div>
<div class='field'><div class="form-group"> {{ form_row(form.cadastralnumberofbuilding)}}</div></div>
<div class='field'><div class="form-group"> {{ form_row(form.totalbuildingarea)}}</div></div>
<div class='field'><div class="form-group">{{ form_row(form.house_constructive_solution)}}</div></div>
<div class='field'><div class="form-group">{{ form_row(form.exploitation_start_year)}}</div></div>
<div class='field'><div class="form-group">{{ form_row(form.non_residential_area)}}</div></div>
<div class='field'><div class="form-group">{{ form_row(form.has_heat_supply_system, {'required': false})}}</div></div>
<div class='field'><div class="form-group">{{ form_row(form.apts_with_individual_heating)}}</div></div>
<h3>{{ 'form.app_applying_person'|trans }}</h3>
<div class='field'><div class="form-group">{{ form_row(form.authorized_person_data)}}</div></div>
<div class='field'><div class="form-group">{{ form_row(form.is_vatpayer, {'required': false})}}</div></div>
<div class='field'><div class="form-group">{{ form_row(form.personal_code)}}</div></div>
<div class='field'><div class="form-group">{{ form_row(form.declared_address)}}</div></div>
<div class='field'><div class="form-group">{{ form_row(form.contact_person_data)}}</div></div>
<h3>{{ 'form.apply_documents'|trans }}</h3>
<section id='cabinet_household_inner_app'>
{% if householdapp is null %}
<h4>{{ 'form.unionRegister'|trans }}</h4>
<div class='field'><div class="form-group">{{ form_widget(form.unionRegister) }}</div></div>
<h4>{{ 'form.aptOwnersDecision'|trans }}</h4>
<div class='field'><div class="form-group">{{ form_widget(form.aptOwnersDecision) }}</div></div>
<h4>{{ 'form.multiAptInventory'|trans }}</h4>
<div class='field'><div class="form-group">{{ form_widget(form.multiAptInventory) }}</div></div>
<h4>{{ 'form.buildingTechnicalSurvey'|trans }}</h4>
<div class='field'><div class="form-group">{{ form_widget(form.buildingTechnicalSurvey) }}</div></div>
<h4>{{ 'form.defectAct'|trans }}</h4>
<div class='field'><div class="form-group">{{ form_widget(form.defectAct) }}</div></div>
<h4>{{ 'form.activityCost'|trans }}</h4>
<div class='field'><div class="form-group">{{ form_widget(form.activityCost) }}</div></div>
{% else %}
<h4>{{ 'form.unionRegister'|trans }}</h4>
Download
<h4>{{ 'form.aptOwnersDecision'|trans }}</h4>
Download
<h4>{{ 'form.multiAptInventory'|trans }}</h4>
Download
<h4>{{ 'form.buildingTechnicalSurvey'|trans }}</h4>
Download
<h4>{{ 'form.defectAct'|trans }}</h4>
Download
<h4>{{ 'form.activityCost'|trans }}</h4>
Download
{% endif %}
<div class="container center" data-prototype="{{ form_widget(form.actOfDefects.vars.prototype)|e('html_attr') }}">
{% for actOfDefect in form.actOfDefects %}
<div class="row document">{{ form_row(actOfDefect) }}</div>
{% endfor %}
<div class="row">
<div class="col-xs-12 center">
<div id="add" class="btn2">{{ "common.addFile"|trans }}</div>
</div>
</div>
</div>
</section>
<div class="center">{{ form_row(form.save) }}</div>
{{ form_end(form, {'render_rest': false}) }}
</div>
</div>
</div>
</section>
{% endblock main %}
{% block js_bottom %}
<script>
var $collectionHolder;
$(document).ready(function () {
// Get the ul that holds the collection of tags
$collectionHolder = $('div.container.center');
// add a delete link to all of the existing tag form li elements
$collectionHolder.find('li').each(function() {
addTagFormDeleteLink($(this));
});
// add the "add a tag" anchor and li to the tags ul
//$collectionHolder.append($newLinkLi);
// count the current form inputs we have (e.g. 2), use that as the new
// index when inserting a new item (e.g. 2)
$collectionHolder.data('index', $collectionHolder.find(':input').length);
$('#add').on('click', function (e) {
// prevent the link from creating a "#" on the URL
e.preventDefault();
addTagForm($collectionHolder);
});
});
function addTagFormDeleteLink($tagFormLi) {
var $removeFormA = $('{{ "common.cancel"|trans }}');
$tagFormLi.append($removeFormA);
$removeFormA.on('click', function(e) {
// prevent the link from creating a "#" on the URL
e.preventDefault();
// remove the li for the tag form
$tagFormLi.remove();
});
}
function addTagForm($collectionHolder) {
var prototype = $collectionHolder.data('prototype');
// get the new index
var index = $collectionHolder.data('index');
// Replace '__name__' in the prototype's HTML to
// instead be a number based on how many items we have
var newForm = prototype.replace(/__name__/g, index);
// increase the index with one for the next item
$collectionHolder.data('index', index + 1);
// Display the form in the page in an li, before the "Add a tag" link li
var $newFormBlock = $('<div class="row document"></div>').append(newForm);
$collectionHolder.append($newFormBlock);
addTagFormDeleteLink($newFormBlock);
}
</script>
{% endblock %}
In your class HouseholdApp, you must have :
public function addActOfDefects(\AppBundle\Entity\DefectAct $actOfDefect)
{
$this->actOfDefects[] = $actOfDefect;
$actOfDefect->setHouseholdApp($this);
return $this;
}
public function removeActOfDefects(\AppBundle\Entity\DefectAct $actOfDefect)
{
$this->actOfDefects->removeElement($actOfDefect);
}
I have a strange problem using a form in Symfony 3.
And my Assert does not works in my entity file.
In "the developer tools" it show the phrase but in the view all is in blank.
<?php
namespace TestBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\Common\Collections\ArrayCollection;
/**
* User
*
* #ORM\Table(name="users")
* #UniqueEntity("name")
* #UniqueEntity("email")
* #ORM\HasLifecycleCallbacks()
*/
class User implements UserInterface
{
/**
* #Assert\NotBlank()
* #Assert\NotBlank(message="Please enter your name.")
*/
private $name;
/**
* #Assert\NotBlank()
* #ORM\Column(type="string", length=50, nullable=false)
*/
private $sername;
/**
* #var string
* #Assert\NotBlank()
* #Assert\Email()
*/
private $email;
/**
* #var string
*/
private $password;
/**
* #var integer
*/
private $role;
/**
* #var \DateTime
*/
private $created;
/**
* #var integer
*/
private $id;
/**
* Set name
*
* #param string $name
*
* #return User
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set sername
*
* #param string $sername
*
* #return User
*/
public function setSername($sername)
{
$this->sername = $sername;
return $this;
}
/**
* Get sername
*
* #return string
*/
public function getSername()
{
return $this->sername;
}
/**
* Set email
*
* #param string $email
*
* #return User
*/
public function setEmail($email)
{
$this->email = $email;
return $this;
}
/**
* Get email
*
* #return string
*/
public function getEmail()
{
return $this->email;
}
/**
* Set password
*
* #param string $password
*
* #return User
*/
public function setPassword($password)
{
$this->password = $password;
return $this;
}
/**
* Get password
*
* #return string
*/
public function getPassword()
{
return $this->password;
}
/**
* Set role
*
* #param integer $role
*
* #return User
*/
public function setRole($role)
{
$this->role = $role;
return $this;
}
/**
* Get role
*
* #return integer
*/
public function getRole()
{
return $this->role;
}
/**
* Set created
*
* #param \DateTime $created
*
* #return User
*/
public function setCreated($created)
{
$this->created = $created;
return $this;
}
/**
* Get created
*
* #return \DateTime
*/
public function getCreated()
{
return $this->created;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
public function getRoles()
{
return array($this->role);
}
public function getSalt()
{
return null;
}
public function eraseCredentials()
{
}
/**
* Get username
*
* #return string
*/
public function getUsername()
{
return null;
}
}
My form Action ( I put required in false to use the symfony valid option):
public function addAction(Request $request){
$user = new User();
$form = $this->createFormBuilder($user)
->add('Name', TextType::class,array('required'=> false,'empty_data' => null))
->add('SerName', TextType::class,array('required'=> false,'empty_data' => null))
->add('role', TextType::class,array('required'=> false,'empty_data' => null))
->add('email', EmailType::class,array('required'=> false,'empty_data' => null))
->add('password', RepeatedType::class, array(
'type' => PasswordType::class,
'first_name' => 'pass',
'second_name' => 'confirm',
'required' => false,
))
->add('save', SubmitType::class, array('label' => 'Create Post'))
->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
//$user = new Users();
// $form->getData() holds the submitted values
// but, the original `$task` variable has also been updated
$password = $form->get('password')->getData();
$encoder = $this->container->get('security.password_encoder');
$encoded = $encoder->encodePassword($user,$password);
$user->setPassword($encoded);
$user = $form->getData();
// ... perform some action, such as saving the task to the database
// for example, if Task is a Doctrine entity, save it!
$em = $this->getDoctrine()->getManager();
$em->persist($user);
$em->flush();
return $this->redirectToRoute('test_homepage');
}
//return $this->render('UserBundle:Default:add.html.twig');
return $this->render("TestBundle:Default:add.html.twig",array("form"=>$form->createView()));
}
EDIT (I PUT VIEW FILE):
My view:
{%extends "principal.html.twig"%}
{% block body %}
{{parent()}}
<h1> {% trans %}New User{% endtrans %}</h1>
{{ form_start(form,{'attr':{'novalidate':'novalidate','role':'form'}}) }}
{{ form_label(form.Name,'Username', {'label_attr': {'class': 'foo'}}) }}
{{ form_widget(form.Name,{'attr':{'class':'form-control'}}) }}
{{form_errors(form.Name)}}
{{ form_label(form.SerName,'Last name', {'label_attr': {'class': 'foo'}}) }}
{{ form_errors(form.SerName) }}
{{ form_widget(form.SerName,{'attr':{'class':'form-control'}}) }}
{{ form_label(form.role,'Role', {'label_attr': {'class': 'foo'}}) }}
{{ form_errors(form.role) }}
{{ form_widget(form.role,{'attr':{'class':'form-control'}}) }}
{{ form_label(form.email,'Email', {'label_attr': {'class': 'foo'}}) }}
{{ form_widget(form.email, {'attr':{'class':'form-control'}}) }}
{{ form_errors(form.email) }}
{{ form_label(form.password.pass, "Password", {'attr':{'class':'form-control'}}) }}
{{ form_errors(form.password) }}
{{ form_widget(form.password.pass, {'attr':{'class':'form-control'}}) }}
{{ form_label(form.password.confirm, "Confirm" , {'attr':{'class':'form-control'}}) }}
{{ form_widget(form.password.confirm, {'attr':{'class':'form-control'}}) }}
{{ form_widget(form.save, { 'label': 'Create User','attr':{'class':'btn btn-primary'}}) }}
{{ form_end(form) }}
{% endblock %}
You should read the doc about form rendering
In your twig template, you have to display the errors by using
{{ form_errors(form.age) }}
Or the whole row (label , error message, inpout) with
{{ form_row(form.age) }}
Which is almost equal to
<div>
{{ form_label(form.age) }}
{{ form_errors(form.age) }}
{{ form_widget(form.age) }}
</div>
In your case, it means you have to add form_errors this way:
{{ form_label(form.Name,'Username', {'label_attr': {'class': 'foo'}}) }}
{{ form_widget(form.Name,{'attr':{'class':'form-control'}}) }}
{{ form_errors(form.Name) }}
I have an article entity. When I create an article, I want to be able to add zero to n images to it.
To do so, I upload files with a jQuery plugin in a temporary directory, and add an <input type=hidden> to my form, with the file's full path as value.
It works fine when there is no validation error. But when there is, I got an error saying:
An exception has been thrown during the rendering of a template ("Notice: Array to string conversion") in form_div_layout.html.twig at line 13
And I'm pretty sure it's because it tries to do this :
ErrorHandler ->handleError ('8', 'Array to string conversion', '/var/www/project/app/cache/dev/twig/47/a4/ac9a00176739f843e919f5f89883191930038a6e0aaa218869e4966ab7c6.php', '175', array('context' => array('value' => array('njm4uqsa9y6#54edfdaedd0736w3wstk.jpeg'),...
Here is my formType :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title')
->add('content')
->add('maxVote')
->add('expireAt', 'datetime', array(
'widget' => 'single_text',
'input' => 'string',
'format' => 'dd-mm-yyyy',
)
)
->add('category', 'entity', array(
'class' => 'AppBundle:StoryCategory',
'required' => true
)
)
->add('answers', 'collection', array(
'type' => new AnswerType()
)
)
->add('visibility', 'choice', array(
'choices' => array(1 => "label.public", 2 => "label.semiprivate", 3 => "label.private"),
'expanded' => false,
'multiple' => false
)
)
->add('attachment', 'hidden', array(
'mapped' => false,
'required' => false
)
)
->add('save', 'submit')
;
}
Do you have any idea what is happening, or what should I do ?
Edit:
The form is empty. I create a new instance of Story
$story = new Story();
$form = $this->createForm(new StoryType(), $story);
And when I submit the form I do
$form->handleRequest($request);
Edit 2:
Story.php
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Story
*
* #ORM\Table(name="story")
* #ORM\Entity(repositoryClass="AppBundle\Repository\StoryRepository")
*/
class Story
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="title", type="string", length=255)
* #Assert\NotBlank()
*/
private $title;
/**
* #var string
*
* #ORM\Column(name="content", type="text")
* #Assert\NotBlank()
*/
private $content;
/**
* #var string
*
* #ORM\Column(name="token", type="string", length=70, unique=true)
*/
private $token;
/**
* #var \DateTime
*
* #ORM\Column(name="created_at", type="datetime")
*/
private $createdAt;
/**
* #var \DateTime
*
* #ORM\Column(name="updated_at", type="datetime")
*/
private $updatedAt;
/**
* #var \DateTime
*
* #ORM\Column(name="expire_at", type="datetime")
* #Assert\NotBlank()
*/
private $expireAt;
/**
* #var string
*
* #ORM\Column(name="author_ip", type="string", length=50, nullable=true)
*/
private $authorIp;
/**
* #var integer
*
* #ORM\Column(name="max_vote", type="integer", nullable=true)
* #Assert\Type(type="integer")
* #Assert\Range(
* min = 0,
* max = 20000,
* minMessage = "validation.story.maxvote.min",
* maxMessage = "validation.story.maxvote.max"
* )
*/
private $maxVote;
/**
* #var integer
*
* #ORM\Column(name="nb_vote", type="integer")
*/
private $nbVote;
/**
* #var integer
* 1 = public, 2 = semi-private, 3 = private (only invited members)
*
* #ORM\Column(name="visibility", type="integer")
*
*/
private $visibility;
/**
* #var boolean
*
* #ORM\Column(name="is_active", type="boolean")
*/
private $isActive;
/**
* #var StoryCategory
*
* #ORM\ManyToOne(targetEntity="StoryCategory", inversedBy="stories")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
* })
* #Assert\NotNull()
*/
private $category;
/**
* #ORM\OneToMany(targetEntity="Answer", mappedBy="story", cascade={"persist", "remove"})
* #Assert\Count(
* min = "2",
* max = "4",
* minMessage = "validation.story.answer.min",
* maxMessage = "validation.story.answer.max",
* groups={"premium"}
* )
* #Assert\Count(
* min = "2",
* max = "2",
* minMessage = "validation.story.answer.min",
* maxMessage = "validation.story.answer.max",
* )
*/
private $answers;
/**
* #ORM\OneToMany(targetEntity="Attachment", mappedBy="story", cascade={"persist", "remove"})
*/
private $attachments;
/**
* #var User
*
* #ORM\ManyToOne(targetEntity="User", inversedBy="stories")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
* })
*/
private $author;
public function __construct()
{
$datetime = new \DateTime();
$this->createdAt = $datetime;
$this->updatedAt = $datetime;
$this->token = base_convert(time(), 10, 36).\AppBundle\Library\StringHelper::randomString();
$this->isActive = false;
$this->nbVote = 0;
}
}
Twig view :
{% extends '::base.html.twig' %}
{% block basecontent %}
{{ form_errors(form) }}
{{ form_start(form, {attr: {id: 'story-form', novalidate: 'novalidate'}}) }}
{{ form_row(form.title) }}
{{ form_row(form.content) }}
{{ form_row(form.category) }}
<h3>Les choix de réponses possibles</h3>
{% for answer in form.answers %}
{{ form_widget(answer) }}
{% endfor %}
{{ form_row(form.visibility) }}
{{ form_row(form.expireAt) }}
{% for attachement in form.attachment %}
{{ form_row(attachment) }}
{% endfor %}
{{ form_end(form) }}
{# upload zone #}
<div class="upload-block">
<form action='{{ path('story_file_upload') }}' id="dropzone" class="dropzone">
</form>
</div>
{% endblock basecontent %}
This is what I add with javascript :
$("#story-form").append('<input type="hidden" name="story_form[attachment][]" value="'+response.filename+'"/>');
So I tried without it being an array (I removed the ending [] in the name), and it works. So I assume the problem comes from that, I have to tell the formType that it's an array. But how ?
Thank you
I couldn't make it work, so I removed the "attachments" field from the formType.
When I upload a file, I then add using javascript an <input type="hidden"> with a name that does not match the fields from the formType.
For example, the fields generated by Symfony are named story_form['name']. I just named mine attachments[]. That way Symfony doesn't tell me that there is an extra field.
In my controller I get the values with $request->request->get('attachments').
There probably is a better way to do it, but I haven't found it.
You're using in your vew:
{% for attachement in form.attachment %}
{{ form_row(attachment) }}
{% endfor %}
Where your declaring attachment as an hidden field:
->add('attachment', 'hidden', array(
'mapped' => false,
'required' => false
)
)
A hidden field is no more than a string stored in an <input type="hidden">, you can't iterate through it.