Zend\Form in conjunction with Doctrine 2 and ManyToOne Relationships - doctrine-orm

today i started reading myself into the features of Zend\Form. I found a great tutorial from Michael Gallego in which he explains how to use some new cool features.
The example works fine so far if we're handling 1-1 Relationships. Doctrine covers them fine.
What i want to do is
Instead of having textarea for the related value, I'd like a select box
The select box should have valid options, depending on what's at the database
For editing purpose later, the currently selected value needs to be selected
Doctrine should not add new rows to the One-Table
As you can see at my github sources i made use of the example in the tutorial, but shortened it to "Product" and "Brand". Brands - in my example - is a DB-Table with predefined Brands (Nike, Adidas, Puma, whatever) and when you create a new Product from the form you get those Brands as a select menu.
Right now, the way i add the options isn't working. I know i can manually set the options with an array like
$form->get('product')->get('brand')->setAttribute('options', array('Nike'=>'1', 'Adidas'=>'2', etc);
But i strongly assume that there is a more automated way to do this. I simply do not understand all this Hydrator classes provided with Zend.
The Problem is, even if i manually define the array as described above, the mapping of Product and Brand is not working correctly. The dump of $product right now looks like this
object(Application\Entity\Product)[210]
protected 'id' => null
protected 'name' => string 'asdasd' (length=6)
protected 'price' => string '123123' (length=6)
protected 'brand' =>
object(Application\Entity\Brand)[215]
protected 'id' => null
protected 'name' => string '1' (length=1)
Obviously the brand is mapped completely wrong (for what i want to achieve, zend probably sees this as right, since the VALUE of my select is 1).
Question How do i tell my Form to map the select-value to the mapped object ID? Though maybe the way i set up my product-model is wrong in that case.
Any help will be greatly appreciated :)

this is code from my form object
I hope it will help
class ProductForm extends Form
{
public function __construct($em)
{
parent::__construct();
$this->add(array(
'name' => 'productGroupId',
'attributes' => array(
'type' => 'select',
'label' => 'Category',
'options' => array(),
),
));
$this->setProductGropus($em->getRepository('Project\Entity\ProductGroup')->findAll());
public function setProductGropus($groups)
{
$groupsForm = array('--Select--'=>'');
foreach ($groups as $group) {
$groupsForm[$group->name] = (string) $group->productGroupId;
}
$this->get('productGroupId')->setAttribute('options',$groupsForm);
}
}
}

Looking at your BrandFieldSet you have only specified name to the InputFilterProvider thus the id will never be passed along.
Secondly im going to recommend you to remove the Registry. Classes created by using the ServiceManager can/should implement ServiceManagareAwareInterface if they need access to anything else if not specified by using constructors.
So in your controller instead of using your registry you access the service manager
$this->getServiceLocator()
->get('FQCN_OR_ALIAS');
There are some great examples written by contributors of the framework and ill list a few of there github repos here.
https://github.com/ZF-Commons And https://github.com/EvanDotPro (Can't post any more since i lack reputation)
Come join us on #zftalk.2 on irc.freenode.org if you have any further questions

Although this is an old question, thought I'd answer anyway. The previous answers don't use the ObjectSelect of Doctrine.
You say to have a OneToOne relationship and do not want records added to the "One-table"; I'm assuming here you have a Uni-directional OneToOne relationship.
However, if you got "Product" and "Brand" as entities a OneToMany Bi-directional relationship might be more suitable ;)
Going however with OneToOne, your entities should look like this:
class Brand {
/**
* #var int
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* #var string
* #ORM\Column(name="name", type="string", nullable=false, length=128)
*/
protected $name;
//Getters/Setters
}
class Product {
/**
* #var int
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* #var string
* #ORM\Column(name="name", type="string", nullable=false, length=128)
*/
protected $name;
//Change below "OneToOne" to "ManyToOne" for proper product + brand relationship. Just this change will leave it as uni-directional.
/**
* #var Brand
* #ORM\OneToOne(targetEntity="Brand", fetch="EAGER")
* #ORM\JoinColumn(name="brand", referencedColumnName="id")
*/
protected $brand;
//Getters/Setters
}
Assuming your entities are correct, you should then use the ObjectSelect build into Doctrine.
class ProductForm
{
/** #var ObjectManager */
protected $objectManager;
public function __construct($name = 'product-form', $options = [])
{
parent::__construct($name, $options);
}
public function init()
{
$this->add([
'type' => 'DoctrineModule\\Form\\Element\\ObjectSelect',
'name' => 'brand',
'required' => true,
'attributes' => [
'id' => 'selectBrand',
'multiple' => false,
'value' => null,
],
'options' => [
'label' => 'Select brand',
'object_manager' => $this->getObjectManager(),
'target_class' => Brand::class,
'property' => 'id',
'is_method' => true,
'find_method' => [
'name' => 'findBy',
'params' => [
'criteria' => [],
'orderBy' => ['name' => 'ASC'],
],
],
'empty_option' => '--- Select Brand ---',
'label_generator' => function (Brand $entity) {
return $entity->getName();
}
],
]);
}
/**
* #return ObjectManager
*/
public function getObjectManager()
{
return $this->objectManager;
}
/**
* #param ObjectManager $objectManager
*/
public function setObjectManager(ObjectManager $objectManager)
{
$this->objectManager = $objectManager;
}
}
Make sure to setup the Module.php to be able to load this form. Add the getServiceConfig() function to it.
public function getServiceConfig()
{
/** #var ServiceManager $sm */
return [
'factories' => [
'product_form' => function ($sm)
{
$form = new ProductForm();
$form->setInputFilter(new ProductInputFilter());
/** #var EntityManager $entityManager */
$entityManager = $sm->get('doctrine.entitymanager.orm_default');
//Set Doctrine ObjectManager
$form->setObjectManager($entityManager);
//Set Doctrine Object as Hydrator
$form->setHydrator(new DoctrineObject($entityManager, Product::class));
//Set Doctrine Entity
$form->setObject(new Product());
//Initialize elements onto form
$form->init();
return $form;
},
],
];
}
}
Next, load the form in a Controller.
$form = $this->getServiceLocator()->get('product_form');
===========================
Note: This works up until Zend Framework 2.5.2

Related

SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'tokenable_id' cannot be null

i want use laravel sanctum with 2 model
this is code for user model
<?php namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens,Notifiable;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'id', 'name', 'family','gender','birhday','national_code','email','mobile','profie_pic','province_code','city_code','address','username','role','status','password',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token','role',
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
}
and usercontroller for function login is :
public function login(Request $request) {
$user= User::where('email', $request->email)->first();
if (!$user || !Hash::check($request->password, $user->password)) {
return response([
'message' => ['These credentials do not match our records.']
], 404);
}
$token = $user->createToken('my-app-token')->plainTextToken;
$response = [
'user' => $user,
'token' => $token
];
return response($response, 201);
}
and token is true and all route with middleware('auth:sanctum') is working true but i want so use model nurces and write this code
first:model nurces:
class Nurces extends model
{
use HasApiTokens,Notifiable;
protected $fillable = [
'name', 'family','gender','birhday','national_code','email','mobile','profie_pic','province_code','city_code','address','username','status','password',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password','remember_token',
];
protected $primaryKey = 'nurces_id';
}
and function login in nurcescontroller is :
public function login(Request $request) {
$nurces= Nurces::where('email', $request->email)->first();
if (!$nurces || !Hash::check($request->password, $nurces->password)) {
return response([
'message' => ['These credentials do not match our records.']
], 404);
}
$token = $nurces->createToken('my-nurces-token-')->plainTextToken;
$response = [
'nurces' => $nurces,
'token' => $token
];
return response($response, 201);
}
when use postman and login with this address - this erorr :
"message": "SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'tokenable_id' cannot be null (SQL: insert into `personal_access_tokens` (`name`, `token`, `abilities`, `tokenable_id`, `tokenable_type`, `updated_at`, `created_at`) values (my-nurces-token-, fa55707e2bd9e1b71f8e5ebc0623f9ce1cc8e49f5b6e1ff804dda262e93811f4, [\"*\"], ?, App\\Nurces, 2020-07-05 11:18:58, 2020-07-05 11:18:58))",
I think, there is an mismatch between primary key of App\Nurces model.
As per documentation and error, two columns values of personal_access_tokens table as follows:
tokenable_type - model name e.g
App\Model\User
tokenable_id - primary key of model
In your case, tokenable_id value trying to insert as NULL, that's why getting error.
Please check and confirm primary key column name of App\Nurces model. Add the below property in the Nurces model class.
protected $primaryKey = 'primary key column name';
https://laravel.com/docs/7.x/eloquent

Symfony 3: UniqueEntity(errorPath) not working

I have an entity called ClassSubject and you can see it bellow,
/**
* ClassSubject
*
* #ORM\Table(name="class_subject")
* #ORM\Entity(repositoryClass="PIE10Bundle\Repository\ClassSubjectRepository")
*
* #UniqueEntity( fields={"class", "subjects"},
* errorPath="subjects",
* message="This subject is already added"
* )
*/
class ClassSubject
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var int
*
* #ORM\ManyToOne(targetEntity="Classes")
* #ORM\JoinColumn(name="class_id", referencedColumnName="id")
*/
private $class;
/**
* #var int
*
* #ORM\ManyToOne(targetEntity="Subject")
* #ORM\JoinColumn(name="subject_id", referencedColumnName="id")
*/
private $subjects;
as you can see, I want to make this Unique Entity so I am following this guide.
Further I have added the followings to the top of the entity file,
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
After all when I submit data it still adds duplicating rows to the table. For an example,
if the table exists with a row like, class=clsA and subjects=subA and if I try to insert with same values, it inserts data with no validation/message "This subject is already added"
My controller is like below,
public function addTeacherSubjectAction( $id, Request $request )
{
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository('PIE10Bundle:Users')->find($id);
$subjects = $em->getRepository('PIE10Bundle:Subject')->findAll();
$form = $this->createForm(ClassSubjectType::class,$subjects);
$form->handleRequest($request);
if( $form->isSubmitted() && $form->isValid() )
{
$allSubjects = $form['subject']->getData();
foreach( $allSubjects as $subject )
{
$subject_repo = $em->getRepository('PIE10Bundle:Subject')->find($subject->getId());
$teacherSubject = new TeacherSubjects();
$teacherSubject->setTeachers($user);
$teacherSubject->setSubjects($subject_repo);
$em->persist($teacherSubject);
$em->flush();
}
$this->addFlash('notice',
'Subjects added');
return $this->redirectToRoute('user_teachers');
}
return $this->render( 'PIE10Bundle:Form:layout_commom_form.html.twig',
array( 'title' => 'Add Subjects',
'form' => $form->createView() )
);
}
and the ClassSubjectType is like below,
class ClassSubjectType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('subject',
EntityType::class,
array('class' => 'PIE10Bundle:Subject',
'expanded' => true,
'multiple' => true,
'attr' => array( 'class' => 'form-control',
'style' => 'margin:5px 0;')));
$builder->add('Add Classes',
SubmitType::class,
array('attr' => array('class' => 'btn btn-primary',
'style' => 'margin:15px 0;')) );
}
}
So I need to know what is wrong with my code. Thanks in advance.

Cakephp 3 - Unit test validationDefault

I'm currently trying to write a unit test for the following model:
<?php
namespace App\Model\Table;
use App\Model\Entity\User;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
/**
* Users Model
*
* #property \Cake\ORM\Association\HasMany $Comments
* #property \Cake\ORM\Association\BelongsToMany $Albums
*/
class UsersTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->table('users');
$this->displayField('id');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->hasMany('Comments', [
'foreignKey' => 'user_id'
]);
$this->belongsToMany('Albums', [
'foreignKey' => 'user_id',
'targetForeignKey' => 'album_id',
'joinTable' => 'users_albums'
]);
}
/**
* #Author: Mark van der Laan
* #Date: 23-02-2016
* #Description: Validating rules for the user model. Some additional, more complex validation rules are added.
* #param \Cake\Validation\Validator $validator Validator instance.
* #return \Cake\Validation\Validator
*/
public function validationDefault(Validator $validator)
{
// id
$validator
->integer('id')
->allowEmpty('id', 'create');
// username
$validator
->requirePresence('username', 'create')
->notEmpty('username')
// Enabled, just in case that the username will be an email address
->email('username')
->add('username', [
'length' => [
'rule' => ['minLength', 7],
'message' => 'Username needs to be at least 7 characters long!',
]
]);
// password
$validator
->requirePresence('password', 'create')
->notEmpty('password')
->add('password', [
'length' => [
'rule' => ['minLength', 7],
'message' => 'Password needs to be at least 7 characters long!',
]
]);
// sign_in_count
$validator
->integer('sign_in_count')
->requirePresence('sign_in_count', 'create')
->notEmpty('sign_in_count');
// ip address
$validator
->allowEmpty('current_sign_in_ip')
->requirePresence('current_sign_in_ip', 'create')
// Currently checking for both IPv4 and IPv6 addresses
->ip('current_sign_in_ip', 'both');
// active
$validator
->boolean('active')
->requirePresence('active', 'create')
->allowEmpty('active');
return $validator;
}
/**
* Returns a rules checker object that will be used for validating
* application integrity.
*
* #param \Cake\ORM\RulesChecker $rules The rules object to be modified.
* #return \Cake\ORM\RulesChecker
*/
public function buildRules(RulesChecker $rules)
{
$rules->add($rules->isUnique(['username']));
return $rules;
}
}
It is important for me to test the validationDefault method which I try to do with the following code snippet:
public function testValidationDefault()
{
$data = ['username' => 'adminadmin#mixtureweb.nl',
'password' => 'testtest123',
'sign_in_count' => 0,
'current_sign_in_ip' => '127.0.0.1',
'active' => 'true'
];
$this->assertTrue($this->Users->save($data));
// $this->assertTrue($data);
}
As I try to do this, this will throw an error saying that I shouldn't pass an array to assertTrue method. Therefore, I'm trying to find examples but I couldn't find anything. Has anyone some references where I can find how to unit test validation rules? (so far I couldn't find anything in the documentation)
Update
public function testValidationDefault()
{
$data = ['username' => 'adminadmin#mixtureweb.nl',
'password' => 'testtest123',
'sign_in_count' => 0,
'current_sign_in_ip' => '127.0.0.1',
'active' => true
];
$user = $this->Users->newEntity($data);
$saved = $this->Users->save($user);
$this->assertTrue($saved);
// $this->assertTrue($data);
}
This will give 'Failed asserting that App\Model\Entity\User Object &0000000011b3c53b0000000040aca14b is true'. Does anyone know what I'm doing wrong?
Take a look at what Table::save() returns, it's \Cake\Datasource\EntityInterface|bool. On success it returns the persisted entity, on failure it returns boolean false. So your save operation succeeds and it will return an entity, hence the error.
If you want to test validation, you should either use the validator object that your table class offers (Table::validationDefault() via Table::validator()), or use Table::patchEntity() or Table::newEntity() and test the value of Entity:errors().
Patching/creating entities is where validation in the model layer happens, the saving process will only apply application rules.
public function testValidationDefault()
{
$data = [
'username' => 'adminadmin#mixtureweb.nl',
'password' => 'testtest123',
'sign_in_count' => 0,
'current_sign_in_ip' => '127.0.0.1',
'active' => true
];
$user = $this->Users->newEntity($data);
$this->assertEmpty($user->errors()); // empty = no validation errors
}
See also
Cookbook > Validation > Validating Data
Cookbook > Validation > Validating Entities
Cookbook > Database Access & ORM > Validating Data > Validation vs. Application Rules

Zend Framework 2 Doctrine 2 many to many entity relation issue

I have the following tables: films, categories, films_categories and the entities:
Film.php
<?php
namespace Admin\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table(name="films")
*/
class Film{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
* #ORM\Column(length=11)
*/
private $id;
/**
* #ORM\Column(type="string")
*/
private $name;
/* .... */
/**
* #ORM\ManyToMany(targetEntity="Category")
* #ORM\JoinTable(name="films_categories",
* joinColumns={#ORM\JoinColumn(name="film_id", referencedColumnName = "id")},
* inverseJoinColumns={#ORM\JoinColumn(name="category_id", referencedColumnName="id")})
*/
private $categories;
public function __construct(){
$this->categories = new ArrayCollection();
}
public function getCategoriesNames(){
$names = array();
foreach($this->categories as $category){
$names[] = $category->getName();
}
return $names;
}
public function getId(){
return $this->id;
}
public function setId($id){
$this->id = $id;
}
/* ... */
/**
* #return Collection
*/
public function getCategories(){
return $this->categories;
}
public function addCategories(Collection $categories){
foreach($categories as $category){
$this->categories->add($category);
}
}
public function removeCategories(Collection $categories){
foreach($categories as $category){
$this->categories->removeElement($category);
}
}
}
Category.php
<?php
namespace Admin\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table(name="categories")
*/
class Category {
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
/* ... */
public function getId(){
return $this->id;
}
public function setId($id){
$this->id = $id;
}
/* ... */
}
What I want to do is create a form and the action to add a new film and assign a category to it. Here is the form I used:
FilmFieldset.php
<?php
namespace Admin\Form;
use Zend\Form\Fieldset;
use Zend\InputFilter\InputFilterProviderInterface;
use DoctrineORMModule\Stdlib\Hydrator\DoctrineEntity;
use Admin\Entity\Film;
class FilmFieldset extends Fieldset implements InputFilterProviderInterface{
protected $entityManager;
public function __construct($em){
parent::__construct('film');
$this->entityManager= $em;
$this->setHydrator(new DoctrineEntity($em,'Admin\Entity\Film'))
->setObject(new Film());
#$this->setAttribute('method','post');
#$this->setAttribute('class','standardForm');
$this->add(array(
'name' => 'id',
'type' => 'hidden'
));
/* ... */
$this->add(
array(
'type' => 'DoctrineModule\Form\Element\ObjectSelect',
'name' => 'categories',
'attributes' => array(
'multiple' => 'multiple',
),
'options' => array(
'object_manager' => $em,
'target_class' => 'Admin\Entity\Category',
'property' => 'name',
'label' => 'Categories: ',
'disable_inarray_validator' => true
),
)
);
}
public function getInputFilterSpecification(){
return array(
/* .... */
'categories' => array(
'required' => true,
),
);
}
}
The FilmForm.php
<?php
namespace Admin\Form;
use Zend\Form\Form;
use Zend\Stdlib\Hydrator\ClassMethods;
use Admin\Entity\Film;
use Zend\InputFilter\InputFilter;
use DoctrineModule\Stdlib\Hydrator\DoctrineObject as DoctrineHydrator;
class FilmForm extends Form{
public function __construct($em){
parent::__construct('filmForm');
$this->setAttribute('method','post')
->setAttribute('class','standardForm')
->setHydrator(new DoctrineHydrator($em,'\Admin\Entity\Film'))
->setInputFilter(new InputFilter());
/* I register the fieldset through a service and not directly here */
// $this->add(array(
// 'type' => new FilmFieldset($em),
// 'options' => array(
// 'user_as_base_fieldset' => true
// )
// ));
$this->add(array(
'name' => 'security',
'type' => 'Zend\Form\Element\Csrf'
));
$this->add(array(
'name' => 'submit',
'type' => 'submit',
));
$this->setValidationGroup(array(
'security',
'film' => array(
'categories',
)
));
}
}
The addAction:
public function addAction() {
$em = $this->getEntityManager();
$form = $this->getForm();
$film = new Film();
$form->bind($film);
if($request->isPost()){
$post = array_merge_recursive(
$request->getPost()->toArray(),
$request->getFiles()->toArray()
);
$form->setData($post);
if($form->isValid()){
$categories = array();
foreach($post['film']['categories'] as $categoryId){
$categories[] = $em->getRepository('Admin\Entity\Category')->find($categoryId);
}
$film->addCategories($categories);
$em->persist($film);
$em->flush();
}else{
// the form is not valid
}
}
The result is various errors and and ORMExcption with the message "Found entity of type on association Admin\Entity\Film#categories, but expecting Admin\Entity\Category"
Please help me out, I'm literally freaking out over this! Thank you :)
From what i found out from this is some thing like this that your entity is receiving Admin\Entity\Film#categories and in this part categories is some what a value. Where as your entity is expecting an object of type Admin\Entity\Film#categories.
To get over this you have to create a
public function SetCategory(Admin\Entity\Category Category)
{
$this->categories(or category or w/e your variable name is)= Category;
}
public function getCategory()
{
return $this->categories(or category or w/e your variable name is);
}
And then in your Action you have to pass object of Category to Film Entity in some thing like this
$Film->SetCategory($categoryObj);
Of course you have to set your business logic according to your part, but this error Should be removed by this apporach.
You need to define the FQCN for the targetEntity.
Change:
* #ORM\ManyToMany(targetEntity="Category")
to:
* #ORM\ManyToMany(targetEntity="Admin\Entity\Category")
This has to do with hydration and lazy loading. I'm no expert on this, but the #categories is a proxy object, and when you are going to save it, it's complaining because it needs to be an actual collection, not a proxy object to a collection.
If you did noobie-php's approach, you're re-attaching new category objects on the main object, so they're not proxies any more.
This is frustrating, as Doctrine was supposed to save a lot of the hassle of this, but it doesn't actually do what you'd naturally expect in many situations. I'd found a bug ticket on this some time ago but can't find it now - will add it to this if I can find it.

Using Zend Framework 2 + Doctrine2.3 with ObjectSelect and ManyToOne relations

I"m having a bit of trouble with using Doctrine's ObjectSelect on ManyToOne relations.
The relations I have below using ManyToMany work 100% adding and editing. My edit form is populated with the current selection without any problems.
The issue arises with the ManyToOne relations, it appears the form is not being populated with the current selection.
I have tried dumping the Task entity before I bind it to the form and it looks 100% right, all my relations are populated in the entity.
However after binding it, the form is being displayed without the current value selected.
Task entity:
/**
* #ORM\Entity
* #ORM\Table(name="tasks")
*/
class Task
{
/**
* #ORM\Id
* #ORM\Column(type="integer");
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
...
/**
* #ORM\ManyToOne(targetEntity="Category")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
protected $category;
...
/**
* #ORM\ManyToMany(targetEntity="ZDUser\Entity\User")
* #ORM\JoinTable(name="tasks_assigned_user_linker",
* joinColumns={#ORM\JoinColumn(name="task_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")}
* )
*/
protected $assignedUsers;
/**
* Initialize
*/
public function __construct()
{
$this->assignedUsers = new ArrayCollection();
}
/**
* We need a few getters and setters
*/
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
}
public function getCategory() {
return $this->category;
}
public function setCategory(Category $category) {
$this->category = $category;
}
public function getAssignedUsers() {
return $this->assignedUsers;
}
public function addAssignedUsers(Collection $users) {
foreach ($users as $user) {
$this->assignedUsers->add($user);
}
}
public function removeAssignedUsers(Collection $users) {
foreach ($users as $user) {
$this->assignedUsers->removeElement($user);
}
}
}
I'm using ManyToOne in most of my entities, I find this way a little easier and extensible going forward. I can just add addtional entities and link them to other ones without having to do relations on both sides.
Category Entity
/**
* #ORM\Entity
* #ORM\Table(name="task_categories")
* #property int $id
* #property string $name
*/
class Category
{
/**
* #ORM\Id
* #ORM\Column(type="integer");
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", nullable=false)
*/
protected $name;
...
/**
* Setters and getters we need
*/
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = (int) $id;
}
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
}
User Entity:
/**
* #ORM\Entity
* #ORM\Table(name="users")
*/
class User implements UserInterface, ProviderInterface
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
...
/**
* #ORM\ManyToMany(targetEntity="ZDUser\Entity\Group")
* #ORM\JoinTable(name="users_groups_linker",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="group_id", referencedColumnName="id")}
* )
*/
protected $groups;
/**
* Initialies the object
*/
public function __construct()
{
$this->groups = new ArrayCollection();
}
/* Getters and setters, we must define these for the implementation to work */
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = (int) $id;
}
/* Get and add groups */
public function getGroups()
{
return $this->groups;
}
public function addGroup(Group $group)
{
$this->groups->add($group);
}
Form code:
class TaskForm extends Form implements ObjectManagerAwareInterface
{
protected $objectmanager;
public function __construct(EntityManager $em)
{
// we want to ignore the name passed
parent::__construct('task');
$this->setHydrator(new DoctrineHydrator($em,'TaskList\Entity\Task'));
$this->setAttribute('method', 'post');
$this->add(array(
'name' => 'id',
'attributes' => array(
'type' => 'hidden',
),
));
$this->add(array(
'name' => 'subject',
'type' => 'Text',
'options' => array(
'label' => 'Subject',
),
));
$this->add(array(
'name' => 'category',
'type' => 'DoctrineModule\Form\Element\ObjectSelect',
'options' => array(
'label' => "Category",
'object_manager' => $em,
'target_class' => 'TaskList\Entity\Category',
'property' => 'name',
),
));
$this->add(array(
'name' => 'assignedUsers',
'type' => 'DoctrineModule\Form\Element\ObjectSelect',
'attributes' => array(
'multiple' => 'multiple',
),
'options' => array(
'label' => "Assigned To (User)",
'object_manager' => $em,
'target_class' => 'ZDUser\Entity\User',
'property' => 'email',
),
));
Controller for Edit & Add:
public function addAction()
{
$this->addedit();
// Grab form
$form = new TaskForm($this->getEntityManager());
// Grab any request we may have
$request = $this->getRequest();
// If it a post ...
if ($request->isPost()) {
$task = new Task();
$form->bind($task);
// Populate data
$form->setData($request->getPost());
// Check if the form is valid
if ($form->isValid()) {
// Setup some things we need
$task->setCreated(new \DateTime("now"));
// Save
$this->getEntityManager()->persist($task);
$this->getEntityManager()->flush();
// Redirect to list of tasks
return $this->redirect()->toRoute('tasklist');
}
}
return array(
'form' => $form
);
}
public function editAction()
{
$this->addedit();
// Get ID or redirect
$id = (int)$this->getEvent()->getRouteMatch()->getParam('id');
if (!$id) {
return $this->redirect()->toRoute('tasklist');
}
// Create a form
$form = new TaskForm($this->getEntityManager());
// Grab entity from doctrine
$task = $this->getEntityManager()->find('TaskList\Entity\Task', $id);
// Bind the form to the task
$form->bind($task);
// Check if we have a request and if its POST
$request = $this->getRequest();
if ($request->isPost()) {
// If it is, set the form data from the request
$form->setData($request->getPost());
// If the form is valid, bind the values
if ($form->isValid()) {
// Setup some things we need
$task->setLastUpdated(new \DateTime("now"));
// Flush the update
$this->getEntityManager()->flush();
// Redirect to list of tasks
return $this->redirect()->toRoute('tasklist');
}
}
return array(
'id' => $id,
'form' => $form,
);
}
I'm so sure I'm missing something really simple.
I had a similar problem, see this issue on the DoctrineORMModule GitHub for more info.
It's something to do with Doctrine not returning the correct ID field from metadata when Proxies Entities are loaded. The solutions are as follows:
Wait for official fix in Doctrine 2.4 (you can install 2.4-beta2 now!).
Subclass the ObjectSelect to force the use of the correct ID field (someone in the aforelinked issue did this).
Patch DoctrineModule with this:
--- doctrine/doctrine-module/src/DoctrineModule/Form/Element/Proxy.php 2013-03-11 17:49:55.406011600 -0300
+++ doctrine/doctrine-module/src/DoctrineModule/Form/Element/Proxy.php 2013-03-11 17:51:33.592710900 -0300
## -240,7 +240,10 ##
if (count($identifier) > 1) {
//$value = $key;
} else {
- $value = current($metadata->getIdentifierValues($value));
+ // Doctrine has a bug that makes the following not work,
+ // this is a horrible workaround until Doctrine 2.4 is released with a fix.
+ //$value = current($metadata->getIdentifierValues($value));
+ $value = $value->getId();
}
}
}
The $value->getId() relies on that method being available in the corresponding entity. This isn't recommended but a quick fix.
Probably not the right way? I posted my idea to their ML :)
Probably not the right way? I posted my idea to their ML :)
diff --git a/vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
index cba525a..2f62375 100644
--- a/vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
+++ b/vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
## -667,7 +667,17 ## class ClassMetadataInfo implements ClassMetadata
return $id;
}
- $value = $this->reflFields[$this->identifier[0]]->getValue($entity);
+
+ /**
+ * NK: First try use the getter, in the case of a proxied object, the reflection is not going to work
+ * as the proxied object does not have any properties
+ */
+ $getter = 'get' . ucfirst($this->identifier[0]);
+ if (method_exists($entity, $getter)) {
+ $value = $entity->$getter();
+ } else {
+ $value = $this->reflFields[$this->identifier[0]]->getValue($entity);
+ }