I am currently learning how to use Doctrine ORM with ZF2 and currently my goal is to retrieve data from a simple table join and display it to screen.
I have read the documents and it looks pretty simple to do.
These are my tables:
user
------------------------
|user_id | name | email |
--------------------------
| 1 | John | j#b.com |
--------------------------
| 2 | Bob | b#j.com |
--------------------------
user_role_linker
--------------------------
|user_id | role_id |
--------------------------
| 1 | administrator |
--------------------------
| 2 | staff |
--------------------------
What I want to achieve is a list to my view as follows:
ID Name Email Role Actions
--------------------------------------------------------
1 John j#b.com Administrator Edit
2 Bob b#j.com Staff Edit
--------------------------------------------------------
Paging goes here
----------------
This is what I currently have and it seems to work except I am not sure how to grab the joined table data:
User entity::
<?php
namespace Administration\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\ManyToMany;
use Doctrine\ORM\Mapping\JoinTable;
use Doctrine\ORM\Mapping\JoinColumn;
use Doctrine\Common\Collections\ArrayCollection;
/** #ORM\Entity */
class User {
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer",name="user_id")
*/
protected $user_id;
/** #ORM\Column(type="integer", name="parent_id") */
protected $parent_id;
/** #ORM\Column(type="string", name="name") */
protected $name;
/** #ORM\Column(type="string", name="email") */
protected $email;
//Setters and getters
public function getUserId() {
return $this->user_id;
}
public function setName($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
public function getEmail() {
return $this->email;
}
public function setEmail($email) {
$this->email = $email;
}
/**
* #ManyToMany(targetEntity="UserRoleLinker")
* #JoinTable(
* name="user_role_linker",
* joinColumns={
* #JoinColumn(
* name="user_id",
* referencedColumnName="id")
* },
* inverseJoinColumns={
* #JoinColumn(
* name="user_id",
* referencedColumnName="id",
* unique=true)
* })
*/
private $role_id;
public function __construct()
{
$this->role_id = new ArrayCollection();
}
/** #return Collection */
public function getRoleId()
{
return $this->role_id;
}
}
User role linker entity::
<?php
namespace Administration\Entity;
use Doctrine\ORM\Mapping as ORM;
/** #ORM\Entity */
class UserRoleLinker {
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer",name="user_id")
*/
protected $user_id;
/** #ORM\Column(type="string", name="role_id") */
protected $role_id;
/** #param User|null */
public function getRoleId() {
return $this->role_id;
}
}
My Administration controller::
public function usersAction() {
$em = $this->getServiceLocator()->get('Doctrine\ORM\EntityManager');
$userFunctions = new UserFunction($em);
$userArray = $userFunctions->getUsers();
$viewModel = new ViewModel(array('users' => $userArray));
return $viewModel;
}
This calls my UserFunctions class::
public function getUsers()
{
//This function returns the users
return $this->em->getRepository('Administration\Entity\User')->findAll();
}
And in my view I list the data like this:
<?php
foreach ($this->users AS $user) {
?>
<tbody>
<tr class="odd gradeX">
<td width="5%"><?php echo $user->getUserId(); ?></td>
<td><?php echo $user->getName(); ?></td>
<td><?php echo $user->getEmail(); ?></td>
<td class="center">*** HOW DO I SHOW THE ROLE ?? ***</td>
<td>Edit</td>
</tr>
<?php } ?>
How do I grab the role to show in the view?
You have to define the correct relationship between user and role in your entity definition.
I am not sure why you use a linker table here. Do you want to actually have a many to many relationship between user and roles (every user can have several roles?).
If not you can easily move the role id into the user table and then you define a ManyToOne relationship between user and role.
Your user table would then look like this:
-------------------------------------------
|user_id | name | email | role_id |
-------------------------------------------
| 1 | John | j#b.com | administrator |
-------------------------------------------
| 2 | Bob | b#j.com | staff |
-------------------------------------------
I would suggest to take a look at ManyToOne with user as the owning side. You can check how to properly define your unidirectional many to one relation inside your entity definition here in the Doctrine2 documentation
After that you can simply call $user->getRole(); in your view...
EDIT
Answer to fix a one to many using a join table:
This is also described in the doctrine documentation here...
You need three tables; a user table, a role table and a user-role-linker table
The user is an entity, the role is an entity the role-linker table is not an entity in your case. You should drop that entity, the linker table is only used for connecting the user and role in the database.
User table
---------------------------
|id | name | email |
---------------------------
| 1 | John | j#b.com |
---------------------------
| 2 | Bob | b#j.com |
---------------------------
Role table
-----------------
| id |
-----------------
| administrator |
-----------------
| staff |
-----------------
| guest |
-----------------
| another role |
-----------------
Linker table
--------------------------
|user_id | role_id |
--------------------------
| 1 | administrator |
--------------------------
| 2 | staff |
--------------------------
In your user entity:
/** ONE-TO-MANY UNIDIRECTIONAL, WITH JOIN TABLE ONLY WORK WITH MANY-TO-MANY ANNOTATION AND A UNIQUE CONSTRAINT
* #ORM\ManyToMany(targetEntity="Administration\Entity\Role")
* #ORM\JoinTable(name="user_role_linker",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="role_id", referencedColumnName="id", unique=true)}
* )
*/
protected $roles;
/**
* Get roles.
*
* #return ArrayCollection
*/
public function getRoles()
{
return $this->roles;
}
/**
* Add a role to the user.
*
* #param Role $role
*
* #return User
*/
public function addRole(Role $role)
{
$this->roles[] = $role;
return $this;
}
/**
* Remove a role from the user
*
* #param Role $role
*
* #return User
*/
public function removeRole(Role $role)
{
$this->roles->removeElement($role);
return $this;
}
Your role entity:
/**
* An example entity that represents a role.
*
* #ORM\Entity
* #ORM\Table(name="role")
* #property string $id
*/
class Role
{
/**
* #var string
* #ORM\Id
* #ORM\Column(type="string", length=255, unique=true, nullable=false)
*/
protected $id;
/**
* Get the id.
*
* #return string
*/
public function getId()
{
return $this->id;
}
}
I think this should help you solve it...
Maybe you should add the following to your application module:
public function doctrineValidate(MvcEvent $event){
$application = $event->getParam('application');
$serviceManager = $application->getServiceManager();
$entityManager = $serviceManager->get('doctrine.entitymanager.orm_default');
$validator = new SchemaValidator($entityManager);
$errors = $validator->validateMapping();
if (count($errors) > 0) {
// Lots of errors!
var_dump($errors);
}
}
And then in bootstrap:
$eventManager->attach('dispatch', array($this, 'doctrineValidate'));
in module:
Doctrine will help you by checking your entity definitions. It might tell you in advance that something is wrong in your entity definitions...
It's confusing, but as you defined in your entities, you could get the roles collection using User entity getRoleId's function. Then, for each UserRoleLinker entity you have to use, again, the getRoleId function, which will return the 'Administrator' or 'Staff' string. Loop Example:
$roles = $user->getRoleId();
foreach ( $roles as $role ) {
$roleId = $role->getRoleId();
}
I suggest you to do it another way. One entity should be User, with a property called roles. On the other side you could have the entity Role. The link between them should be a One-To-Many, Unidirectional with Join Table (which is the user_role_linker table).
As suggested by lluisaznar (although I need a many to 1 relationship since each user only has one role).
I am trying the following:
<?php
namespace Administration\Entity;
//use stuff
/** #ORM\Entity */
class User {
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer",name="user_id")
*/
//setters getters
/**
* #ManyToOne(targetEntity="Role")
* #JoinColumn(name="user_id", referencedColumnName="id")
*/
private $role;
public function __construct()
{
$this->role = new ArrayCollection();
}
/** #return Collection */
public function getRoleId()
{
return $this->role;
}
}
And the Role entity:
<?php
namespace Administration\Entity;
use Doctrine\ORM\Mapping as ORM;
/** #ORM\Entity */
class Role {
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer",name="user_id")
*/
protected $user_id;
/** #ORM\Column(type="string", name="role_id") */
protected $role;
/** #param User|null */
public function getRoleId() {
return $this->role;
}
}
When I run this I get the following notices:
Notice: Undefined index: id in trunk/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php on line 2611
and
Notice: Undefined index: user_id in trunk/vendor/doctrine/common/lib/Doctrine/Common/Proxy/AbstractProxyFactory.php on line 121
Also the role is not being displayed, when I dump the $user object under role I get this:
private 'role' =>
object(DoctrineORMModule\Proxy\__CG__\Administration\Entity\Role)[609]
public '__initializer__' =>
object(Closure)[595]
public '__cloner__' =>
object(Closure)[596]
public '__isInitialized__' => boolean false
protected 'user_id' => null
protected 'role' => null
Related
I want to use Product and Price classes with one-to-many references. This is my Product class.
/**
* #ApiResource
*
* #Document
*/
class Product
{
/**
* #ODM\Id(strategy="INCREMENT", type="integer")
*/
private $id;
/**
* #ODM\Field(type="string")
* #Assert\NotBlank
*/
public $name;
/**
* #ODM\ReferenceMany(targetDocument=Price::class, mappedBy="product", cascade={"all"}, storeAs="id")
*/
public $prices ;
public function __construct()
{
$this->prices = new ArrayCollection();
}
//getter and setter of id...
}
This is Price class
/**
* #ApiResource
*
* #ODM\Document
*/
class Price
{
/**
* #ODM\Id(strategy="INCREMENT", type="integer")
*/
private $id;
/**
* #ODM\Field(type="float")
* #Assert\NotBlank
* #Assert\Range(min=0, minMessage="The price must be superior to 0.")
* #Assert\Type(type="float")
*/
public $price;
/**
* #Assert\Type(type="integer")
*/
private $discount;
/**
* #ODM\ReferenceOne(targetDocument=Product::class, inversedBy="prices", storeAs="id")
*/
public $product;
I can put and get Price with id=1 but when I want to put Product in swagger ui I use this reference.
{
"name": "productno1",
"prices": [
"/api/prices/1/"
]
}
It gives 201. I can check the db it is stored. The name of the product is productno1 but price section is empty. Can anyone help about what is wrong?
I changed mappedby tag of prices variable in Product class with inversedby, then problem solved.
I'm newbie in symfony and I would like some advices for creating a query builder statement. Is pretty simple, the idea is to get the data from a third entity.
I have the entities:
/**
* Entity
* #ORM\Entity(repositoryClass="LoungepassBundle\Entity\LoungepassRepository")
* #ORM\Table(name="loungepass_loungepass")
*
*/
class Loungepass{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
// ...
/**
* #ORM\ManyToOne(targetEntity="\AppBundle\Entity\Agency", inversedBy="loungepasses")
* #ORM\JoinColumn(name="agency_id", referencedColumnName="iata8", nullable=false)
*/
private $agency;
//...
}
Agency Entity
class Agency {
/**
* #ORM\Id
* #ORM\Column(type="string", length=8, name="iata8")
*/
protected $id;
//...
/**
* #ORM\OneToMany(targetEntity="LoungepassBundle\Entity\Loungepass", mappedBy="agency")
*/
protected $loungepasses;
/**
* #var Market
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Market")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="sales_country", referencedColumnName="id")
* })
*/
private $market;
//...
}
I would like to do a query, like this:
public function queryBySlugInContext($slug, $user) {
$query = $this->createQueryBuilder("l")
->where('l.slug = :slug')
->setParameter('slug', $slug);
if(count($user->getAgencies()) > 0){
$query->andWhere(':agencyIds MEMBER OF l.agencies')
->setParameter('agencyIds',$user->getAgencies());
}
Able to access the information from the market attribute located in the agency entity.So basically, retrieve information from the Market Entity.
any suggestions?
Thank you in advance
I modified a bit your code but the mind is here
When you have to access to retrieve data from other tables just keep in mind to use differents mysql join accessible with doctrine
public function queryBySlugInContext($slug, $user) {
$query = $this->createQueryBuilder("l")
->where('l.slug = :slug')
->setParameter('slug', $slug);
if(0 !== count($user->getAgencies())) {
$query = $query->innerJoin('l.agency', 'a')
->andWhere('a.id IN(:userAgenciesIds)')
->setParameter('userAgenciesIds', array_map(function(Agency $agency) {
return $agency->getId();
}, $user->getAgencies());
}
You should take a time to read some content on internet like for example the doctrine query builder documentation
is it possible to add a choice list in configureformfields with choices values mapped from the database instead of configuring it manually like this :
->add('testfield', 'choice', array('choices' => array(
'1' => 'choice 1',
'2' => 'choice 2',)))
if the entity is correctly mapped then you can just use:
->add('testfield')
and Sonata admin will do the job.
Let's say you have a Product class linked to a Category class:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Product
*
* #ORM\Table(name="product")
*
*/
class Product
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="products")
*/
protected $category;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set category
*
* #param Category $category
*
* #return Product
*/
public function setCategory(Category $category = null)
{
$this->category = $category;
return $this;
}
/**
* Get category
*
* #return Category
*/
public function getCategory()
{
return $this->category;
}
}
Simply using:
->add('category')
will provide a select form field with all the categories.
You can also use SONATA_TYPE_MODEL if you want something more advanced:
<?php
// src/AppBundle/Admin/ProductAdmin.php
class ProductAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
$imageFieldOptions = array(); // see available options below
$formMapper
->add('category', 'sonata_type_model', $imageFieldOptions)
;
}
}
The documentation is on this page: Form Types
Hope this helps!
I have entity and proper mapping with them. PFB
/**
* User
*
* #ORM\Entity
* #ORM\Table(name="user")
*/
class User{
/**
* #ORM\OneToOne(targetEntity="Language", mappedBy="languageCode2")
**/
private $languageObj;
function __construct(){
$this->languageObj = new ArrayCollection();
}
}
/**
* Language
*
* #ORM\Entity
* #ORM\Table(name="language")
*/
class Language
{
/**
* #ORM\OneToOne(targetEntity="User", inversedBy="languageObj")
* #ORM\JoinColumn(name="language_id", referencedColumnName="language_id")
*/
private $languageCode2;
public function __construct()
{
$this->languageCode2 = new ArrayCollection();
}
}
when I print it It gives below
[languageObj:User:private] => Common\User\Entity\Language Object
(
[languageId:Language:private] => 1
[languageCode:Language:private] => en
[languageName:Language:private] => English
[languageCode2:Language:private] => User Object
what issue I am facing is, i am not able to fetch languageCode from Language object
To do that I have written below in __construct() of User class
$this->lc = $this->languageObj->first();
AND
$this->lc = $this->languageObj->getCode();
getCode() is method in Language class, which return $this->languageCode variable
I've a problem figuring out how to configure the mapping of my classes with Doctrine2.
Let say I've these tables:
Address table
---------------------
- id
- civic_no
- road
- state
- country
PersonnalAddress table
---------------------
- id
- base_address_id
- type
- is_primary
BusinessAddress table
---------------------
- id
- base_address_id
- business_name
- shipping_phone
- is_primary
And thoses PHP objects:
class Address{}
class BusinessAddress extends Address{}
class PersonalAddress extends Address{}
Considering the following requirements:
An address can exist by itself (the Address class is not abstract)
A personalAddress and a businessAddress can have the very same address data
If I delete or edit the address, it has an impact on all the business or personal address that are inherited from it.
I don't want any data duplication in the database (this is a requirement of the 2nd normal form)
Proxy methods mean code duplication, I prefer not to have any.
Magic methods are not good in term of testability, I prefer not to have any.
To better illustrate the problem, I expect the data in the data base to look like:
Address table:
id | civic_no | road | state | country
1 123 test qc ca
PersonnalAddress table:
id | base_address_id | type | is_primary
1 1 A 0
2 1 B 1
BusinessAddress table:
id | base_address_id | business_name | shipping_phone | is_primary
1 1 chic choc 1231234 1
What would be the best strategy to implement a solution that match theses requirements ?
Ok this is a bit of a long one but I think it covers all your bases, if you have any questions then feel free to ask.
This comes with a caveat that I don't know if you can do Many-To-One on a MappedSuperclass. If that isn't possible then you may be able to use Class Table Inheritance instead. Give it a try and tell us if it works.
Keep in mind I pushed this code out pretty quickly, it is untested so it may not be correct but hopefully you'll get the idea of how it works.
Here we go!
Interface to make it easier to say "what is an address" without making abstract classes then overriding methods and causing the "bad design" feeling ;)
interface Address {
function getCivicNo();
function getRoad();
function getState();
function getCountry();
}
Abstract Entity which you don't really need but if you dont use it you need to duplicate the ID code.
/**
* #ORM\MappedSuperclass
*/
abstract class AbstractEntity {
/**
* Entity ID column.
*
* #var integer
*
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
private $id;
public function getId() {
return $id;
}
}
BasicAddress which you can store alone or have linked to a "ComplexAddress"
/**
* #ORM\Entity
*/
class BasicAddress extends AbstractEntity implements Address {
/** #ORM\Column() */
private $road;
public function getRoad() {
return $this->road;
}
// etc etc
}
"ComplexAddress" is just here to let you re-use the code for delegating calls to a basic address.
/**
* #ORM\MappedSuperclass
*/
abstract class ComplexAddress extends AbstractEntity implements Address {
/** #ORM\Many-To-One(targetEntity="BasicAddress")
private $basicAddress;
public function __construct(BasicAddress $basicAddress) {
$this->basicAddress = $basicAddress;
}
public function getRoad() {
return $this->basicAddress->getRoad();
}
// other methods for implementing "Address" just delegate to BasicAddress
}
PublicAddress
/**
* #ORM\Entity()
*/
class PersonalAddress extends ComplexAddress {
/** #ORM\Column(type="boolean") */
private $isPrimary;
public function isPrimary() {
return $isPrimary;
}
// other personal address methods here
}
BusinessAddress
/**
* #ORM\Entity()
*/
class BusinessAddress extends ComplexAddress {
/** #ORM\Column() */
private $businessName;
public function getBusinessName() {
return $this->businessName;
}
// other business address methods here
}
Edit: Just noticed I forgot to put cascade parameters for deletion, you might need to handle this directly though - when a BasicAddress is deleted also delete the other addresses that use it.
Instead of extending the Address class, you'll have to create a OneToMany relationship on the Address and a ManyToOne relationship on the PersonalAddress and BusinessAddress. Something like this:
<?php
// ...
use Doctrine\Common\Collections\ArrayCollection;
// ...
class Address
{
// ...
/**
* #ORM\OneToMany(targetEntity="PersonalAddress", mappedBy="address")
*/
private $personalAddresses;
/**
* #ORM\OneToMany(targetEntity="BusinessAddress", mappedBy="address")
*/
private $businessAddresses;
public function __construct()
{
$this->personalAddresses = new ArrayCollection();
$this->businessAddresses = new ArrayCollection();
}
// ...
}
And, for the child classes:
<?php
// ...
// ...
class PersonalAddress
{
// ...
/**
* #ORM\ManyToOne(targetEntity="Address", inversedBy="personalAddresses")
* #ORM\JoinColumn(name="base_address_id", referencedColumnName="id")
*/
private $address;
// ...
}