I have an entity which looks like
.
.
.
/**
* #ORM\ManyToMany(targetEntity="PrLeadBundle\Entity\Stock")
* #ORM\JoinColumn(name="stock_id", referencedColumnName="id", nullable=true)
*/
private $stock;
public function __construct()
{
$this->stock = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add stock
*
* #param \PrLeadBundle\Entity\Stock $stock
* #return ValueList
*/
public function addStock(\PrLeadBundle\Entity\Stock $stock)
{
$this->stock[] = $stock;
return $this;
}
/**
* Remove stock
*
* #param \PrLeadBundle\Entity\Stock $stock
*/
public function removeStock(\PrLeadBundle\Entity\Stock $stock)
{
$this->stock->removeElement($stock);
}
/**
* Get stock
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getStock()
{
return $this->stock;
}
Now, If I try to access theese values I get an Join Column error.
I'm using :
$Values = $entityManager->getRepository('PrLeadBundle:ValueList')->findBy(array('disabled' => '0','exportable'=>'1'), array('title' => 'ASC'));
foreach ($Values as $item){
var_dump($item->getStock()->getId());
}
This is a bit confusing for me cause i ran into an :
Attempted to call method "getId" on class
"Doctrine\ORM\PersistentCollection". 500 Internal Server Error -
UndefinedMethodException
I did thsi for certain times in past, and I can also access the stock values in twig by using {{ item.stock.id }} ...
What am I doing wrong??
Because $stock variable is a Doctrine\Common\Collections\ArrayCollection,
so instead of $item->getStock()->getId() you should try something like $item->getStock()->first()->getId()); or var_dump($item->getStock() to see the structure before extracting the element.
Related
I use KNP Doctrinebehaviors Bundle to translate my entity, and a2lix_translations to get i18n form,
I have no problems with those steps :
Adding entity with multi-languages.
Getting entities in cases my default locale language.
Update entity.
Delete entity.
But the probleme is how to access the propreties of my Page Entity in twig?
This is somes pictures to understand the problem :
This is my PageEntity
public function findAllByLocale($locale){
return $this->createQueryBuilder('a')
->join('a.translations', 'aTrans')
->where('aTrans.locale = :locale')
->setParameter("locale", $locale)
->addSelect('aTrans')
->getQuery()
->getResult()
;
}
use ORMBehaviors\Translatable\Translation;
/**
* #var string $title
*
* #ORM\Column(name="title", type="string", length=255)
*/
private $title;
/**
* #var string $content
*
* #ORM\Column(name="content", type="text")
*/
private $content;
/**
* #ORM\ManyToOne(targetEntity="Page", inversedBy="trans", cascade={"persist", "remove"})
* #var Collection
*/
private $object;
/**
* Get title
*
* #return string
*/
public function getTitle()
{
if( $title == $this->translate()->getTitle() ) {
return $title;
}
return '';
}
/**
* Set title
*
* #param string $title
* #return Page
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
/**
* Set content
*
* #param string $content
* #return Page
*/
public function setContent($content)
{
$this->content = $content;
return $this;
}
/**
* Get content
*
* #return string
*/
public function getContent()
{
return $this->content;
}
/**
* #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);
}
and this is my query :
<!-- begin snippet: js hide: true -->
FormType
twig page : index.html.twig
Thank you
Here's the answer to what I understood:
FIRST:
If you want to display the translated fields of an entity based on guessed locale, from the doc:
proxy translations
An extra feature allows you to proxy translated fields of a translatable entity.
You can use it in the magic __call method of you translatable entity so that when you try to call getName (for example) it will return you the translated value of the name for current locale:
public function __call($method, $arguments)
{
return $this->proxyCurrentLocaleTranslation($method, $arguments);
}
Now when displaying an entity that has a translated field name in a twig template you can use:
{{ entity.getName() }}
The proxy will intercept your call and return the appropriate content by guessing the locale from the request. This is how you'll want to display most entities.
SECOND:
The other way if you specifically want to translate an entity in French then you can use the following, also in twig:
{{ entity.translate('fr').getName() }}
I am working with form aimed at uploading the file and updating the database in Symfony2. I want to manually set value of book_id field and not to allow user to change it in the form. Thus in my controller before using doctrine to persist document I am calling:
$documents->setBookId('1');
Unluckilly I get error which indicates that the doctrine does not recognise the above hard coded value input.
An exception occurred while executing 'INSERT INTO Documents (book_id, marker, document_date, link, notes) VALUES (?, ?, ?, ?, ?)' with params [null, "fdd", "2015-04-04", null, "test"]:
To my mind this may be connected with the fact that book_id field is related to Books. Therefore probably I should use setBook function instead. Could you please advice how to do this properly?
My controler file looks like this:
/**
* This code is aimed at checking if the book is chosen and therefore whether any further works may be carried out
*/
$session = new Session();
if(!$session->get("App_Books_Chosen_Lp")) return new RedirectResponse($this->generateUrl('app_listbooks'));
// Authorization goes here
$documents = new Documents();
$form = $this->createForm(new DocumentsType(), $documents);
$form->add('save', 'submit', array('label' => 'Dodaj dokument'));
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$documents->upload();
$documents->setBookId('1');
$em->persist($documents);
$em->flush();
}
return $this->render('AppBundle:Documents:adddocuments.html.twig', array('form' => $form->createView()));
Document class:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* #ORM\Entity
* #ORM\Table(name="Documents")
* #ORM\HasLifecycleCallbacks
*/
class Documents
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Books", inversedBy="documents")
* #ORM\JoinColumn(name="book_id", referencedColumnName="id")
*/
protected $book;
/**
* #ORM\Column(type="integer")
*/
protected $book_id;
/**
* #ORM\Column(type="string", length=220)
*/
protected $marker;
/**
* #ORM\Column(type="date", length=220)
*/
protected $document_date;
/**
* #ORM\Column(type="string", length=220)
* #Assert\File(maxSize="6000000")
*/
protected $link;
/**
* #ORM\Column(type="text")
*/
protected $notes;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set book_id
*
* #param integer $bookId
* #return Documents
*/
public function setBookId($bookId)
{
$this->book_id = $bookId;
return $this;
}
/**
* Get book_id
*
* #return integer
*/
public function getBookId()
{
return $this->book_id;
}
/**
* Set marker
*
* #param string $marker
* #return Documents
*/
public function setMarker($marker)
{
$this->marker = $marker;
return $this;
}
/**
* Get marker
*
* #return string
*/
public function getMarker()
{
return $this->marker;
}
/**
* Set document_date
*
* #param \DateTime $documentDate
* #return Documents
*/
public function setDocumentDate($documentDate)
{
$this->document_date = $documentDate;
return $this;
}
/**
* Get document_date
*
* #return \DateTime
*/
public function getDocumentDate()
{
return $this->document_date;
}
/**
* Set link
*
* #param string $link
* #return Documents
*/
public function setLink($link)
{
$this->link = $link;
return $this;
}
/**
* Get link
*
* #return string
*/
public function getLink()
{
return $this->link;
}
/**
* Set notes
*
* #param string $notes
* #return Documents
*/
public function setNotes($notes)
{
$this->notes = $notes;
return $this;
}
/**
* Get notes
*
* #return string
*/
public function getNotes()
{
return $this->notes;
}
/**
* Set book
*
* #param \AppBundle\Entity\Books $book
* #return Documents
*/
public function setBook(\AppBundle\Entity\Books $book = null)
{
$this->book = $book;
return $this;
}
/**
* Get book
*
* #return \AppBundle\Entity\Books
*/
public function getBook()
{
return $this->book;
}
/*
* ### FILE UPLOAD PROCESS ###
*/
/**
* #Assert\File(maxSize="6000000")
*/
private $file;
/**
* Sets file.
*
* #param UploadedFile $file
*/
public function setFile(UploadedFile $file = null)
{
$this->file = $file;
}
/**
* Get file.
*
* #return UploadedFile
*/
public function getFile()
{
return $this->file;
}
public function getAbsolutePath()
{
return null === $this->path
? null
: $this->getUploadRootDir().'/'.$this->path;
}
public function getWebPath()
{
return null === $this->path
? null
: $this->getUploadDir().'/'.$this->path;
}
protected function getUploadRootDir()
{
// the absolute directory path where uploaded
// documents should be saved
return __DIR__.'/../../../../web/'.$this->getUploadDir();
}
protected function getUploadDir()
{
// get rid of the __DIR__ so it doesn't screw up
// when displaying uploaded doc/image in the view.
return 'uploads/documents';
}
public function upload()
{
// the file property can be empty if the field is not required
if (null === $this->getFile()) {
return;
}
// use the original file name here but you should
// sanitize it at least to avoid any security issues
// move takes the target directory and then the
// target filename to move to
$this->getFile()->move(
$this->getUploadRootDir(),
$this->getFile()->getClientOriginalName()
);
// set the path property to the filename where you've saved the file
$this->path = $this->getFile()->getClientOriginalName();
// clean up the file property as you won't need it anymore
$this->file = null;
}
}
Okay, first since you're using ManyToOne relation, you don't actually need another property refering to the book - book_id. You can remove that and leave book only.
Then in your controller you have to query the database for that Book and set the that object your Document.
You can do it like this:
$bookId = 1; // Following your example, let's say tou already know the book ID.
$book = $em->getReference('AppBundle:Books', $bookId);
// Check if we actually found a record and then set it to Documents
// Looking at your entity mapping, your reference to Book can not be null,
// but doing an extra check never hurts, since this is just an example.
if( $book ) {
$documents->setBook($book);
}
-Update-
If you want to directly insert the bookID, then what is the purpose of having ManyToOne reference in your entity? Eventually you're going to have to start using doctrine's relations and objects properly. Also, the cool thing about getReference method is that you are getting a reference to an entity, without having to load the entity from the database - you get the so called Proxy objects.
The method EntityManager#getReference($entityName, $identifier) lets you obtain a reference to an entity for which the identifier is known, without loading that entity from the database. This is useful, for example, as a performance enhancement, when you want to establish an association to an entity for which you have the identifier
You can read further about this here.
I am trying to setup a weighted tag cloud, that works when tags are a string property in the blog entity.
Now I've setup tags as it's own entity and related to blog as a bi-directional ManyToMany/ManyToMany relationship.
Needless to say it's not working, I'm guessing it's because tag is now it's own object.
My question: What am I doing wrong here now that tags is it's own entity and not a string property from blog entity?
When I dump $tagWeights from the controller below, I get an error when I should be seeing something like this:
array (size=78)
'Tag1' => float 1
'Tag2' => float 5
'Tag3' => float 2
error: (this line:
foreach ($tags as $tag) {
$tagWeights[$tag] = (isset($tagWeights[$tag])) ? $tagWeights[$tag] + 1 : 1;
}
ContextErrorException: Warning: Illegal offset type in isset or empty in /var/www/html/Satori/src/Symfony/AcmeBundle/Entity/TagRepository.php line 34
I'm calling tags in the following way in twig:
Twig
{% for tag, weight in tags %}
<span class="weight-{{ weight }}">{{ tag }}</span>
{% else %}
<p>There are no tags</p>
{% endfor %}
Controller (dumping $tags works great shows all the tags)
public function footerAction()
{
$em = $this->getDoctrine()->getManager();
$tags = $em->getRepository('AcmeBundle:Tag')
->getTags();
$tagWeights = $em->getRepository('AcmeBundle:Tag')
->getTagWeights($tags);
var_dump($tagWeights); die();
return array(
'tags' => $tagWeights,
);
}
Here is the getTags and getTagWeights:
public function getTags()
{
$tags = $this->createQueryBuilder('t')
->select('t.tag')
->getQuery()
->getResult();
return $tags;
}
public function getTagWeights($tags)
{
$tagWeights = array();
if (empty($tags))
return $tagWeights;
foreach ($tags as $tag)
{
$tagWeights[$tag] = (isset($tagWeights[$tag['tag']])) ? $tagWeights[$tag] + 1 : 1;
}
// Shuffle the tags
uksort($tagWeights, function() {
return rand() > rand();
});
$max = max($tagWeights);
// Max of 5 weights
$multiplier = ($max > 5) ? 5 / $max : 1;
foreach ($tagWeights as &$tag)
{
$tag = ceil($tag * $multiplier);
}
return $tagWeights;
}
Tag entity
class Tag
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="tag", type="string", length=255)
*/
private $tag;
/**
* #ORM\ManyToMany(targetEntity="Blog", mappedBy="tags")
*/
protected $blogs;
public function __construct()
{
$this->blogs = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set tag
*
* #param string $tag
* #return Tag
*/
public function setTag($tag)
{
$this->tag = $tag;
return $this;
}
/**
* Get tag
*
* #return string
*/
public function getTag()
{
return $this->tag;
}
/**
* Add blogs
*
* #param \AcmeBundle\Entity\Blog $blogs
* #return Tag
*/
public function addBlog(\AcmeBundle\Entity\Blog $blogs)
{
$this->blogs[] = $blogs;
return $this;
}
/**
* Remove blogs
*
* #param \AcmeBundle\Entity\Blog $blogs
*/
public function removeBlog(\AcmeBundle\Entity\Blog $blogs)
{
$this->blogs->removeElement($blogs);
}
/**
* Get blogs
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getBlogs()
{
return $this->blogs;
}
}
Proposed solutions and results
Using (isset($tagWeights[$tag['tag']]))
error:
ContextErrorException: Warning: Illegal offset type in /var/www/html/Satori/src/Symfony/AcmeBundle/Entity/TagRepository.php line 34
Using (isset($tagWeights[$tag[$tag->getTag()]))
error:
ContextErrorException: Notice: Trying to get property of non-object in /var/www/html/Satori/src/Symfony/AcmeBundle/Entity/TagRepository.php line 34
I think I know what's happening here...
When you make this call in your footerAction:
$tags = $em->getRepository('AcmeBundle:Tag')
->getTags();
your repository returns to you an array of arrays. So when you say:
isset($tagWeights[$tag])
...that $tag is actually an associative array of the result set. I think what you mean to say here is:
isset($tagWeights[$tag['tag']])
That said, I'd advise against giving your Tag entity a property called tag. a tag doesn't have a tag, it has a name, or a description. And it'd make much more sense here to say
isset($tagWeights[$tag['name']])
...or by having the repository return the Tag entities themselves (why do you even need to override the built-in getXyz() method?), and do:
isset($tagWeights[$tag->getName()])
In an entity I have a field that looks like this:
/**
* #ORM\Column(type="array")
*/
protected $category;
and QueryBuilder
$qb = $this->createQueryBuilder('s');
$qb->select($fields)
->where( 's.category IN (:category)') //////////// <----
->orderBy('s.name', 'ASC')
->setParameter('category', $category_id);
So in database field category is Doctrine2 Array. I want to select records from database with QueryBuilder. My question is, how can i do this, with WHERE clause that will be checking fields from that array ?
A look here may help you
// Instead, use $qb->expr()->in('value', array('?1')) and bind your parameter to ?1 (see section above)
public function in($x, $y); // Returns Expr\Func instance
$qb->select($fields)
->where($qb->expr()->in('s.category', $categories))
#Cerad gave you a perfectly valid comment. One of the problem of storing arrays is that you don't have any chance of searching.
See PHP/MySQL - Storing array in database, and Storing arrays in the database. As you can see, it is a terrible practice.
The best way is to simply create a Category entity, and to have a OneToMany relation with that category.
Here is an example of an entity Book that has many categories:
1 Create your category entity:
class Category implements CategoryInterface
{
//.....
/**
* Title of the category
*
* #ORM\Column(type="string", length=100)
*/
protected $title;
/**
* Relation with your book entity for example
*
* #ORM\ManyToOne(targetEntity="Book", inversedBy="categories")
* #ORM\JoinColumn(name="book_id", referencedColumnName="id")
*/
private $book;
/**
* Set book
*
* #param BookInterface $book
*/
public function setBook(BookInterface $book)
{
$this->book = $book;
}
/**
* Get book
*
* #return BookInterface
*/
public function getBook()
{
return $this->book;
}
}
2 Your book entity:
use Doctrine\Common\Collections\ArrayCollection;
class Book implements BookInterface
{
/**
* Categories for the books
*
* #ORM\OneToMany(targetEntity="Category", mappedBy="book")
* #var CategoryInterface[]
*/
protected $categories ;
public function __construct()
{
$this->categories = new ArrayCollection();
}
/**
* Add Categories
*
* #param CategoryInterface $category
*/
public function addCategory(CategoryInterface $category)
{
$category->setBook($this);
$this->categories->add($category);
}
/**
* Remove Category
*
* #param CategoryInterface $category
* #return bool
*/
public function removeCategory(CategoryInterface $category)
{
return $this->categories->removeElement($category);
}
/**
* Get Categories
*
* #return Doctrine\Common\Collections\Collection
*/
public function getCategories()
{
return $this->categories;
}
/**
* Set Categories
*
* #param ArrayCollection $categories
*/
public function setCategories($categories) {
$this->categories->clear();
foreach ($categories as $category) {
$this->addCategory($category);
}
return $this;
}
3 Your can now search properly.
I'm learning SF2 - really impressed with the job done, faced my first real issue I can't solve myself.
I have two entities: Post and Tag. Shortened code below:
class Tag
{
/**
* #ORM\ManyToMany(targetEntity="Post", mappedBy="tags", cascade={"persist"})
*/
private $posts;
public function __construct()
{
$this->posts = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* #param \My\AppBundle\Entity\Snippet $posts
* #return Tag
*/
public function addSnippet(\My\AppBundle\Entity\Post $posts)
{
$this->posts[] = $posts;
return $this;
}
/**
* #param \My\AppBundle\Entity\Snippet $snippets
*/
public function removeSnippet(\My\AppBundle\Entity\Post $posts)
{
$this->posts->removeElement($posts);
}
/**
* #return \Doctrine\Common\Collections\Collection
*/
public function getSnippets()
{
return $this->posts;
}
}
class Post
{
/**
* #ORM\ManyToMany(targetEntity="Tag", inversedBy="posts", cascade={"persist"})
* #ORM\JoinTable(name="posts_tags",
* joinColumns={#ORM\JoinColumn(name="post_id", referencedColumnName="id", unique=true, onDelete="cascade")},
* inverseJoinColumns={#ORM\JoinColumn(name="tag_id", referencedColumnName="id", unique=true, onDelete="cascade")}
* )
*/
private $tags;
public function __construct()
{
$this->tags = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* #param \My\AppBundle\Entity\Tag $tags
* #return Snippet
*/
public function addTag(\My\AppBundle\Entity\Tag $tags)
{
$this->tags[] = $tags;
return $this;
}
/**
* #param \My\AppBundle\Entity\Tag $tags
*/
public function removeTag(\My\AppBundle\Entity\Tag $tags)
{
$this->tags->removeElement($tags);
}
/**
* #return \Doctrine\Common\Collections\Collection
*/
public function getTags()
{
return $this->tags;
}
}
As you can see I have M:M relation between two entities.
I have also a form to add Post with embedded Tag collection:
$builder
->add('title')
->add('tags', 'collection', array(
'type' => new \My\AppBundle\Form\TagType(),
'allow_add' => true,
'by_reference' => false,
'prototype' => true
))
;
TagType form class:
$builder->add('name');
Everything works as expected. Except one thing: if there's a Tag object with the following name, I'm getting SQLSTATE[23000]: Integrity constraint violation MySQL error which is obvious. If I apply unique validation constraint I can add a tag to post (if it already exists in database).
It's obvious I need to check if following tag does exist in database and add it only if does not, but... how to do it Symfony way?
Any suggestions appreciated!
You can use UniqueEntity to handle this. I can't see your annotations on your tags class, or your declaration of 'name' but if you add something like the below it should give you a unique validation constraint based on name with an optional message to throw back.
/**
* #ORM\Entity
* #UniqueEntity(fields="name", message="This tag name already exists")
*/
class Tag...
/**
* #var string $name
*
* #ORM\Column(name="name", type="string", length=255, unique=true)
*/
protected $name;