Related
I have 2 entities, Exercise and Product, they have a One to One relationship (the owning side is Product), and I have an ExerciseFormType and a ProductFormType.
What I want I to be able to create an exercise, and the product associated with it at the same time.
My idea was to add the the builder of exerciseFormType, 'product' and its type would be a ProductFormType. The problem is that the product needs the exercise_id, and the exercise isn't persisted yet so it doesn't have one. Here's what I have so far (it works only for creating a product when the exercise exists).
class ExerciseType extends AbstractType {
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('name')
->add('content')
->add('language')
->add('level')
->add('skillsRequired')
->add('skillsTargetted')
->add('tags')
->add('product', ProductType::class, array(
'required' => false,
'exercise'=> $builder->getData()
)
);
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver){
$resolver->setDefaults(array(
'data_class' => 'SchoolBundle\Entity\Exercise'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix(){
return 'schoolbundle_exercise';
}
class ProductType extends AbstractType {
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options){
$builder->add('visibility')->add('price', MoneyType::class);
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$product = $event->getData();
$form = $event->getForm();
$field = (!$product || null === $product->getId()) ? 'publicationDate' : 'updateDate';
$form->add($field, DateTimeType::class, array('data' => new \DateTime()));
});
$data = array(
'data' => $options['exercise'],
'class' => Exercise::class,
'choice_label' => 'getName'
);
$builder->add('exercise', EntityType::class, $data);
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver){
$resolver->setDefaults(array(
'data_class' => 'MarketBundle\Entity\Product'
));
$resolver->setRequired(array(
'exercise'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix(){
return 'marketbundle_product';
}
}
In exercise I have that
<?php
/**
* exercise
*
* #ORM\Table(name="exercise")
* #ORM\Entity(repositoryClass="SchoolBundle\Repository\ExerciseRepository")
*/
class Exercise {
/**
* #var int
* #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;
/**
* #var string
* #ORM\Column(name="content", type="string", length=255)
*/
private $content;
/**
* #var string
* #ORM\Column(name="language", type="string", length=255)
*/
private $language;
/**
* #var int
* #ORM\Column(name="level", type="integer")
*/
private $level;
/**
* #var string
* #ORM\Column(name="skills_required", type="string", length=255)
*/
private $skillsRequired;
/**
* #var string
* #ORM\Column(name="skills_targetted", type="string", length=255)
*/
private $skillsTargetted;
/**
* #var string
* #ORM\Column(name="tags", type="string", length=255)
*/
private $tags;
/**
* #ORM\OneToMany(targetEntity="Session", mappedBy="exercise")
*/
private $sessions;
/**
* #ORM\OneToMany(targetEntity="ExerciseIO", mappedBy="exercise")
*/
private $exerciseIOs;
/**
* #ORM\OneToOne(targetEntity="MarketBundle\Entity\Product", mappedBy="exercise", cascade={"persist"}))
*/
private $product;
/**
* #ORM\ManyToOne(targetEntity="Professor", inversedBy="createdExercises")
* #ORM\JoinColumn(name="creator_id", referencedColumnName="id", nullable=false)
*/
private $creator;
/**
* #ORM\ManyToMany(targetEntity="Professor", mappedBy="boughtExercises")
*/
private $owners;
public function __construct(){
$this->sessions = new ArrayCollection();
$this->exerciseIOs = new ArrayCollection();
$this->owners = new ArrayCollection();
}
/**
* Get creator
* #return Professor
*/
public function getCreator(){
return $this->creator;
}
/**
* Set creator
* #param Professor $creator
* #return Exercise
*/
public function setCreator($creator){
$this->creator = $creator;
return $this;
}
/**
* Get owner
* #return ArrayCollection
*/
public function getOwners(){
return $this->owners;
}
/**
* Get product
* #return Product
*/
public function getProduct(){
return $this->product;
}
/**
* #param Product $product
* #return Exercise
*/
public function setProduct($product){
$this->product = $product;
return $this;
}
/**
* Get exerciseIOs
* #return ArrayCollection
*/
public function getExerciseIOs(){
return $this->exerciseIOs;
}
/**
* Get sessions
* #return ArrayCollection
*/
public function getSessions(){
return $this->sessions;
}
/**
* Get id
* #return int
*/
public function getId(){
return $this->id;
}
/**
* Set name
* #param string $name
* #return Exercise
*/
public function setName($name){
$this->name = $name;
return $this;
}
/**
* Get name
* #return string
*/
public function getName(){
return $this->name;
}
/**
* Set content
* #param string $content
* #return Exercise
*/
public function setContent($content){
$this->content = $content;
return $this;
}
/**
* Get content
* #return string
*/
public function getContent(){
return $this->content;
}
/**
* Set language
* #param string $language
* #return Exercise
*/
public function setLanguage($language){
$this->language = $language;
return $this;
}
/**
* Get language
* #return string
*/
public function getLanguage(){
return $this->language;
}
/**
* Set level
* #param string $level
* #return Exercise
*/
public function setLevel($level){
$this->level = $level;
return $this;
}
/**
* Get level
* #return string
*/
public function getLevel(){
return $this->level;
}
/**
* Set skillsRequired
* #param string $skillsRequired
* #return Exercise
*/
public function setSkillsRequired($skillsRequired){
$this->skillsRequired = $skillsRequired;
return $this;
}
/**
* Get skillsRequired
* #return string
*/
public function getSkillsRequired(){
return $this->skillsRequired;
}
/**
* Set skillsTargetted
* #param string $skillsTargetted
* #return Exercise
*/
public function setSkillsTargetted($skillsTargetted){
$this->skillsTargetted = $skillsTargetted;
return $this;
}
/**
* Get skillsTargetted
* #return string
*/
public function getSkillsTargetted(){
return $this->skillsTargetted;
}
/**
* Set tags
* #param string $tags
* #return Exercise
*/
public function setTags($tags){
$this->tags = $tags;
return $this;
}
/**
* Get tags
* #return string
*/
public function getTags(){
return $this->tags;
}
}
And in Product
/**
* Product
*
* #ORM\Table(name="product")
* #ORM\Entity(repositoryClass="\MarketBundle\Repository\ProductRepository")
*/
class Product {
/**
* #var int
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var bool
* #ORM\Column(name="visibility", type="boolean")
*/
private $visibility;
/**
* #var float
* #ORM\Column(name="price", type="float")
*/
private $price;
/**
* #var \DateTime
* #ORM\Column(name="publication_date", type="datetimetz")
*/
private $publicationDate;
/**
* #var \DateTime
* #ORM\Column(name="update_date", type="datetimetz", nullable=true)
*/
private $updateDate;
/**
* #ORM\OneToMany(targetEntity="ProductComment", mappedBy="product")
*/
private $productComments;
/**
* #ORM\OneToOne(targetEntity="\SchoolBundle\Entity\Exercise", inversedBy="product")
* #ORM\JoinColumn(name="exercise_id", referencedColumnName="id", nullable=false)
*/
private $exercise;
public function __construct(){
$this->productComments = new ArrayCollection();
}
/**
* Get exercise
* #return Exercise
*/
public function getExercise(){
return $this->exercise;
}
/**
* Set exercise
* #param Exercise $exercise
* #return Product
*/
public function setExercise($exercise){
$this->exercise = $exercise;
return $this;
}
/**
* Get productComments
* #return ArrayCollection
*/
public function getProductComments(){
return $this->productComments;
}
/**
* Get id
* #return int
*/
public function getId(){
return $this->id;
}
/**
* Set visibility
* #param boolean $visibility
* #return Product
*/
public function setVisibility($visibility){
$this->visibility = $visibility;
return $this;
}
/**
* Get visibility
* #return bool
*/
public function getVisibility(){
return $this->visibility;
}
/**
* Set price
* #param float $price
* #return Product
*/
public function setPrice($price){
$this->price = $price;
return $this;
}
/**
* Get price
* #return float
*/
public function getPrice(){
return $this->price;
}
/**
* Set publicationDate
* #param \DateTime $publicationDate
* #return Product
*/
public function setPublicationDate($publicationDate){
$this->publicationDate = $publicationDate;
return $this;
}
/**
* Get publicationDate
* #return \DateTime
*/
public function getPublicationDate(){
return $this->publicationDate;
}
/**
* Set updateDate
* #param \DateTime $updateDate
* #return Product
*/
public function setUpdateDate($updateDate){
$this->updateDate = $updateDate;
return $this;
}
/**
* Get updateDate
* #return \DateTime
*/
public function getUpdateDate(){
return $this->updateDate;
}
}
I've said it works for an existing exercise (and no product).
When I try to create both at the same time I get
Entities passed to the choice field must be managed. Maybe persist them in the entity manager?
It's probably from the $builder->getData(), because in that case I only passed to the form a "new Exercice()". Here's the controller action
public function newAction(Request $request){
$exercise = new Exercise();
$form = $this->createForm(ExerciseType::class, $exercise);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$exercise->setCreator($this->getUser());
$em = $this->getDoctrine()->getManager();
$em->persist($exercise);
$em->flush();
return $this->redirectToRoute('exercise_show', array('id' => $exercise->getId()));
}
(Also this is another question but when I disable a formType, it is rendered, and the value appears but it's not submitted. I would actually prefer a hiddenFormType but I need the formatting of the DateTimeFormType)
Ok, this is the solution I've made once with Symfony2.8, I hope it can helps you.
/**
* #ORM\Entity
* #ORM\Table(name="picture")
*/
class Picture
{
/**
* #ORM\OneToOne(targetEntity="AppBundle\Entity\Host",
* inversedBy="pictureId"
* )
* #ORM\JoinColumn(name="avatar_host_id", referencedColumnName="id", nullable=true)
*/
private $avatarHostId;
}
/**
* #ORM\Entity
* #ORM\Table(name="host")
*/
class Host
{
/**
* #ORM\OneToOne(targetEntity="AppBundle\Entity\Picture",
* mappedBy="avatarHostId", cascade={"persist", "remove", "merge"}
* )
*/
private $pictureId;
}
class HostType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('pictureId', PictureFileType::class);
}
}
class PictureFileType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('assetFile', FileType::class, array(
'label' => 'Picture File',
'required' => false,
'validation_groups' => array('creation')
));
}
}
First, I recommend moving away from using integers as IDs. There are some issues around them, including that you can't really generate one at will in this kind of scenario. I would recommend Uuids along with ramsey/uuid, which also has a Doctrine mapping.
When you've got a situation where you need an ID before it's been persisted and the Uuid generated, generate the Uuid yourself. For instance, in a contrived example
class Order
{
/**
* #var UuidInterface
*
* #ORM\Id
* #ORM\Column(type="uuid")
* #ORM\GeneratedValue(strategy="NONE")
* #ORM\CustomIdGenerator(class=UuidGenerator::class)
*/
private $id;
...
/**
* Constructor.
*
* #param UuidInterface $id
*/
public function __construct(?UuidInterface $id = null)
{
$this->id = $id ?? Uuid::uuid4();
}
...
}
class OrderController
{
...
public function orderProductAction(): JsonResponse
{
...
// We need this ahead of time, because
// maybe this is Async and we need to
// return the order id. This happens a
// lot with commands.
$generatedId = Uuid::uuid4();
// Here it's going to use the given ID,
// if not given, the Order class will
// generate it.
$order = new Order($generatedId, ...);
// Stuff happens, persisted... maybe we
// don't have the persisted Order object...
return $this->json([
'order' => [
'id' => $generatedId->toString(),
],
]);
}
...
}
The meat of it is providing the generated Uuid in the constructor (which alternately generates it if null). Do not add a setter. Here's a more involved example:
public function createAction(Request $request): JsonResponse
{
$request_data = \json_decode($request->getContent(), true);
$tenant_data = $request_data['data'];
// Pre-generate our Uuid for the Tenant.
$generated_id = Uuid::uuid4();
/** #var CreateTenant $create_tenant_cmd */
$create_tenant_cmd = new CreateTenant($generated_id);
$create_tenant_cmd->setName($tenant_data['name']);
$create_tenant_cmd->setDomainName($tenant_data['domain_name']);
$create_tenant_cmd->setOwnerUsername($tenant_data['owner_username']);
$create_tenant_cmd->setOwnerPassword($tenant_data['owner_password']);
$create_tenant_cmd->setOwnerFirstName($tenant_data['owner_first_name']);
$create_tenant_cmd->setOwnerLastName($tenant_data['owner_last_name']);
$create_tenant_cmd->setOwnerEmail($tenant_data['owner_email']);
if (!$this->isGranted('perform', $create_tenant_cmd)) {
throw new AccessDeniedException('Access denied to create tenant.');
}
// Here, we should expect NO side-effects, the command
// can't return anything. So no Tenant object with
// generated Uuid...
$this->get('command_bus')->handle($create_tenant_cmd);
/* #var Serializer $serializer */
$serializer = $this->get('jms_serializer');
$serializer_context = SerializationContext::create()->setGroups([
'tenant_meta',
]);
$tenant = $this->get('app.repository.tenant')->find($generated_id);
return $this->json(\json_decode($serializer->serialize(
$tenant,
'json',
$serializer_context
)));
}
That's the ID question; the second part of your question is now easily resolved, that being giving the Uuid to the FormType instead:
$form = $this->createForm(OrderProductType::class, $product, [
'product' => $product,
'generatedId' => $generatedId,
]);
class OrderProductType extends AbstractType implements DataMapperInterface
{
/** #var UuidInterface */
protected $generatedId;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->generatedId = $options['generatedId'];
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefaults([
'data_class' => OrderProduct::class,
])
->setRequired(['generatedId'])
;
}
public function mapFormsToData($forms, &$data)
{
$forms = iterator_to_array($forms);
$data = new OrderProduct($this->generatedId);
...
}
}
Of course, if you need it as a field, use it with the field type of your choice instead of storing it as a variable, and then get it from that field:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('product_id', HiddenType::class, [
'product_id' => $options['generatedId']->toString(),
])
...
;
}
public function mapFormsToData($forms, &$data)
{
$forms = iterator_to_array($forms);
// Note use of Uuid::fromString().
$data = new OrderProduct(Uuid::fromString(
$forms['productId']->getData()
));
...
}
This is one of the downsides of not delegating the process of interaction to a delegate object like a command, instead of generating an entity; you're stuck constructing your object before you need it, in whole, or perverting the design to make it fit. It also makes working with assertions harder as well (you only get one set, the entity's).
I am designing an application for managing reports. I'm developing this with Symfony 3.2.6 In this picture you can see my data model. I want to do two things:
1. Create new layouts for a report with a number of given modules
2. Create instances of this reports and save them in the database
So I think this is a way to do this with this data model, isn't it? But how can I now create a form in Symfony from that?
I do something like that:
$builder
->add('name', TextType::class)
;
foreach ($options['moduleValues'] as $moduleValue)
{
if($moduleValue instanceof RangeModuleValue)
{
$builder->add('value', RangeType::class, array(
'attr' => array(
'min' => $moduleValue->getRangeModule()->getStartValue(),
'max' => $moduleValue->getRangeModule()->getEndValue()
)
));
}
}
But then I get the error:
Neither the property "value" nor one of the methods "getValue()", "value()", "isValue()", "hasValue()", "__get()" exist and have public access in class "ReportBundle\Entity\Report".
I think the error is clear, the "value" is in the table range_module_value. But how should I change my design or my Form to handle this?
Note: the parent class Module exists, because there will be other modules like "TextModule" in future.
Here is my class Report:
class Report
{
/**
* #var int
*
* #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;
/**
* #ORM\ManyToOne(targetEntity="ReportBundle\Entity\ReportLayout")
* #ORM\JoinColumn(name="layout_id", referencedColumnName="id")
*/
private $layout;
/**
* Report constructor.
* #param int $id
*/
public function __construct($layout)
{
$this->layout = $layout;
}
/**
* #return int
*/
public function getLayout()
{
return $this->layout;
}
/**
* #param int $layout
*/
public function setLayout($layout)
{
$this->layout = $layout;
}
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Report
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
}
And here is the class RangeModuleValue, in which I want to persist the value of a module for a specific report.
class RangeModuleValue
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="ReportBundle\Entity\RangeModule")
* #ORM\JoinColumn(name="rangeModule_id", referencedColumnName="id")
*/
private $rangeModule;
/**
* #ORM\ManyToOne(targetEntity="ReportBundle\Entity\Report")
* #ORM\JoinColumn(name="report_id", referencedColumnName="id")
*/
private $report;
/**
* #var int
*
* #ORM\Column(name="value", type="integer")
*/
private $value;
/**
* RangeModuleValue constructor.
* #param $rangeModule
* #param $report
*/
public function __construct($report, $rangeModule)
{
$this->report = $report;
$this->rangeModule = $rangeModule;
}
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set rangeModule
*
* #param string $rangeModule
*
* #return RangeModuleValue
*/
public function setRangeModule($rangeModule)
{
$this->rangeModule = $rangeModule;
return $this;
}
/**
* Get rangeModule
*
* #return string
*/
public function getRangeModule()
{
return $this->rangeModule;
}
/**
* Set report
*
* #param string $report
*
* #return RangeModuleValue
*/
public function setReport($report)
{
$this->report = $report;
return $this;
}
/**
* Get report
*
* #return string
*/
public function getReport()
{
return $this->report;
}
/**
* Set value
*
* #param integer $value
*
* #return RangeModuleValue
*/
public function setValue($value)
{
$this->value = $value;
return $this;
}
/**
* Get value
*
* #return int
*/
public function getValue()
{
return $this->value;
}
}
In class report you add this function to allow add many $rangeModule or $value :
public function addrangemodule (RangeModuleValue $rangeModule)
{
$day->setIdReport($this);
$this->ranges->add($day);
}
But ranged should be an ArrayCollection :
public function setranges(ArrayCollection $ranges)
{
$this->ranges= $ranges;
}
In Controller adds as much as you need :
$range = new RangeModuleValue();
$report->addrangemodule ($range);
This code is just an example i am not sure that he works.
For more information this is the documentation :
https://symfony.com/doc/current/form/form_collections.html
http://www.doctrine-project.org/api/common/2.3/class-Doctrine.Common.Collections.ArrayCollection.html
Symfony makes it all so easy. It sets out the big lines for your project and nothing ever goes wrong. Until something does go wrong. You'll be looking for the right solution for days. At least that's what I'm doing right now.
I'm working on a little test project in which you can add urls as bookmarks and give each url a variety of tags to categorize the urls.
I have used the generate:doctrine:crud command to build my forms. But I am getting a weird error.
An exception has been thrown during the rendering of a template ("Catchable Fatal Error: Object of class Bla\LinkBundle\Entity\Url could not be converted to string") in form_div_layout.html.twig at line 13.
I can solve this issue by adding a __toString() method in my Url entity but I wanna know why.
The problem is with the name property being null. If I return this value as '' using __toString() it works fine. But I do not like this solution. All other values are null as well when I am in the "create url" form, so why is it complaining about the name property?
Form
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('url')
->add('flags')
->add('tags', EntityType::class, array(
'class' => 'Bla\LinkBundle\Entity\Tag',
'choice_label' => 'name',
'multiple' => true,
))
;
}
Controller
public function newAction(Request $request)
{
$url = new Url();
$form = $this->createForm('Bla\LinkBundle\Form\UrlType', $url);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($url);
$em->flush();
return $this->redirectToRoute('url_show', array('id' => $url->getId()));
}
return $this->render('url/new.html.twig', array(
'url' => $url,
'form' => $form->createView(),
));
}
Entity
namespace Bla\LinkBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Bla\LinkBundle\Entity\Tag;
/**
* URL
*
* #ORM\Table(name="urls")
* #ORM\Entity
*/
class Url
{
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255, nullable=false)
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="url", type="text", length=65535, nullable=false)
*/
private $url;
/**
* #var integer
*
* #ORM\Column(name="flags", type="integer", nullable=false)
*/
private $flags;
/**
* #var integer
*
* #ORM\Column(name="id", type="bigint")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity="\Bla\LinkBundle\Entity\Tag", cascade={"persist"})
* #ORM\JoinTable(name="url_tag",
* joinColumns={#ORM\JoinColumn(name="url_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="tag_id", referencedColumnName="id", unique=true)}
* )
*/
protected $tags;
public function __construct()
{
$this->tags = new ArrayCollection();
}
/**
* Set name
*
* #param string $name
*
* #return Urls
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set url
*
* #param string $url
*
* #return Urls
*/
public function setUrl($url)
{
$this->url = $url;
return $this;
}
/**
* Get url
*
* #return string
*/
public function getUrl()
{
return $this->url;
}
/**
* Set flags
*
* #param integer $flags
*
* #return Urls
*/
public function setFlags($flags)
{
$this->flags = $flags;
return $this;
}
/**
* Get flags
*
* #return integer
*/
public function getFlags()
{
return $this->flags;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* #param \Doctrine\Common\Collections\ArrayCollection $tags
* #return Urls
*/
public function setTags($tags)
{
$this->tags = $tags;
return $this;
}
/**
* #return \Doctrine\Common\Collections\ArrayCollection
*/
public function getTags()
{
return $this->tags;
}
/**
* Add tag
*
* #param Tag $tag
* #return Urls
*/
public function addTag(Tag $tag)
{
$this->tags->add($tag);
return $this;
}
/**
* Remove tag
*
* #param Tags $tag
*/
public function removeTag(Tag $tag)
{
$this->tags->removeElement($tag);
}
}
try to implements this into form class :
public function getName()
{
return 'filter_type_url';
}
public function getParent()
{
return 'multiselectentity';
}
I have the following model, or as you call them entity, and I also have a controller, everything works in this action, but when I check the database there is no user. So I am curious as what I am missing. So lets start at the beginning as to what I have:
bootstrap.php contains the following code, among other things.
...
/** ---------------------------------------------------------------- **/
// Lets Setup Doctrine.
/** ---------------------------------------------------------------- **/
require_once 'vendor/autoload.php';
$loader = require 'vendor/autoload.php';
\Doctrine\Common\Annotations\AnnotationRegistry::registerLoader(array($loader, 'loadClass'));
use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\EntityManager;
/**
* Set up Doctrine.
*/
class DoctrineSetup {
/**
* #var array $paths - where the entities live.
*/
protected $paths = array(APP_MODELS);
/**
* #var bool $isDevMode - Are we considered "in development."
*/
protected $isDevMode = false;
/**
* #var array $dbParams - The database paramters.
*/
protected $dbParams = null;
/**
* Constructor to set some core values.
*/
public function __construct(){
if (!file_exists('db_config.ini')) {
throw new \Exception(
'Missing db_config.ini. You can create this from the db_config_sample.ini'
);
}
$this->dbParams = array(
'driver' => 'pdo_mysql',
'user' => parse_ini_file('db_config.ini')['DB_USER'],
'password' => parse_ini_file('db_config.ini')['DB_PASSWORD'],
'dbname' => parse_ini_file('db_config.ini')['DB_NAME']
);
}
/**
* Get the entity manager for use through out the app.
*
* #return EntityManager
*/
public function getEntityManager() {
$config = Setup::createAnnotationMetadataConfiguration($this->paths, $this->isDevMode, null, null, false);
return EntityManager::create($this->dbParams, $config);
}
}
/**
* Function that can be called through out the app.
*
* #return EntityManager
*/
function getEntityManager() {
$ds = new DoctrineSetup();
return $ds->getEntityManager();
}
/**
* Function that returns the conection to the database.
*/
function getConnection() {
$ds = new DoctrineSetup();
return $ds->getEntityManager()->getConnection();
}
...
So now that we have doctrine set up its time to create a model (entity) and set which fields can and cannot be blank and so on and so forth.
Note At this point, you should know that I am not using Symfony other then its components on top of Doctrine. I am using Slim Framework. So if any suggestion is to use x or y from symfony, please make sure its a component.
Models/User.php
<?php
namespace ImageUploader\Models;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\Table(name="users", uniqueConstraints={
* #ORM\UniqueConstraint(name="user", columns={"userName", "email"})}
* )
*/
class User {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
protected $id;
/**
* #ORM\Column(type="string", length=32, nullable=false)
* #Assert\NotBlank()
*/
protected $firstName;
/**
* #ORM\Column(type="string", length=32, nullable=false)
* #Assert\NotBlank()
*/
protected $lastName;
/**
* #ORM\Column(type="string", length=100, unique=true, nullable=false)
* #Assert\NotBlank(
* message = "Username cannot be blank"
* )
*/
protected $userName;
/**
* #ORM\Column(type="string", length=100, unique=true, nullable=false)
* #Assert\NotBlank(
* message = "Email field cannot be blank."
* )
* #Assert\Email(
* message = "The email you entered is invalid.",
* checkMX = true
* )
*/
protected $email;
/**
* #ORM\Column(type="string", length=500, nullable=false)
* #Assert\NotBlank(
* message = "The password field cannot be empty."
* )
*/
protected $password;
/**
* #ORM\Column(type="datetime", nullable=true)
*/
protected $created_at;
/**
* #ORM\Column(type="datetime", nullable=true)
*/
protected $updated_at;
/**
* Get the value of Created At
*
* #return mixed
*/
public function getCreatedAt()
{
return $this->created_at;
}
/**
* Set the value of Created At
*
* #param mixed created_at
*
* #return self
*/
public function setCreatedAt(\DateTime $created_at = null)
{
$this->created_at = $created_at;
return $this;
}
/**
* Get the value of Updated At
*
* #return mixed
*/
public function getUpdatedAt()
{
return $this->updated_at;
}
/**
* Set the value of Updated At
*
* #param mixed updated_at
*
* #return self
*/
public function setUpdatedAt(\DateTime $updated_at = null)
{
$this->updated_at = $updated_at;
return $this;
}
/**
* Get the value of First Name
*
* #return mixed
*/
public function getFirstName()
{
return $this->firstName;
}
/**
* Set the value of First Name
*
* #param mixed firstName
*
* #return self
*/
public function setFirstName($firstName)
{
$this->firstName = $firstName;
return $this;
}
/**
* Get the value of Last Name
*
* #return mixed
*/
public function getLastName()
{
return $this->lastName;
}
/**
* Set the value of Last Name
*
* #param mixed lastName
*
* #return self
*/
public function setLastName($lastName)
{
$this->lastName = $lastName;
return $this;
}
/**
* Get the value of User Name
*
* #return mixed
*/
public function getUserName()
{
return $this->userName;
}
/**
* Set the value of User Name
*
* #param mixed userName
*
* #return self
*/
public function setUserName($userName)
{
$this->userName = $userName;
return $this;
}
/**
* Get the value of Email
*
* #return mixed
*/
public function getEmail()
{
return $this->email;
}
/**
* Set the value of Email
*
* #param mixed email
*
* #return self
*/
public function setEmail($email)
{
$this->email = $email;
return $this;
}
/**
* Set ths password.
*
* #param string password
*
* #return self
*/
public function setPassword($password) {
$this->password = password_hash($password, PASSWORD_DEFAULT);
return $this;
}
/**
* Check the users password against that which is enterd.
*
* #param string password
*
* #return bool
*/
public function checkPassword($password) {
if (password_hash($password, PASSWORD_DEFAULT) === $this->getPassword()) {
return true;
}
return false;
}
/**
* Return the password value.
*
* #return hash
*/
private function getPassword(){
return $this->password;
}
/**
* #ORM\PrePersist
*/
public function setCreatedAtTimeStamp() {
if (is_null($this->getCreatedAt())) {
$this->setCreatedAt(new \DateTime());
}
}
/**
* #ORM\PreUpdate
*/
public function setUpdatedAtTimeStamp() {
if (is_null($this->getUpdatedAt())) {
$this->setUpdatedAt(new \DateTime());
}
}
}
The above model is correct, as far as I know, I mean when I run "vendor/bin/doctrine migrations:migrate" a database table is created.
Now, where is all this used? it's used in a controller called SignupController under an action called createAction($params)
**createAction($params)**
public static function createAction($params){
$postParams = $params->request()->post();
$flash = new Flash();
if ($postParams['password'] !== $postParams['repassword']) {
$flash->createFlash('error', 'Your passwords do not match.');
self::$createEncryptedPostParams($postParams);
$params->redirect('/signup/error');
}
$user = new User();
$user->setFirstName($postParams['firstname'])
->setLastName($postParams['lastname'])
->setUserName($postParams['username'])
->setEmail($postParams['email'])
->setPassword($postParams['password'])
->setCreatedAtTimeStamp();
$validator = Validator::createValidatorBuilder();
$validator->enableAnnotationMapping();
$errors = $validator->getValidator()->validate($user);
if (count($errors) > 0) {
foreach($errors as $error) {
$flash->createFlash(
$error->getPropertyPath() . 'error',
$error->getMessage()
);
}
self::createEncryptedPostParams($postParams);
$params->redirect('/signup/error');
}
$anyEncryptedErors = self::getEncryptedPostParams();
if ($anyEncryptedErors !== null) {
$anyEncryptedErors->destroy('error');
}
getEntityManager()->flush();
getEntityManager()->persist($user);
$flash->createFlash('success', ' You have signed up successfully! Please sign in!');
$params->redirect('/signin');
}
Now should you enter everything in correctly I show a flash of success and redirect you. THIS WORKS it redirects, it shows a flash message. But its the:
getEntityManager()->flush();
getEntityManager()->persist($user);
That I don't think is working. Why? Because doing a select * from users on the database in question comes back with no records.
Why?
Flush statement should be execute after persist. So Code should be:
getEntityManager()->persist($user);
getEntityManager()->flush();
I had a similar issue and thought I would post it here. I was creating an entity and everything was responding correctly, but when I checked the database no record had been created.
Just wrapped the flush in a try-catch and logged the error.
$this->em->persist($insectLifeCycle);
try {
$this->em->flush();
} catch (\Exception $error) {
$this->logger->debug($error);
}
It turns out that one of properties was exceeding its character limit and the database was throwing an error. Also found out I need to improve my error handling....
As Samiul Amin Shanto said:
getEntityManager()->persist($user);
getEntityManager()->flush();
will be correct way, because persist action prepare the data to be stored in DB and flush "Flushes all changes to now to the database."
If you have the object id and then, the database is not showing it, you might have a "START TRANSACTION" and then you have your insert, after this insert you will have a "COMMIT". If any error appears between your persist and the COMMIT, object won't be stored in your database.
Check your Symfony request profiler information.
You can find it using the developer tool and checking your response for it.
I'm new in Symfony2. I've a task to create blog. One of the necessary is displaying most popular posts. So I think that the best varient is create listener. It will be call, when visiter will read post. And listener will increment onŠµ of the fild in database(MySQL). I create method in repository, which makes selection by this field. And also create Action, which renders posts by this selection. But when I try to read post, a have error:
FatalErrorException: Error: Call to a member function getId() on a non-object in /var/www/blo/src/Blog/Bundle/BlogBundle/EventListener/PostVisitedListener.php line 20.
Please, help me.
This my Entity (Post):
namespace Blog\Bundle\BlogBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Post
*
* #ORM\Table(name="post")
* #ORM\Entity(repositoryClass="Blog\Bundle\BlogBundle\Entity\PostRepository")
*/
class Post
{
/**
* #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
* #Assert\Length(min="13", max="255")
*/
private $title;
/**
* #var string
*
* #ORM\Column(name="author", type="string", length=100)
* #Assert\NotBlank
* #Assert\Length(min="13", max="100")
*/
private $author;
/**
* #var string
*
* #ORM\Column(name="post", type="text")
* #Assert\NotBlank
* #Assert\Length(min="100")
*/
private $post;
/**
* #var string
*
* #ORM\Column(name="image", type="string", length=100)
*/
private $image;
/**
* #Gedmo\Timestampable(on="create")
* #ORM\Column(name="createdAt", type="datetime")
*
*/
private $createdAt;
/**
*
* #ORM\Column(name="tags", type="text")
*/
private $tags;
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="posts")
*/
private $category;
/**
* #ORM\OneToMany(targetEntity="Comment", mappedBy="post")
*/
private $comments;
/**
* #var integer
*
* #ORM\Column(name="visitedIncrement", type="integer")
*/
private $visitedIncrement;
public function __construct()
{
$this->comments = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set title
*
* #param string $title
* #return Post
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
/**
* Get title
*
* #return string
*/
public function getTitle()
{
return $this->title;
}
/**
* Set author
*
* #param string $author
* #return Post
*/
public function setAuthor($author)
{
$this->author = $author;
return $this;
}
/**
* Get author
*
* #return string
*/
public function getAuthor()
{
return $this->author;
}
/**
* Set post
*
* #param string $post
* #return Post
*/
public function setPost($post)
{
$this->post = $post;
return $this;
}
/**
* Get post
*
* #return string
*/
public function getPost()
{
return $this->post;
}
/**
* Set image
*
* #param string $image
* #return Post
*/
public function setImage($image)
{
$this->image = $image;
return $this;
}
/**
* Get image
*
* #return string
*/
public function getImage()
{
return $this->image;
}
/**
* Set tags
*
* #param string $tags
* #return Post
*/
public function setTags($tags)
{
$this->tags = $tags;
return $this;
}
/**
* Get tags
*
* #return string
*/
public function getTags()
{
return $this->tags;
}
/**
* Set category
*
* #param $category
* #return $this
*/
public function setCategory($category)
{
$this->category = $category;
return $this;
}
/**
* Get category
*
* #return integer
*/
public function getCategory()
{
return $this->category;
}
/**
* Set comments
*
* #param string $comments
* #return Post
*/
public function setComments($comments)
{
$this->comments = $comments;
return $this;
}
/**
* Get comments
*
* #return string
*/
public function getComments()
{
return $this->comments;
}
/**
* #param \DateTime $createdAt
*/
public function setCreatedAt($createdAt)
{
$this->createdAt = $createdAt;
return $this;
}
/**
* #return \DateTime
*/
public function getCreatedAt()
{
return $this->createdAt;
}
/**
* #param int $visitedIncrement
*/
public function setVisitedIncrement($visitedIncrement)
{
$this->visitedIncrement = $visitedIncrement;
return $this;
}
/**
* #return int
*/
public function getVisitedIncrement()
{
return $this->visitedIncrement;
}
public function __toString()
{
return $this->getTitle();
}
}
This is my PostRepository
public function visitedIncrement($id)
{
$query = $this->getEntityManager()
->createQuery(
'UPDATE BlogBlogBundle:Post p
SET p.visitedIncrement = p.visitedIncrement + 1
WHERE p.id = :post_id')
->setParameter(':post_id', $id);
$query->execute();
This is my PostVisitedEvent
namespace Blog\Bundle\BlogBundle\Event;
use Blog\Bundle\BlogBundle\Entity\Post;
use Symfony\Component\EventDispatcher\Event;
class PostVisitedEvent extends Event
{
protected $post;
/**
* #param Post $post
*/
public function setPost(Post $post)
{
return $this->post;
}
/**
* #return Post
*/
public function getPost()
{
return $this->post;
}
}
This is my PostVisitedListener
namespace Blog\Bundle\BlogBundle\EventListener;
use Blog\Bundle\BlogBundle\Entity\PostRepository;
use Doctrine\ORM\EntityManager;
use Blog\Bundle\BlogBundle\Event\PostVisitedEvent;
class PostVisitedListener
{
protected $repository;
public function __construct(PostRepository $repository)
{
$this->repository = $repository;
}
public function onPostVisited(PostVisitedEvent $event)
{
$this->repository->visitedIncrement($event->getPost()->getId());
}
This is my Action (it opens post and gives a opportunity to create comment):
public function showPostAction($id)
{
$postRepository = $this->container->get('blog_blog_bundle.post.repository');
$post = $postRepository->find($id);
if (!$post) {
throw $this->createNotFoundException('The post is not found!');
}
$commentRepository = $this->container->get('blog_blog_bundle.comment.repository');
$comments = $commentRepository->findCommentForPost($post->getId());
$event = new PostVisitedEvent();
$event->setPost($post);
$eventDispatcher = $this->get('event_dispatcher');
$eventDispatcher->dispatch('blog_blog_bundle.post_visited', $event);
return $this->render('BlogBlogBundle:Default:showPost.html.twig', array(
'post' => $post,
'comments' => $comments,
));
}
Yuo can see, that I also create services for repositories and listener. There are:
service id="blog_blog_bundle.post.repository" class="Blog\Bundle\BlogBundle\Entity\PostRepository" factory-service="doctrine.orm.entity_manager" factory-method="getRepository"
argument>BlogBlogBundle:Post argument
service
service id="blog_blog_bundle.comment.repository" class="Blog\Bundle\BlogBundle\Entity\CommentRepository" factory-service="doctrine.orm.entity_manager" factory-method="getRepository"
argument BlogBlogBundle:Comment argument
service
service id="blog_blog_bundle.post_visited_listener" class="Blog\Bundle\BlogBundle\EventListener\PostVisitedListener"
argument type="service" id="blog_blog_bundle.post.repository"
tag name="kernel.event_listener" event="blog_blog_bundle.post_visited" method="onPostVisited"
service
Please, help me.
public function onPostVisited(PostVisitedEvent $event)
{
if (!($event->getPost() instanceof PostInterface)) return;
$this->repository->visitedIncrement($event->getPost()->getId());
}
PostInterface is not a symfony interface. You have to code it. Asking for interface is better than asking for a concrete instance because symfony sometimes uses proxy classes instead of concrete classes.