animals:
| id | name |
|----|------|
| 1 | cat |
| 2 | dog |
| 3 | frog |
category:
| id | name |
|----|--------|
| 1 | green |
| 2 | blue |
| 3 | orange |
animals_category:
| animals_id | category_id |
|------------|-------------|
| 1 | 1 |
| 2 | 1 |
| 2 | 2 |
What I want to do is get the categories for dog:
green, blue
This is my approach:
Controller:
$id = '2';
$result = $this->getDoctrine()->getRepository('Animals:Category')->findByIdJoinedToCategory(['id'=>$id]);
Animals Repository:
public function findByIdJoinedToCategory()
{
$query = $this->getEntityManager()
->createQuery(
'SELECT a, b FROM Animals:Category a
JOIN a.category b');
try {
return $query->getResult();
} catch (\Doctrine\ORM\NoResultException $e) {
return null;
}
}
But I get an error message:
Unknown Entity namespace alias 'Animals'.
entity Animals:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity(repositoryClass="App\Repository\AnimalsRepository")
*/
class Animals
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
/**
* #ORM\ManyToMany(targetEntity="Category")
* #ORM\JoinColumn(name="category", referencedColumnName="id")
*/
private $category;
public function getId(): ?int
{
return $this->id;
}
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
public function getCategory()
{
return $this->category;
}
public function setCategory($category): self
{
$this->category = $category;
return $this;
}
public function addCategory(Category $category): self
{
$this->category[] = $category;
return $this;
}
public function __construct()
{
$this->category = new ArrayCollection();
}
}
There's no Animals:Category entity. You have entities Animals and Category.
The correct answer depends if you're using Symfony 3 or 4, because Symfony 3 uses entity aliases (namespacing with : notation which you're trying ot use), while Symfony 4 prefers full qualified namespace (\App\Entity\Animals).
So, first mistake is in line where you're trying to get repository:
getRepository('Animals:Category')
And the second in findByIdJoinedToCategory() in DQL query :
'SELECT a, b FROM Animals:Category a
JOIN a.category b'
Now solutions:
Symfony 3
Since it looks you don't have any bundles (I guess it's Symfony 4 but whatever), you don't have any entity namespace alias, so you should simply use its name.
getRepository('Animals')
Now, I assume, that with a you want to reference Animals entity/table, so it should be
'SELECT a, b FROM Animals a
JOIN a.category b'
Symfony 4
If you use Symfony 4, then use should use entity FQNS as entity name (App\Entity\Animals).
So it would be
getRepository('\App\Entity\Animals')
or
getRepository(\App\Entity\Animals::class)
to get repository. The second one is better, because it will be easier to refactor when needed (IDE will be able to find usage of class).
And in query it would be
'SELECT a, b FROM App\Entity\Animals a
JOIN a.category b'
or if you would like to avoid using hardcoded string class names:
'SELECT a, b FROM ' . \App\Entity\Animals:class . ' a
JOIN a.category b'
Related
In my application I have two columns employable_id && employable_type in almost every table, which are used for storing the information about the user who has created the record.
Something like this:
subscribers: products:
------------------------------------ ------------------------------------
id | employable_id | employable_type id | employable_id | employable_type
------------------------------------ ------------------------------------
1 | 1 | App\Company 1 | 1 | App\Company
2 | 1 | App\Company 2 | 1 | App\Employee
3 | 1 | App\Employee 3 | 3 | App\Employee
4 | 1 | App\Employee 4 | 8 | App\Employee and more...
Subscriber.php
class Subscriber extends Model
{
/**
* Polymorphic relations
*/
public function employable()
{
return $this->morphTo();
}
}
I have created an accessor to combine polymorphic relationship's columns, which is working fine.
class Subscriber extends Model
{
/**
* Polymorphic relations
*/
public function employable()
{
return $this->morphTo();
}
/**
* Accessor
*/
public function getAddedByAttribute()
{
return $this->employable->name . ' [ ' . $this->employable->designation . ' ]';
}
}
class Product extends Model
{
/**
* Polymorphic relations
*/
public function employable()
{
return $this->morphTo();
}
/**
* Accessor
*/
public function getAddedByAttribute()
{
return $this->employable->name . ' [ ' . $this->employable->designation . ' ]';
}
}
I am trying to make the getAddedByAttribute method global instead of adding in every model class.
How can I do that..?
you can make a trait and use it in every model need this function
I have two entities Modules and Orders where One order have Many modules and I'm wondering how to fetch an array collection of modules persisted as follow:
Table: Orders
id | modules | user_id | ... | created_at |
----------------------------------------------------
1 | [2,6,5] | 12 | ... | 2018-07-28 00:00:00 |
----------------------------------------------------
As you can see my modules are persisted as array. So after that how can I make Doctrine (with Symfony) to get my modules
I think you need a ManyToOne relationShip ... as I know, we never store an array in database.
in your example order can have many modules and module can have just one order ...
in this case order called owning side and module called invers side ...
and module keep id of order ...
look at this example
Table: Orders
id | user_id | ... | created_at |
----------------------------------------------------
1 | 12 | ... | 2018-07-28 00:00:00 |
----------------------------------------------------
Table: Modules
id | order_id | ... | created_at |
----------------------------------------------------
1 | 1 | ... | 2018-07-28 00:00:00 |
----------------------------------------------------
2 | 1 | ... | 2018-07-29 00:00:00 |
----------------------------------------------------
you must write your code like this...
Order Class
class Order implements OrderInterface
{
/**
* #var Collection
*
* #ORM\OneToMany(targetEntity="Module", mappedBy="order", cascade={"persist"})
*/
protected $modules;
/**
* Don't forget initial your collection property
* Order constructor.
*/
public function __construct()
{
$this->modules = new ArrayCollection();
}
/**
* #return Collection
*/
public function getModules(): Collection
{
return $this->modules;
}
/**
* #param ModuleInterface $module
*/
public function addModule(ModuleInterface $module): void
{
if ($this->getModules()->contains($module)) {
return;
} else {
$this->getModules()->add($module);
$module->setOrder($this);
}
}
/**
* #param ModuleInterface $module
*/
public function removeModule(ModuleInterface $module): void
{
if (!$this->getModules()->contains($module)) {
return;
} else {
$this->getModules()->removeElement($module);
$module->removeOrder($this);
}
}
}
Module Class
class Module implements ModuleInterface
{
/**
* #var OrderInterface
*
* #ORM\OneToMany(targetEntity="Order", mappedBy="modules", cascade={"persist"})
*/
protected $order;
/**
* #param OrderInterface $order
*/
public function setOrder(OrderInterface $order)
{
$this->order = order;
}
public function getOrder(): OrderInterface
{
return $this->order;
}
}
when you persist order object by doctrine... doctrine handle this and create items
I have a self-referencing entity Product:
<?php
/** #Entity #Table(name="products") **/
class Product
{
/** #Id #Column(type="integer") #GeneratedValue **/
protected $id;
/** #Column(type="string", nullable=true) **/
protected $name;
/**
* #ManyToMany(targetEntity="Product", mappedBy="connectedBy", cascade={"all"})
*/
protected $connectedWith;
/**
* #ManyToMany(targetEntity="Product", inversedBy="connectedWith", cascade={"all"})
* #JoinTable(name="connection",
* joinColumns={#JoinColumn(name="product_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="connected_product_id", referencedColumnName="id")}
* )
*/
protected $connectedBy;
public function __construct()
{
$this->connectedWith = new \Doctrine\Common\Collections\ArrayCollection();
$this->connectedBy = new \Doctrine\Common\Collections\ArrayCollection();
}
public function getConnected()
{
return $this->connectedWith;
}
public function addConnection(Product $product)
{
$this->connectedWith->add($product);
$product->connectedBy->add($this);
}
public function removeConnection(Product $product)
{
$this->connectedBy->removeElement($product);
$this->connectedWith->removeElement($product);
}
}
Next I created two products (IDs 1 and 2) and a connection between the both products:
mysql> select * from products;
+----+------+
| id | name |
+----+------+
| 1 | NULL |
| 2 | NULL |
+----+------+
2 rows in set (0.00 sec)
mysql> select * from connection;
+------------+----------------------+
| product_id | connected_product_id |
+------------+----------------------+
| 2 | 1 |
+------------+----------------------+
1 row in set (0.01 sec)
Now I want to remove the connection with this code:
$product1 = $entityManager->find('Product', 1);
$product2 = $entityManager->find('Product', 2);
$product1->removeConnection($product2);
$entityManager->persist($product1);
$entityManager->flush();
$product3 = $entityManager->find('Product', 1);
print count($product3->getConnected()) . "\n";
As expected, the code prints 0 as its result. But when I look into the database, the connection entry still exists. What could be the cause any how could this be fixed?
I've already tried to $entityManager->persist($product2) but to no avail.
I researched a little more and found the solution myself:
My function removeConnection() has a bug: I removed the product from both lists, connectedBy and connectedWith, which is wrong. Instead, I should do it like in addConnection():
$this->connectedWith->removeElement($product);
$product->connectedBy->removeElement($this);
I've implemented a many to many ralation between two yii2 models:
slider, images, sliders_images where sliders_images is the junction table.
Each model extend a basic model generated by Gii, so when i need i can overwrite the base model without lose personal method.
Slider.php
...
public function getImages(){
return $this->hasMany(Images::className(), ['id' => 'image_id'])
->viaTable('sliders_images', ['slider_id' => 'id']);
}
...
Images.php
...
public function getSlider(){
return $this->hasMany(Slider::className(), ['slider_id' => 'id'])
->viaTable('sliders_images', ['image_id' => 'id']);
}
...
SlidersImages.php
...
/**
* #return \yii\db\ActiveQuery
*/
public function getImage()
{
return $this->hasOne(Images::className(), ['id' => 'image_id']);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getSlider()
{
return $this->hasOne(Slider::className(), ['id' => 'slider_id']);
}
...
When I use the function link() to popolate the junction table on create a slider all work fine but the problem occurs when i try to get images from slider ActiveRecord object (Yii2 documentation):
public function actionView($id)
{
$slider = $this->findModel($id);
return $this->render('view', [
'model' => $slider,
'images' => $slider->images
]);
}
protected function findModel($id)
{
if (($model = Slider::findOne($id)) !== null) {
return $model;
} else {
throw new NotFoundHttpException('The requested page does not exist.');
}
}
If i debug $images variable in the view this is null and not contain the related images.
How i can set the models for obtain the right access at the relations?
Edit:
when i try to access at slidersImage to get the rows of juncyion table:$slider->sliderImage work fine, miss the access at images row.
slider table
id | nome | descrizione | active
-------------------------------------------
28 | adfjkhbfvòja | JAFNHÒDF | 1
sliders_images table
slider_id | image_id | display_order|
--------------------------------------
28 | 16 | 3 |
--------------------------------------
28 | 17 | 5 |
images table
id | date | url |
------------------------------------
16 | 2016-06-21 16:21:04 | img/url |
------------------------------------
17 | 2016-06-21 16:22:37 | img/url |
Edit2:
The database sequence from debugger:
1 11:27:02.666 0.7 ms SHOW SHOW FULL COLUMNS FROM `admin`
/var/www/html/yii_advance/backend/models/Admin.php (65)
-------------------------------------------------------------------------
2 11:27:02.668 0.6 ms SHOW SHOW FULL COLUMNS FROM `slider`
/var/www/html/yii_advance/common/modules/sliders/controllers/SliderController.php (173)
/var/www/html/yii_advance/common/modules/sliders/controllers/SliderController.php (79)
--------------------------------------------------------------------------
3 11:27:02.665 0.6 ms SELECT SELECT * FROM `admin` WHERE (`id`=2) AND (`status`=10)
/var/www/html/yii_advance/backend/models/Admin.php (65)
[+] Explain
--------------------------------------------------------------------------
4 11:27:02.667 0.5 ms SELECT SELECT
kcu.constraint_name,
kcu.column_name,
kcu.referenced_table_name,
kcu.referenced_column_name
FROM information_schema.referential_constraints AS rc
JOIN information_schema.key_column_usage AS kcu ON
(
kcu.constraint_catalog = rc.constraint_catalog OR
(kcu.constraint_catalog IS NULL AND rc.constraint_catalog IS NULL)
) AND
kcu.constraint_schema = rc.constraint_schema AND
kcu.constraint_name = rc.constraint_name
WHERE rc.constraint_schema = database() AND kcu.table_schema = database()
AND rc.table_name = 'admin' AND kcu.table_name = 'admin'
/var/www/html/yii_advance/backend/models/Admin.php (65)
[+] Explain
--------------------------------------------------------------------------
5 11:27:02.669 0.5 ms SELECT SELECT
kcu.constraint_name,
kcu.column_name,
kcu.referenced_table_name,
kcu.referenced_column_name
FROM information_schema.referential_constraints AS rc
JOIN information_schema.key_column_usage AS kcu ON
(
kcu.constraint_catalog = rc.constraint_catalog OR
(kcu.constraint_catalog IS NULL AND rc.constraint_catalog IS NULL)
) AND
kcu.constraint_schema = rc.constraint_schema AND
kcu.constraint_name = rc.constraint_name
WHERE rc.constraint_schema = database() AND kcu.table_schema = database()
AND rc.table_name = 'slider' AND kcu.table_name = 'slider'
/var/www/html/yii_advance/common/modules/sliders/controllers/SliderController.php (173)
/var/www/html/yii_advance/common/modules/sliders/controllers/SliderController.php (79)
[+] Explain
--------------------------------------------------------------------------
6 11:27:02.669 0.4 ms SELECT SELECT * FROM `slider` WHERE `id`='28'
/var/www/html/yii_advance/common/modules/sliders/controllers/SliderController.php (173)
/var/www/html/yii_advance/common/modules/sliders/controllers/SliderController.php (79)
This public $images; property should be renamed because it coincides with the name of relation getImages()
class Slider extends Sl
{
const SCENARIO_CREATE = 'create';
const SCENARIO_VIEW = 'view';
const SCENARIO_UPDATE = 'update';
public $images; // This should be renamed
...
I would like to know if exist a way to add fields on the fly to any entity on Symfony2. I'm searching on the big internet and I didn't find anything. When I said "a way", I mean if exist a Doctrine Extension with that behavior, a bundle that implement it, design pattern, etc.
My idea is something similar to Translatable behavior of Doctrine Extensions. Supouse I have a Address entity, so I would like to add some attributes on the fly like street, number, intersections, and others but at the begining I didn't know what fields could exist.
I'm thinking something as 2 entities: Address and AddressFieldValues. Address will have specifics attributes like id, foreing keys of relationships with others classess and will be used to inject the dynamic attributes (a collections of field-values). AddressFieldValue will have the reals fields-values of Address, with the following attributes: id, address_id, field_name, field_value.
So, entity Address could be like this:
/**
* Address
*
* #ORM\Entity(repositoryClass="AddressRepository")
* #ORM\Table(name="address")
*/
class Address
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(
* targetEntity="AddressFieldValues",
* mappedBy="object",
* cascade={"persist", "remove"}
* )
*/
private $field_value;
public function __construct()
{
$this->field_value = new ArrayCollection();
}
public function getFieldValue()
{
return $this->field_value;
}
public function addFieldValue(AddressFieldValues $fv)
{
if (!$this->field_value->contains($fv)) {
$this->field_value[] = $fv;
$fv->setObject($this);
}
}
public function getId()
{
return $this->id;
}
}
and AddressFieldValues entity could be like this:
/**
* #ORM\Entity
* #ORM\Table(name="address_field_values",
* uniqueConstraints={#ORM\UniqueConstraint(name="lookup_unique_idx", columns={
* "object_id", "field"
* })}
* )
*/
class AddressFieldValues
{
/**
* #var integer $id
*
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue
*/
protected $id;
/**
* #var string $field
*
* #ORM\Column(type="string", length=32)
*/
protected $field;
/**
* #ORM\ManyToOne(targetEntity="Address", inversedBy="field_value")
* #ORM\JoinColumn(name="object_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $object;
/**
* #var string $content
*
* #ORM\Column(type="text", nullable=true)
*/
protected $content;
/**
* Convenient constructor
*
* #param string $field
* #param string $value
*/
public function __construct($field, $value)
{
$this->setField($field);
$this->setContent($value);
}
/**
* Get id
*
* #return integer $id
*/
public function getId()
{
return $this->id;
}
/**
* Set field
*
* #param string $field
*/
public function setField($field)
{
$this->field = $field;
return $this;
}
/**
* Get field
*
* #return string $field
*/
public function getField()
{
return $this->field;
}
/**
* Set object related
*
* #param string $object
*/
public function setObject($object)
{
$this->object = $object;
return $this;
}
/**
* Get related object
*
* #return object $object
*/
public function getObject()
{
return $this->object;
}
/**
* Set content
*
* #param string $content
*/
public function setContent($content)
{
$this->content = $content;
return $this;
}
/**
* Get content
*
* #return string $content
*/
public function getContent()
{
return $this->content;
}
}
So, if I have the following values on table: address_field_values
id | object | field | content
1 | 1 | street | 1st Ave
2 | 1 | number | 12345
3 | 1 | intersections | 2sd Ave and 4th Ave
4 | 2 | street | 1st Ave
5 | 2 | number | 12347
6 | 2 | intersections | 2sd Ave and 4th Ave
7 | 3 | street | 1st Ave
8 | 3 | number | 12349
9 | 3 | intersections | 2sd Ave and 4th Ave
For now address table only have the following values:
| id |
| 1 |
| 2 |
| 3 |
I could like to inject those fields-values to a Address object on the fly, to do something like this:
// if I need get de Address with id = 2
$addressRepository = $em->getRepository('Address');
$address = $addressRepository->find(2);
sprintf('The address is: "%s", #"%s" between "%s".', $address->getStreet(), $address->getNumber(), $address->getIntersections());
// then it should show: The address is 1st Ave, #12347 between 2sd Ave and 4th Ave.
//
// or if I need add a new Address, do something like this:
$address = new Address();
$address->setStreet('1st Ave');
$address->setNumber('12351');
$address->setIntersections('2sd Ave and 4th Ave');
$em->persist($address);
$em->flush();
then it save the address and address_field_values, and the tables have the following values:
// address
| id |
| 1 |
| 2 |
| 3 |
| 4 |
// address_field_values
id | object | field | content
1 | 1 | street | 1st Ave
2 | 1 | number | 12345
3 | 1 | intersections | 2sd Ave and 4th Ave
4 | 2 | street | 1st Ave
5 | 2 | number | 12347
6 | 2 | intersections | 2sd Ave and 4th Ave
7 | 3 | street | 1st Ave
8 | 3 | number | 12349
9 | 3 | intersections | 2sd Ave and 4th Ave
10 | 4 | street | 1st Ave
11 | 4 | number | 12351
12 | 4 | intersections | 2sd Ave and 4th Ave
So, any ideas how can I do that?
Remember, I have as requirement in my bussiness logic that I didn't know what fields could have a Address at beginig so I need to inject the fields on the fly. I use Address as example but this behavior can be used for any entity.
Thanks in advance
I think that your request is similar to a collection in a form (Doctrine2 documentation).
In the documentation, a collection of Tags entities with name property) is linked to a Task entity. In your case, the entity AddressFieldValue will have the field and content properties and the collection of AddressFieldValue entities will be added to Address entity.
So, by using this documentation and replacing Task by Address and Tag by AddressFieldValue it should works.