Im working on a multilingual-news publisher in symfony2 using the doctrine extensions:-
Translatable and Loggable
Although I have them both working with a default english posts (i.e logs on update) I cant get edited translations to be logged... the record is inserted into 'ext_log_entries' table but i only get an empty serialised array.
Does anyone have any ideas or examples? I've pretty much followed the git-hub guide for both extensions.
Thanks!
I tried to do the same thing earlier. I had to find out it is not possible to use these two things together, you have to implement it for yourselfe.
I did it this way:
Text.php
use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table(name="text")
* #ORM\Entity
* #Gedmo\Loggable
*/
class Text
{
/** #ORM\Id #ORM\GeneratedValue #ORM\Column(type="integer") */
private $id;
/** #ORM\Column(name="class", type="string") */
private $class;
/** #ORM\Column(name="field", type="string") */
private $field;
/** #ORM\Column(name="key_id", type="integer") */
private $key_id;
/**
* #ORM\ManyToOne(targetEntity="Language")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="lang_id", referencedColumnName="id")
* })
*/
private $lang;
/**
* #Gedmo\Versioned
* #ORM\Column(name="text", type="text")
*/
private $text;
...getter setter...
}
Language.php
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table(name="language")
* #ORM\Entity
*/
class Language {
/** #ORM\Id #ORM\GeneratedValue #ORM\Column(type="integer") */
private $id;
/** #var #ORM\Column(name="language", type="string") */
private $language;
... getter setter ....
Now I wrote a service for managing these:
TextManager.php
/**
* #param $object
* #param $field
* #param $langKey
* #return Text
*
* gets the existing text for the object
* if it does not exist, it creates a new one
*/
public function setupText($object, $field, $langKey){
$lang = $this->langRepo->findOneBy(array("language" => $langKey));
$txt = $this->textRepo->findOneBy(array(
'class' => get_class($object), 'field' => $field,
'lang' => $lang, 'key_id' => $object->getId()
));
if($txt != null){
return $txt;
}
$txt = new Text();
$txt->setClass(get_class($object));
$txt->setField($field);
$txt->setKeyId($object->getId());
$txt->setLang($lang);
return $txt;
}
/**
* #param $object
* #param $field
* #param $langKey
* #return mixed
* gives you the text object or false if not found
*/
public function getText($object, $field, $langKey){
$lang = $this->langRepo->findOneBy(array("language" => $langKey));
$txt = $this->textRepo->findOneBy(array(
'class' => get_class($object), 'field' => $field,
'lang' => $lang, 'key_id' => $object->getId()
));
if ($txt == null){
return false;
}
return $txt;
}
In your controller you can now to things like that:
to get an text object in ohter version
$logRepo = $em->getRepository('Gedmo\Loggable\Entity\LogEntry'); // we use default log entry class
$text = $textManager->setupText($object, 'abouttext', 'de');
$logRepo->revert($text, 1); //1 is the version you would like to have
$text->getText(); //version 1 text
to make a new text object
$text = $textManager->setupText($object, 'otherfield', 'fr');
$text->setText("your super text");
$em->persist($text);
$em->flush();
Hope it helps you, and if you have any question, dont mind asking.
Related
I have two entities (User and Product) with a OneToMany relationship. I want to retrieve all Product entities related to a specific User filtering them by a field called "finished". I will post the relevant information of those entities:
User Entity:
/**
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
*/
class User implements UserInterface
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Product", mappedBy="user")
*/
private $products;
....
Product Entity:
/**
* #ORM\Entity(repositoryClass="App\Repository\Productepository")
*/
class Product
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="products")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
/**
* #ORM\Column(type="boolean")
*/
private $finished;
....
I am using the following code to filter my products, but is there any other more efficient way to do it?
$user = $this->getUser();
$products = $user->getProducts();
$filtered = [];
foreach ($products as $product){
if(!$product->getFinished()){
$filtered[] = $product;
}
}
Thanks for your help
One of the solutions would be to use a database query like the following :
- in the controller, you can do
<?php
namespace App\Controller\ProductController;
use App\Repository\ProductRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
class ProductController extends AbstractController
{
/**
* #Route("/products", methods={"GET"}, name="app_get_products")
* #IsGranted("IS_AUTHENTICATED_REMEMBERED")
*/
public function listProduct(ProductRepository $repository)
{
$user = $this->getUser();
$products = $repository->findBy([
'user' => $user,
'finished' => false
]);
return $this->render('Product/list.html.twig', [
'products' => $products,
]);
}
}
Notice the request that is made : this is an attempt at a solution.
I want to achieve a join between my product entity and my provider entity, only, all seems ok but it does not work ..
My mistake : Could not resolve type of column "id" of class "AppBundle\Entity\Colombus\Provider"
Could not resolve type of column "id" of class "AppBundle \ Entity \ Columbus \ Provider"
Here is the mapping of the Product entity:
<?php
namespace AppBundle\Entity\Colombus;
use Doctrine\ORM\Mapping as ORM;
/**
* Product
*
* #ORM\Table(name="product")
* #ORM\Entity(repositoryClass="AppBundle\Repository\ProductRepository")
*/
class Product
{
/**
* #var int
*
* #ORM\Column(name="reference_produit", type="integer")
* #ORM\Id
*/
private $id;
/**
* #var string
*
* #ORM\ManyToOne(targetEntity="Provider")
* #ORM\JoinColumn(name="code_fournisseur_principal", referencedColumnName="id")
*/
private $provider;
Mapping of the provider entity: I spare you the other attributes:
<?php
namespace AppBundle\Entity\Colombus;
use Doctrine\ORM\Mapping as ORM;
/**
* Provider
*
* #ORM\Table(name="provider")
* #ORM\Entity(repositoryClass="AppBundle\Repository\ProviderRepository")
*/
class Provider
{
/**
* #var int
*
* #ORM\Column(name="code_fournisseur", type="string")
* #ORM\Id
*/
private $id;
Controller :
/**
*
* #Route("/{_locale}/build_order/step1/2", name="order.build")
* #return Response
*/
public function buildOrderAction(SessionInterface $session){
$em = $this->getManager($session->get('em'));
$products = $em->getRepository('AppBundle\Entity\Colombus\Product')->findAll();
return $this->render('AppBundle::creation/create_order.html.twig',[
'products' => $products
]);
}
If it can help you too:
public function getManager($dbname){
$paths = array(__DIR__ . '/AppBundle/Entity/Colombus');
$isDevMode = false;
$conn = array(
'dbname' => $dbname,
'user' => 'root',
'password' => '',
'host' => '127.0.0.1',
'driver' => 'pdo_mysql',
'charset' => 'utf8',
);
$config = Setup::createConfiguration($isDevMode);
$driver = new AnnotationDriver(new AnnotationReader(), $paths);
AnnotationRegistry::registerLoader('class_exists');
$config->setMetadataDriverImpl($driver);
$em = EntityManager::create($conn, $config);
return $em;
}
You need to correct your referencedColumnName in Product entity like
/**
* #var string
*
* #ORM\ManyToOne(targetEntity="Provider")
* #ORM\JoinColumn(name="code_fournisseur_principal", referencedColumnName="code_fournisseur")
*/
private $provider;
But i am conscious regarding the join part your are trying to relate a string type column code_fournisseur of Provider and its not set to auto or primary key which may give you another error related to your mapping
The disclaimer: I'm new to Symfony. Really struggling with the collection field type and simple setup of a OnetoOne relationship.
Scenario: I have a Product entity class and a Category entity class. I am using a collection field on the product to create Category items. I am picturing a separate Category Table with a Name column and a related product_id column. I know it's not typically practical. For arguments sake Category may as well be Feature or something else as I just want to establish the relationship as a scenario to extend. Ultimately Category will become an Image field allowing me to pull related images into a view.
The Problem: I've followed the cookbook article (http://symfony.com/doc/current/cookbook/form/form_collections.html) over and over but just hitting brick wall. I'm on board with the principle but feel like I'm missing something significant. I've got a protoype form field generating from the javascript and I'm successfully persisting/saving new Products (in full) and new Categories (only in part). The related product id is not being written into the join column.
I'm sure it's a case of getters/setters not being taken care of correctly. I' relying on doctrine to generate them automatically. Or the issue may be with some unspecified requirement to set the id to the Category in the controller.
Code to follow. Help greatly appreciated as been banging this around for a couple of days and getting nowhere fast. Really frustrating as grasped all other principles really quickly and really chuffed with building a first project in symfony.
Product Entity
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
use Doctrine\Common\Collections\ArrayCollection;
use AppBundle\Entity\Category;
/**
* Page
*
* #ORM\Table(name="product")
* #ORM\Entity
* #Vich\Uploadable
*/
class Product
{
/**
* #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;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* #ORM\OnetoOne(targetEntity="Category", cascade={"persist"})
*/
protected $categorys;
public function __construct()
{
$this->categorys = new ArrayCollection();
}
}
Category Entity
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use AppBundle\Entity\Product;
/**
* Category
*
* #ORM\Table(name="category")
* #ORM\Entity
*/
class Category
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #ORM\OneToOne(targetEntity="Product", cascade={"persist"})
*/
protected $product;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Category
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set product
*
* #param \AppBundle\Entity\Product $product
* #return Category
*/
public function setProduct(\AppBundle\Entity\Product $product = null)
{
$this->product = $product;
return $this;
}
/**
* Get product
*
* #return \AppBundle\Entity\Product
*/
public function getProduct()
{
return $this->product;
}
}
Product Type
<?php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use AppBundle\Entity\Product;
use AppBundle\Entity\Category;
class ProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', 'text')
->add('categorys', 'collection', array(
'type' => new CategoryType(),
'allow_add' => true,
'by_reference' => false,
))
->add('save', 'submit', array(
'attr' => array('class' => 'btn btn-default'),
))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Product',
));
}
public function getName()
{
return 'product';
}
}
Category Type
<?php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use AppBundle\Entity\Category;
class CategoryType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', 'text');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Category',
));
}
public function getName()
{
return 'category';
}
}
Product Controller: New Product
/**
* #Route("admin/product/new", name="product_add")
* #Security("has_role('ROLE_ADMIN')")
*/
public function newAction(Request $request)
{
$product = new Product();
$form = $this->createForm(new ProductType(), $product);
$category = new Category();
$form->handleRequest($request);
if ($form->isValid()) {
$category->getProduct($this);
$em = $this->getDoctrine()->getManager();
$em->persist($product);
$em->flush();
return $this->redirectToRoute('products_admin');
}
return $this->render('Product/productAdd.html.twig', array(
'form' => $form->createView(),
));
}
I bet that you don't need OneToOne but OneToMany relation for Product vs Category. Because i think that one category can have multiple products but a product can only have one category.
Another approach is even to make a ManyToMany relation. In that case each category can have multiple products, but each product can be in multiple categories too.
OneToOne is not often used, only in special circumstances.
I think you dont need the collection-type too in your case. You could change this for the entity type. Just make the entities ManyToOne as i explained.
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Product
*
* #ORM\Table()
* #ORM\Entity
*/
class Product
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="description", type="string", length=64)
*/
private $description;
/**
* #var float
*
* #ORM\Column(name="price", type="float")
*/
private $price;
/**
* #ORM\ManyToOne(targetEntity="Category")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
**/
private $category;
}
and category:
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Category
*
* #ORM\Table()
* #ORM\Entity
*/
class Category
{
/**
* #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=64)
*/
private $name;
public function __toString() {
return $this->name;
}
}
and now use the console to run some commands:
app/console doctrine:generate:entities AppBundle
app/console doctrine:schema:update --force
app/console doctrine:generate:crud AppBundle:Product
app/console doctrine:generate:crud AppBundle:Category
choose YES if asked if you want to generate write Actions
Add a __toString() method to your Category entity:
public function __toString() {
return $this->name;
}
Now see your new controllers and routes and try them.
To see all your routes:
app/console router:debug
You must have this in your app/config/routing.yml:
app:
resource: "#AppBundle/Controller/"
type: annotation
At the end you will have:
- two new entities
- two new controllers
- two new form types
- eight new views
You can learn a lot from the created code and change everything as you wish.
Good luck
I know it's a old question, but :
/**
* #Route("admin/product/new", name="product_add")
* #Security("has_role('ROLE_ADMIN')")
*/
public function newAction(Request $request)
{
$product = new Product();
$form = $this->createForm(new ProductType(), $product);
$category = new Category();
$form->handleRequest($request);
if ($form->isValid()) {
#################################
##$category->getProduct($this);##
#################################
$category->setProduct($this);
$em = $this->getDoctrine()->getManager();
$em->persist($product);
$em->flush();
return $this->redirectToRoute('products_admin');
}
return $this->render('Product/productAdd.html.twig', array(
'form' => $form->createView(),
));
}
You call getProduct($this) instead of setProduct($this)
I'm trying to perform a ManyToMany self referencing association in my Symfony 2.1 project by following the Doctrine docs: http://docs.doctrine-project.org/en/latest/reference/association-mapping.html#many-to-many-self-referencing
My use-case is that I'm working on a CMS and I'm adding the ability to have related items of content. For example: I could have a sidebar on a website which would say that this piece of content X is related to Y and Z. Similarly on pages where content Y appears it says that it is related to content item X.
In my tests using this to add a new relation between content items fails because it reaches PHP's maximum nesting level of 100 because it is running toArray() on the current content item and then again on the related content item and so on and so on.
I've seen many similar questions on SO about Many-to-Many Self referential Doctrine associations but none with enough complete code to be able to see how others have managed this. Can anybody help?
My Content entity:
/**
* #ORM\MappedSuperclass
* #ORM\Table(name="content")
* #ORM\Entity(repositoryClass="CMS\Bundle\Common\ContentBundle\Entity\ContentRepository")
* #ORM\InheritanceType("JOINED")
*/
abstract class content implements ContentInterface
{
/**
* #var int $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $title
*
* #ORM\Column(name="title", type="string", length=255)
* #Assert\NotBlank()
*/
private $title;
// Other class properties
/**
* #var array
*
* #ORM\ManyToMany(targetEntity="Content", cascade={"persist"})
* #ORM\JoinTable(name="content_relation",
* joinColumns={#ORM\JoinColumn(name="relation_id", referencedColumnName="id")},
* inverseJoinColumns={
* #ORM\JoinColumn(name="related_content_id", referencedColumnName="id")
* })
**/
private $related;
public function __construct()
{
$this->related = new ArrayCollection();
}
// Other getters & setters for class properties
/**
* #return array
*/
public function getRelated()
{
return $this->related;
}
/**
* #param Content $relation
*/
public function addRelation(Content $relation)
{
$this->related->add($relation);
$this->related->add($this);
}
/**
* #return array
*/
public function toArray()
{
$related = array();
foreach($this->getRelated() as $relatedItem) {
$related[] = $relatedItem->toArray();
}
return array(
'type' => static::getType(),
'id' => $this->id,
'title' => $this->title,
....
'related' => $related
);
}
In my RelationsController for managing the related content data I use it like this:
/**
* Creates a new relation to a content item
*
* #Route("{_locale}/content/{id}/related", name="relation_add")
* #Method("POST")
*/
public function addAction(Request $request, $id)
{
// Validation and error checking
// $entity is loaded by the repository manager doing a find on the passed $id
$entity->addRelation($relation);
$em = $this->getEntityManager();
$em->persist($entity);
$em->persist($relation);
$em->flush();
$response = $relation->toArray();
return new JsonResponse($response, 201);
}
The fix for this was to use the JMSSerializerBundle to encode the entity to JSON instead of using a toArray method and change the addRelation function to:
/**
* #param Content $relation
*/
public function addRelation(Content $relation)
{
$this->related[] = $relation;
if (! $relation->getRelated()->contains($this)) {
$relation->addRelation($this);
}
}
I am stuck and frustrated with the bellow error message:
Catchable Fatal Error: Argument 1 passed to Medicine\UserBundle\Entity\User
::setUsertype() must be an instance of Medicine\UserBundle\Entity\Usertype,
instance of Doctrine\Common\Collections\ArrayCollection given, called in
/opt/lampp/htdocs/drugs/vendor/symfony/src/Symfony/Component/Form/Util
/PropertyPath.php on line 347 and defined in /opt/lampp/htdocs/drugs/src/
Medicine/UserBundle/Entity/User.php line 224
What I think this error is due to use of manytoone field in my entity, I even tried with keeping onetomany in another entity.
I have a user entity and a usertype entity, the usertype_id is a manytoone field in user table. here is the code for both the entities:-
User
namespace Medicine\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity(repositoryClass="Medicine\UserBundle\Repository\UserRepository")
* #ORM\Table(name="user")
* #ORM\HasLifecycleCallbacks()
*/
class User
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\HasLifecycleCallbacks()
*/
protected $id;
/**
* #ORM\Column(type="string")
*/
protected $username;
/**
* #ORM\ManyToOne(targetEntity="Usertype", inversedBy="users")
* #ORM\JoinColumn(name="usertype_id", referencedColumnName="id")
*/
protected $usertype;
/**
* #ORM\Column(type="string")
*/
protected $image;
/**
* Set usertype
*
* #param Medicine\UserBundle\Entity\Usertype $usertype
*/
public function setUsertype(\Medicine\UserBundle\Entity\Usertype $usertype)
{
$this->usertype = $usertype;
}
/**
* Get usertype
*
* #return Medicine\UserBundle\Entity\Usertype
*/
public function getUsertype()
{
return $this->usertype;
}
}
I am just showing the concerned code, i have all the getter and setter methods for the above code.
UserType
namespace Medicine\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity(repositoryClass="Medicine\UserBundle\Repository\UsertypeRepository")
* #ORM\Table(name="usertype")
* #ORM\HasLifecycleCallbacks()
*/
class Usertype
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\HasLifecycleCallbacks()
*/
protected $id;
/**
* #ORM\Column(type="string")
*/
protected $name;
/**
* #ORM\OneToMany(targetEntity="User", mappedBy="usertype")
*/
protected $users;
public function __construct()
{
$this->users = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add users
*
* #param Medicine\UserBundle\Entity\User $users
*/
public function addUser(\Medicine\UserBundle\Entity\User $users)
{
$this->users[] = $users;
}
/**
* Get users
*
* #return Doctrine\Common\Collections\Collection
*/
public function getUsers()
{
return $this->users;
}
}
Controller
This Executes when a user wants to login. He will fill in the username password and a UserType:
public function indexAction()
{
$entity = new User();
$form = $this->createForm(new LoginForm(), $entity);
$request = $this->getRequest();
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
if ($form->isValid()) {
echo "<pre>"; print_r($entity->getUsertype()); exit;
$em = $this->getDoctrine()
->getEntityManager();
$em->persist($entity);
$userrepository = $em->getRepository('MedicineUserBundle:User');
echo "<pre>"; print_r($entity->getUsertype()); exit;
$all = $userrepository->findOneBy(array('login' => $entity->getLogin(), 'password' => $entity->getPassword()));
if($all)
{
return $this->redirect($this->generateUrl('MedicineUserBundle_login'));
}
}
}
return $this->render('MedicineUserBundle:User:loginpage.html.twig',array(
'form' => $form->createView()
));
}
Login Form
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('login', 'text', array('label' => 'Username',))
->add('password','password')
->add('usertype', 'entity', array('class' => 'MedicineUserBundle:Usertype', 'property'=>'name', 'multiple' => true, ))
;
}
The 'multiple' => true in combination with your entity association definition is causing this problem.
You should find that if you change multiple to false (and as such can only select one UserType for your User), things work properly.
If you want multiple UserTypes for one User, you have a Many-to-Many association - one user can have many UserTypes, and one UserType can have many Users. See Doctrine's ManyToMany association type to implement this. Documentation Here.
Hope this helps.