Zend Framework - Cascading Deleting Using Table Data Mapper pattern - zend-framework

My problem getting Zend Framework to provide a DRI layer can now be summarized as such.
Using the class definitions below I am able to delete the user but not the related comment through my local UserController "public/users/delete/userId/22", even though I have set up a refernece map and table relationship definition.
Does anyone have any answers to why the associated comment record is not deleted when i delete the users record?
class Default_Model_DbTable_Comment extends Zend_Db_Table_Abstract
{
/**
* #var string Name of the database table
*/
protected $_name = 'comment';
/**
* #desc reference map
*
* Rows in the comment table are to be automatically deleted if the row in the
* User table to which they refer is deleted
*
*/
protected $_referenceMap = array(
'User' => array(
'columns' => 'user_id', // the foreign key(s)
'refTableClass' => 'Default_Model_DbTable_Users',
'refColumns' => 'id',
'onDelete' => self::CASCADE,
)
);
}
class Default_Model_DbTable_Users extends Zend_Db_Table_Abstract
{
/**
* #var string Name of the database table
*/
protected $_name = 'users';
/**
* #desc Defining referential integrity here since we are using MyISAM
* Dependent tables are referred via the class name.
*/
protected $_dependentTables = 'Default_Model_DbTable_Comment';
}

I've created models as yours and on testing it appears that it only works if dependent tables are listed in an array:
class Default_Model_DbTable_Users extends Zend_Db_Table_Abstract
{
/**
* #var string Name of the database table
*/
protected $_name = 'users';
/**
* #desc Defining referential integrity here since we are using MyISAM
* Dependent tables are referred via the class name.
*/
protected $_dependentTables = array('Default_Model_DbTable_Comment');
}
When they aren't listed in an array, I get the error
Warning: Invalid argument supplied for foreach() in C:\PHP\includes\ZendFramework-1.8.4-minimal\library\Zend\Db\Table\Row\Abstract.php on line 632
This error may not have been visible in your environment.

Related

Form setter option Symfony 3

I'm using Symfony 3 to build a website. I have an Entity (Users) that is in OneToOne relation with itself in order to make couples. (I didn't have others idea on how to do it easily)
The end goal is to create a form to reference the id of the other Users in the couple. So I created an IntegerType field and assign it the id but I can't set it (because there are no setId(...)). So I would know if there is a setter option (can't find in Doc/Tests), and if there isn't how could I achieve this ?
The steps to register a new couple would have been:
Send new id (of the other Users) [FORM]
Fetch the other Users ($userCouple = ...findOne...) [BDD]
If he have $couple == null then $userCouple->setCouple($this) and $this->setCouple($userCouple)
So my Users entity looks like:
<?php
namespace Acme\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use FOS\UserBundle\Model\User as BaseUser;
/**
* Users
*
* #ORM\Table(name="users")
* #ORM\Entity(repositoryClass="Acme\UserBundle\Repository\UsersRepository")
*/
class Users extends BaseUser
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToOne(targetEntity="Acme\UserBundle\Entity\Users")
* #ORM\JoinColumn(nullable=true)
*/
protected $couple;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set couple.
*
* #param \Acme\UserBundle\Entity\Users|null $couple
*
* #return Users
*/
public function setCouple(\Acme\UserBundle\Entity\Users $couple = null)
{
$this->couple = $couple;
return $this;
}
/**
* Get couple.
*
* #return \Acme\UserBundle\Entity\Users|null
*/
public function getCouple()
{
return $this->couple;
}
}
And my form looks like :
<?php
namespace Acme\UserBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
class ProfileFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('couple', IntegerType::class, array(
'label' => 'Couple ID',
'property_path' => 'couple.id',
'attr' => array('min' => 0),
));
}
public function getBlockPrefix()
{
return 'acme_user_profile';
}
}
You should solve this using a one-to-one self-referencing relation (see more here). Basically your couple would be replaced by partner which suites best the case:
...
/**
* #OneToOne(targetEntity="User")
* #JoinColumn(name="partner_id", referencedColumnName="id")
*/
protected $partner;
...
Then in the form you could use the EntityType (not sure why you wanted to use IntegerType in the first place) and do something like this:
$builder->add('users', EntityType::class, array(
// query choices from this entity
'class' => 'UserBundle:User',
// use the User.username property as the visible option string
'choice_label' => 'username',
));
Of course you can exclude the user you're editing the profile from the list of users you show as possible partners using query_builder option (passing a custom query) or choices to pass the collection of User entities you want to use (getting them first and filter out current user).

Extend shopware database, creating relations

I'm trying to implement a plugin to add sales representative data to my shop and associate this data to users.
On this context (users and sales representative) I have:
sales_rep - Sales representative table
sales_rep_user - Relation between User and Sales Representative
1st For the swg_sales_rep and swg_sales_rep_user relation (OneToMany) I could create that without problems
SwgSalesRepresentative.php
...
**
* #ORM\Entity
* #ORM\Table(name="swg_sales_rep")
*/
class SwgSalesRepresentative extends ModelEntity
{
...
/**
* INVERSE SIDE
*
* #var \Doctrine\Common\Collections\ArrayCollection
*
* #ORM\OneToMany(
* targetEntity="Shopware\CustomModels\SwagUserSalesRepresentative\SwgSalesRepresentative",
* mappedBy="salesRepresentative",
* orphanRemoval=true
* )
*/
protected $salesRepresentativeUsers;
...
SwgSalesRepresentativeUsers.php
/**
* #ORM\Entity
* #ORM\Table(name="swg_sales_rep_users")
*/
class SwgSalesRepresentativeUsers extends ModelEntity
{
...
/**
*
* #ORM\ManyToOne(targetEntity="Shopware\CustomModels\SwagUserSalesRepresentative\SwgSalesRepresentative")
* #ORM\JoinColumn(name="sales_rep_id", referencedColumnName="id")
*/
protected $salesRepresentative;
/**
* #return mixed
*/
public function getSalesRepresentative()
{
return $this->salesRepresentative;
}
/**
* #param $salesRepresentative
* #return ModelEntity
*/
public function setSalesRepresentative($salesRepresentative)
{
return $this->setManyToOne(
$salesRepresentative,
'\Shopware\CustomModels\SwagUserSalesRepresentative\SwgSalesRepresentative',
'salesRepresentativeUsers'
);
}
And after install I get my tables with foreign key ok.
For the relation between swg_sales_rep_user and s_user (OneToOne) I have problems. My first idea was extend the User model and add the additional logic we need. But this implies to overwrite my users table, take the risk to lose data.
What I did was create a SwgUser model that extends User model, like
SwgSalesRepresentativeUsers.php
/**
* #ORM\Entity
* #ORM\Table(name="swg_sales_rep_users")
*/
class SwgSalesRepresentativeUsers extends ModelEntity
{
...
/**
* #var \Shopware\CustomModels\SwagUserSalesRepresentative\SwgUser $user
*
* #ORM\OneToOne(targetEntity="Shopware\CustomModels\SwagUserSalesRepresentative\SwgUser", inversedBy="salesRepresentative")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
/**
* #return mixed
*/
public function getUser()
{
return $this->user;
}
/**
* #param $user
* #return ModelEntity
*/
public function setUser($user)
{
return $this->setOneToOne(
$user,
'\Shopware\CustomModels\SwagUserSalesRepresentative\SwgUser',
'user',
'salesRepresentative'
);
}
...
SwgUser.php
/**
* #ORM\Entity
* #ORM\Table(name="s_user")
*/
class SwgUser extends User
{
/**
*
* #ORM\OneToOne(targetEntity="Shopware\CustomModels\SwagUserSalesRepresentative\SwgSalesRepresentativeUsers", mappedBy="user")
*/
protected $salesRepresentative;
...
And bootstrap.php install/uninstall looks like
/**
* Install method
*
* #return bool
*/
public function install()
{
$this->updateSchema();
return true;
}
/**
* Uninstall method
*
* #return bool
*/
public function uninstall()
{
$this->registerCustomModels();
$em = $this->Application()->Models();
$tool = new \Doctrine\ORM\Tools\SchemaTool($em);
$classes = array(
$em->getClassMetadata('Shopware\CustomModels\SwagUserSalesRepresentative\SwgSalesRepresentative'),
$em->getClassMetadata('Shopware\CustomModels\SwagUserSalesRepresentative\SwgUser'),
$em->getClassMetadata('Shopware\CustomModels\SwagUserSalesRepresentative\SwgSalesRepresentativeUsers')
);
$tool->dropSchema($classes);
return true;
}
/**
* Creates the database scheme from existing doctrine models.
*
* Will remove the table first, so handle with care.
*/
protected function updateSchema()
{
$this->registerCustomModels();
$em = $this->Application()->Models();
$tool = new \Doctrine\ORM\Tools\SchemaTool($em);
$classes = array(
$em->getClassMetadata('Shopware\CustomModels\SwagUserSalesRepresentative\SwgSalesRepresentative'),
$em->getClassMetadata('Shopware\CustomModels\SwagUserSalesRepresentative\SwgUser'),
$em->getClassMetadata('Shopware\CustomModels\SwagUserSalesRepresentative\SwgSalesRepresentativeUsers')
);
try {
$tool->dropSchema($classes);
} catch (Exception $e) {
//ignore
}
$tool->createSchema($classes);
}
I tried to use the unidirectional association mapping and it creates the field but not the relation with s_user table (Foreign key).
So question is, how can I create relations with core tables on shopware without have to recreate (drop/create) the core tables?
Is it possible to alter tables programmatically? what is the best approach for these needs. Do you have an example that demonstrate this?
Thanks for helping.
there is no way to create bidirectional associations with shopware core tables yet. You can have unidirectional associations for sure, but you will not be able to add relational properties to core entities so far.
Except you intend to modify the shopware core itself which should be avoided at any time.
The only - and very tiny - possibility would be by trying to create a relation over a core entities attribute table which is quite "magic stuff" in shopware.

Symfony2 form entity processing

I have a User entity that has an ArrayCollection of Subscriptions. I have these setters and getters.
public function addSubscription(\Doge\WowBundle\Entity\Subscription $subscription)
{
$this->subscriptions[] = $subscription;
return $this;
}
public function removeSubscription(\Doge\WowBundle\Entity\Subscription $subscription)
{
$this->subscriptions->removeElement($subscription);
}
public function getSubscriptions()
{
return $this->subscriptions;
}
There is another entity called Plan. A Subscription is basically the intermediate entity between User and Plan, except it holds an extra field so it is necessary to be a dedicated entity.
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="subscriptions")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $user;
/**
* #ORM\ManyToOne(targetEntity="Plan", inversedBy="subscriptions")
* #ORM\JoinColumn(name="plan_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $plan;
/**
* #ORM\Column(type="date")
*/
protected $nextDue;
Otherwise, it would just be a many-to-many relationship with an automatic intermediate table generated.
Now in the User registration form, a user can choose between plans available in the Plans table with this code in the FormBuilder
$builder->add('subscriptions', 'entity', array('class' => 'DogeWowBundle:Plan'))
How can I create a new Subscription object given the Plan object? Would I do so in the controller? Use a datatransformer? What is the best practice for this?
you have 2 options, the first is that you have a form that contains a form. One form is mapped to your user and the second is mapped to your subscription. So basically in your user form you would have
$builder->add('subscriptions', new SubscriptionsType())
and within that SubscriptionsType you would have your entity for plans like:
$builder->add('plan', 'entity', array(
'class' => 'DogeWowBundle:Plan',
'property' => 'plan_name',
));
this way your subscriptions will be auto generated and updated as necessary.
You could also use a data transformer, but i personally like using forms within forms.

Symfony2 Doctrine insert entity with related - NOT UPDATE

I have problem with inserting User entity with related entity UserProfile.
class User
{
/**
* #var integer
*
* #ORM\Id
* #ORM\Column(name="id", type="integer", options={"unsigned"=true})
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var UserProfile
*
* #ORM\OneToOne(targetEntity="UserProfile", mappedBy="user", cascade={"persist", "remove"})
*/
protected $profile
....
}
class UserProfile
{
/**
* #var User
*
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\OneToOne(targetEntity="User", inversedBy="profile" )
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
.....
}
Standard User-UserProfile bidirectional relation. When I submit form contains fields from User entity and UserProfile entity, $form->getData() gives me User object with UserProfile. So far so good.
Mysql generate a unique identity by auto-increment PK. To insert entity I have to do:
$em->persist($user)
$em->flush()
to get PK as id for persist and flush UserProfile.
But doctrine CANT insert $user because $user object has related $user->profile received from form Type:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email', 'email', array('label' => 'email'))
->add('profile', new RegistrationProfileFormType(), array('required'=>true,))
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'User',
'cascade_validation'=>true,
));
}
Update: Exception msg
Entity of type Entity\UserProfile has identity through a foreign entity Entity\User, however this entity has no identity itself. You have to call EntityManager#persist() on the related entity and make sure that an identifier was generated before trying to persist 'Entity\UserProfile'. In case of Post Insert ID Generation (such as MySQL Auto-Increment or PostgreSQL SERIAL) this means you have to call EntityManager#flush() between both persist operations.
Q: How to handle this problem properly ?
Sorry for my rusty English.
The problem is you are going to perist objects through not owning side. Owning side is the one with "inversedBy" property. And you should persist objects that way.
You can read more about it here: http://docs.doctrine-project.org/en/latest/reference/unitofwork-associations.html
So, example for your case:
$profile->setUser($user);
$em->persist($profile).
$em->flush();
Although, if you want to keep the way you already have, there are two options:
1) Make User entity as the owning side and UserProfile as the inverse side
or
2) In your profile setter in User class do something like that:
public function setUserProfile(UserProfile $profile)
{
$this->profile = $profile;
$this->profile->setUser($this);
}
edit:
I've just notice that you don't have "id" field in your UserProfile entity - probably this is the reason. the easiest way would be separate primary and foreign keys. But if you want treat foreign as primary take a look at this article: http://docs.doctrine-project.org/en/latest/tutorials/composite-primary-keys.html#identity-through-foreign-entities

Joining-Table with Metadata Impairs Getters/Setters - Doctrine 2

I'm writing a feature which calls for the records of my joining table to carry extra metadata (Joining-Table with Metadata). I've attempted to implement this in accordance with this section of the Doctrine documentation.
See below for example Entity definitions.
The challenge now is that getGroups and setGroups do not yield/set Group entities (& the same is true from the Group instance perspective), but they yield GroupUser entities.
This adds a substantial delay to process of managing this relationships, which so far have been extremely smooth - for example, I cannot simply add, remove, or check for the existence of a Group to the collection which getGroups yields.
Can anyone identity any errors in my implementation, or else recommend a more fluid way of implementing this concept?
Thanks in advance for any input.
EDIT:
My main concern is this: using this implementation, retrieving a collection of Users from a Group entity requires this Entity method's mediation:
public function getUsers() {
return $this->users->map(function($groupUser){
return $groupUser->getUser();
});
}
I'm concerned that this could imply a major performance hit down the road. Am I incorrect?
Furthermore, how does one re-implement the setUsers method?
Group entity:
<?php
/**
* #Entity
* #Table(name="group")
*/
class Group {
/**
* #Column(type="integer", nullable=false)
* #Id
*/
protected $id = null;
/**
* #OneToMany(targetEntity="GroupUser", mappedBy="group")
* #var \Doctrine\Common\Collections\Collection
*/
protected $users;
}
User entity:
<?php
/**
* #Entity
* #Table(name="user")
*/
class User {
/**
* #Column(type="integer", nullable=false)
* #Id
*/
protected $id = null;
/**
* #OneToMany(targetEntity="GroupUser", mappedBy="user")
* #var \Doctrine\Common\Collections\Collection
*/
protected $groups;
}
Joining entity:
<?php
/**
* #Entity
* #Table(name="group_user")
*/
class GroupUser {
/**
* #Id
* #ManyToOne(targetEntity="User", inversedBy="groups")
* #JoinColumn(name="userId", referencedColumnName="id")
*/
protected $user;
/**
* #Id
* #ManyToOne(targetEntity="Group", inversedBy="users")
* #JoinColumn(name="groupId", referencedColumnName="id")
*/
protected $group;
/**
* #Column(type="integer")
*/
protected $relationship;
}
Related -
Same goal, slightly different approach, which consistently produced errors once the resulting collections were manipulated: http://www.doctrine-project.org/jira/browse/DDC-1323
Supports the approach, no technical details: Doctrine 2 join table + extra fields
I've found just two examples (see question) of entity definitions for this specific type of relationship, however no example code for how they're used. As such it was fairly unclear how fluid (or otherwise) the resulting setters & getters could be expected to be. Hopefully this code will help clear up the approach for anyone else making a similar attempt.
The ideal solution under the circumstances (thanks #doctrine # freenode) was to implement a custom repository - a more flexible & efficient place for creating & managing the association.
Example Custom Repository for Join-Table with Metadata Class - Solution accompanies code in original question
<?php
use Doctrine\ORM\EntityRepository;
class GroupUserRepository extends EntityRepository {
/**
* #param \User $user
* #param \Group $group
* #param integer $type One of the integer class constants defined by GroupUser
* #param string $role Optional string defining user's role in the group.
* #return \GroupUser
*/
public function addUserToGroup(User $user, Group $group, $relationship, $role = '') {
$groupUser = $this->findOneBy(array('user' => $user->getId(), 'group' => $group->getId()));
if(!$groupUser) {
$groupUser = new GroupUser();
$groupUser->setGroup($group);
$groupUser->setUser($user);
$groupUser->setRole($role);
$groupUser->setRelationship($relationship);
$this->_em->persist($groupUser);
}
return $groupUser;
}
/**
* #param \User $user
* #param \Group $group
* #return null
*/
public function removeUserFromGroup(User $user, Group $group) {
$groupUser = $this->findOneBy(array('user' => $user->getId(), 'group' => $group->getId()));
if($groupUser)
$this->_em->remove($groupUser);
}
}
Then, from the join-table class, modify the Entity meta-data accordingly to specify the custom repository.
<?php
/**
* #Entity(repositoryClass="\Path\To\GroupUserRepository")
*/
class GroupUser {
// ...
}
This causes the custom repository to yield in place of the default one, making a proxy method from the Entity class simple.
<?php
/**
* #Entity
*/
class Group {
/**
* #param \User $user
* #param integer $relationship One of the integer class constants defined by GroupUser
* #param string $role Optional string defining user's role in the group.
* #return \GroupUser
*/
public function addUser(User $user, $relationship, $role = '') {
return $this->_em->getRepository('GroupUser')
->addUserToGroup($user, $this, $relationship, $role);
}
}
And things are about as manageable as they were before.