I have just installed the doctrine extensions to use Sluggable.
I make this :
composer.json
"stof/doctrine-extensions-bundle": "1.2.*#dev"
AppKernel.php
new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),
app/config/config.yml
stof_doctrine_extensions:
orm:
default:
sluggable: true
Djoo\AppliBundle\Entity\Nomenclature.php
namespace Djoo\AppliBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\DBAL\Types\SmallIntType;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* Nomenclature
*
*
* #ORM\Table(name="app_nomenclature")
* #ORM\Entity
*/
class Nomenclature
{
.....
/**
* #var string
*
* #ORM\Column(name="titre", type="string", length=200, nullable=false)
*/
private $titre;
/**
* #var string
*
* #ORM\Column(name="finess", type="string", length=10, nullable=true)
*/
private $finess;
/**
* #Gedmo\Slug(fields={"titre","finess"},suffix=".html")
* #ORM\Column(length=128, unique=true,nullable=true)
*/
private $slug;
public function getSlug() {
return $this->slug;
}
public function setSlug($slug){
$this->slug = $slug;
return $this;
}
}
In my controller i make this to generate slug for old values in my datatable :
$filterBuilder = $this->get('doctrine.orm.entity_manager')>getRepository('DjooAppliBundle:Nomenclature')->createQueryBuilder('e')->orderBy('e.titre', 'asc');
$query = $filterBuilder->getQuery();
$nomenclatures = $query->getResult();
foreach($nomenclatures as $nomenclaturee){
$nomenclature->setSlug(null);
$this->get('doctrine.orm.entity_manager')->persist($nomenclature);
$this->get('doctrine.orm.entity_manager')->flush();
}
I have no error, but my old values are a null slug. I try to create a new element and i have a good slug. Have you and idea ?
Thanks
To change the slug you must change the related property. You can add a space at the end of $titre, save it, change it back and save it again. That will flush the slugs.
$uow = $em->getUnitOfWork();
$uow->propertyChanged($entity, 'slug', NULL, NULL);
$uow->scheduleForUpdate($entity);
$em->flush();
Why it didn't work for OP, but worked for others (eg. #gregor):
When creating slug, your first instinct is to create slug property with this column configuration:
..
#ORM\Column(unique=true, nullable=false)
private $slug;
..
When running app/console doctrine:schema:update and this will result in 2 sql statements:
ALTER TABLE ... ADD slug ... NOT NULL
CREATE UNIQUE INDEX...`.
By default column slug will be filled with value '' (empty string) which would make 2nd statement to fail with (Duplicate entry '') error. Now you have two choices:
Choice A: Ignore failure of the 2nd statement
If you ignore the error, and later try generating slugs manually using the documented method $entity->setSlug(null) everything would work. It would work because by using $entity->setSlug(null) you would let Doctrine know that propertyslug was changed (from '' to null) and this in turn would trigger internally $uow->propertyChanged() and $uow->scheduleForUpdate() (Thanks to #Sebastian Radu for his example). The Sluggable extension will notice this change as well and will regenerate the slug. Now as all the slugs are unique, next time you run app/console doc:schema:update it will succeed in creating index on slug and your schema will be fully in sync.
Choice B: Modify slug field to be nullable
After noticing error your instinct would be to mark slug field as nullable, so that index creation succeeds:
..
#ORM\Column(unique=true, nullable=true)
private $slug;
..
This would result in slug column having NULL as it's default value. Now as you try using documented $entity->setSlug(null) approach it won't work (just as OP has posted). This happens because when $entity->slug property is already NULL. Thus when you use $entity->setSlug(null) no changes are detected by Doctrine, and thus Sluggable regeneration behaviour is never triggered. In order to trigger the changes there were two answers:
hack with adding space to the slug source properties $entity -> setTitre($titre." "); (but this would result in extra space you would have to trim after)
approach by #Sebastian Radu, where he shows how to tell Doctrine directly that the field was changed (I personally prefer this one and wonder why it was unfairly downvoted)
Hope this helps you understand a bit better the inner workings of Doctrine and extensions.
The sluggable documentation states the following:
In case if you want the slug to regenerate itself based on sluggable
fields, set the slug to null.
<?php
$entity = $em->find('Entity\Something', $id);
$entity->setSlug(null);
$em->persist($entity);
$em->flush();
It does work for me.
Related
I have a OneToMany Unidirectional relationship between an "Employee" and "Status".
There is then also a ManyToMany biderectional relationship between "Employee" and "Documents".
When I have my a Document, i am trying to find all related employees ($Document->getEmployees()) and then "filter" (using ->matching(Criteria)) by the "Status"
I keep getting the below error:
2018-04-05T14:35:19+00:00 [error] Error thrown while running command "app:expiration-check". Message: "Notice: Undefined index: Status"
In DefaultQuoteStrategy.php line 39:
Notice: Undefined index: Status
Here is the Code i am using:
$Employees = $Document->getEmployees()->matching(
Criteria::create()
->andWhere(Criteria::expr()->eq('Status',$this->GlobalSettings->getApprovedEmployeeStatus()))
);
Interestingly enough, the exact same criteria works if i am using the Employee Repository
$Employees = $this->em->getRepository(Employee::class)->matching(
Criteria::create()
->andWhere(Criteria::expr()->eq('Status',$this->GlobalSettings->getApprovedEmployeeStatus()))
);
Matching static fields also works fine.
$Employees = $Document->getEmployees()->matching(
Criteria::create()
->andWhere(Criteria::expr()->eq('FirstName',"Keven"))
);
Here is the Status Column defintion
/**
* #ORM\ManyToOne(targetEntity="Entity\Accounts\EmployeeStatus")
* #ORM\JoinColumn(name="StatusId", referencedColumnName="id", nullable=false)
*/
private $Status;
Here is the Employees Defintion (on Document Entity)
/**
* #ORM\ManyToMany(targetEntity="Entity\Accounts\Employee", mappedBy="Documents")
*/
private $Employees;
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
$this->Employees = new \Doctrine\Common\Collections\ArrayCollection();
}
and Here is the getEmployees() (also on Document)
/**
* Get employees.
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getEmployees()
{
return $this->Employees;
}
To manage ManyToMany relations, doctrine uses Doctrine\ORM\Persisters\Collection\ManyToManyPersister class.
You can see it being used here
Unfortunately, currently in the latest release, v2.6.1, method loadCriteria of this class is lacking the feature to use relation fields. Only static fields are supported.
Looking at the master branch currently, this support has been added: Doctrine\ORM\Persisters\Collection\ManyToManyPersister as of today
but it is not part of a release yet. Also having a quick look at 2.7 branch it does not look it will be there.
I am not sure whether you could use the master branch with symfony `s doctrine bundle. I think it will be difficult to get this to work now.
What you could do, is initialize the ManyToMany collection $Document->getEmployees() and then use the matching function, which means that you load all employees and then filter, not lazy load as you would expect.
So do:
$employees = $Document->getEmployees();
$employees->initialize();
$employees->matching(
Criteria::create()
->andWhere(Criteria::expr()->eq('Status',$this->GlobalSettings->getApprovedEmployeeStatus()))
);
and put a note to change the code, when the new changes are released.
If you want to know how to set default value in symfony2, look here.
I can set it through both ways. For example, I can set it through the variable directly like this.
/**
* #var string $variable
*
* #ORM\Column(name="variable", type="string", nullable=true)
*/
private $variable = "default_value";
or i can use the options attribute
/**
* #var string $variable
*
* #ORM\Column(name="variable", type="string", nullable=true,options={"default" = "default_value"})
*/
private $variable = "default_value";
I want to know what is the difference b/w each of these two. More importantly what are the cases when the first way won't suffice , and options attribute has to be used.
From what i've come to know so far, setting the variable directly sets default value on a symfony2 level , and the options attribute sets it for doctrine. What difference does it make to set default value on ORM level, and when does it clash with symfony2 defaults? What happens if i only use one of the two.
You're right, the difference is that the value is being set on different levels. To elaborate, if you create a default value using:
/**
* #var string $variable
*
* #ORM\Column(name="variable", type="string", nullable=true)
*/
private $variable = "default_value";
When do you create a new object with the default value.
$a = new MyClass();
$a->getVariable(); // -> 'default_value'
So the actual default value is accessible immediately. If you use the second approach:
/**
* #var string $variable
*
* #ORM\Column(name="variable", type="string", nullable=true,options={"default" = "default_value"})
*/
private $variable;
This will cause that the schema in DB will contain default value for the column so the value will be accessible after you save the entity
$a = new MyClass();
$a->getVariable(); // -> null
$em->perist($a);
$em->flush();
$a->getVariable(); // -> 'default_value'
I would say this is the basic difference. It really depends of where you want to control your default values and when you want to have them.
In my opinion, in most of the cases the assignment in the entity itself is better, since if you want to change the value later in the system, all you need to do is update the value on the entity.
If you will be using the default in the database, then updating to a different value will need to update the annotation and then create a migration to alter the default value in the DB field
UPDATE (Migration question):
In case you use default value specified in Doctrine annotation and you're using Doctrine migrations bundle for the Symfony project and you will write your migration for this class manually (not by using php app/console doctrine:migrations:diff), you need to specify manually the default as an option. The annotation itself just tells how the column definition should look like.
If the default value is set only in PHP, i.e. private $variable = 'default_value'; migration bundle will not be affected by it at all. That means that if you run php app/console doctrine:migrations:diff the DEFAULT value for the field won't be filled in. Of course if you write the migration yourself, the default value in the database will be there only if you fill it automatically in.
To sum the migration budle functionality - the automatically generated migrations are affected only by default value in #ORM\Column annotation.
I work with Symfony2 and Doctrine and I have a question regarding entities.
In a performance worries, I'm wondering if it is possible to use an entity without going all the associations?
Currently, I have not found another way to create a model inheriting the class with associations and associations specify NULL in the class that inherits.
thank you in advance
OK, a little detail, it's for a API REST (JSON).
This is my class :
/**
* Offerequipment
*
* #ORM\Table(name="offer_equipment")
* #ORM\Entity(repositoryClass="Charlotte\OfferBundle\Repository\Offerequipment")
*/
class Offerequipment
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Charlotte\OfferBundle\Entity\Offer")
* #ORM\JoinColumn(name="offer_id", referencedColumnName="id")
*/
private $offer;
/**
* #ORM\ManyToOne(targetEntity="Charlotte\ProductBundle\Entity\Equipment")
* #ORM\JoinColumn(name="equipment_id", referencedColumnName="id")
*/
private $equipment;
/**
* #VirtualProperty
*
* #return String
*/
public function getExample()
{
return $something;
}
and with QueryBuilder method, i can't get my virtual properties or getters.
Thanks for your help :)
Look at Serialization.
By serialising your entities, you can choose to exclude or expose a property of an entity when you render it.
Look at the Symfony built-in Serializer and/or JMSSerializer.
Otherwise, you can use QueryBuilder and DQL to choose what fields you want to fetch in your queries.
Like this, you can make your own find method in the Repository of your entities.
// src/AcmeBundle/Repository/FooRepository
class FooRepository extends \Doctrine\ORM\EntityRepository
// ...
public function find($id) {
$queryBuilder = $this->createQueryBuilder('e')
->select('e.fieldA', 'e.fieldB') // selected fields
->where('e.id = :id') // where statement on 'id'
->setParameter('id', $id);
$query = $queryBuilder->getQuery();
$result = $query->getResult();
}
// ...
}
Don't forget define the Repository in the corresponding Entity.
/**
* Foo.
*
* #ORM\Entity(repositoryClass="AcmeBundle\Repository\FooRepository")
*/
class Foo
{
// ...
}
By default Doctrine will not automatically fetch all of the associations in your entities unless you specifically each association as EAGER or unless you are using a OneToOne association. So if you are looking to eliminate JOINs, you can just use Doctrine in its default state and it won't JOIN anything automatically.
However, you this will not alleviate all of your performance concerns. Say, for example, you are displaying a list of 50 products in your application on a single page and you want to show their possible discounts, where discounts are an association on your product entity. Doctrine will create 50 additional queries just to retrieve the discount data unless you explicitly join the discount entity in your query.
Essentially, the Symfony profiler will be your friend and show you when you should be joining entities on your query - don't just think that because you aren't joining associations automatically that your performance will always be better.
Finally, after many days, I've found the solution to select only one entity.
VirtualProperties are found :)
public function findAllByOffer($parameters)
{
$queryBuilder = $this->createQueryBuilder('oe');
$queryBuilder->select('oe, equipment');
$queryBuilder->join('oe.equipment', 'equipment');
$result = $queryBuilder->getQuery()->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true)->getResult();
return $result;
}
Suppose we have a field with ManyToMany relation as
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Users")
* #ORM\JoinTable(name="users_roles",
* joinColumns={#ORM\JoinColumn(name="User_Id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="Role_Id", referencedColumnName="id")})
*/
protected $userRole;
To remove one related element from table we can have this function in our entity:
/**
* Remove userRole
* #param \Acme\MyBundle\Entity\Users $user
*/
public function remvoveUserRole(\Acme\MyBundle\Entity\Users $user)
{
$this->userRole->removeElement($user);
}
The Question:
The ArrayCollection type has the function removeElement which is used to remove one element of the relationship. There is another function clear which in api says Clears the collection, removing all elements, therefore can I have a function like below in my entity to clear all the related elements so that by flushing it removes all?
/**
* Remove all user Roles
*/
public function remvoveAllUserRole()
{
$this->userRole->clear();
}
will it work for just ManyToMany related tables or it might work for ManyToOne, too?
Ţîgan Ion is right - removeElement/clear only removes those elements from memory.
However, I think you could achieve something as close depending on how did you configure cascade and orphanRemoval in your relationship.
$em = ...; // your EntityManager
$roles = $user->getRoles();
$roles->clear();
$user = $em->merge($user); // this is crucial
$em->flush();
In order for this to work, you need to configure User relationship to
cascade={"merge"} - this will make $em->merge() call propagate to roles.
orphanRemoval = true - since this is #ManyToMany, this will make EntityManager remove free-dangling roles.
Can't test this now, but as far as I can see it could work. I will try this out tomorrow and update the answer in need be.
Hope this helps...
Note: This logic works for ManyToMany relationship, but not for ManyToOne
I tested the way to delete all related roles for specific use (ManyToMany) and it worked. What you need is to define a function in your UserEntity as
/**
* Remove all user Roles
*/
public function remvoveAllUserRole()
{
$this->userRole->clear();
}
Then in your controller or anywhere else(if you need) you can call the function as below
$specificUser = $em->getRepository('MyBundle:Users')->findOneBy(array('username' => 'test user'));
if (!empty($specificUser)) {
$specificUser->removeAllUserRole();
$em->flush();
}
Then it will delete all related roles for the test user and we don't need to use the for loop and remove them one by one
if I'm not mistaken, this will not work, you have to delete the "role
s" from the arrayCollection directly from the database
$roles = $user->getRoles()
foreach $role from $roles
$em->remove($role);
$em->flush();
now you should get an empty collection
p.s: the best way is to test your ideas
The context is a Symfony2 project (2.0.23) using Doctrine2.
I have a Candidate entity with one to one relation Website. When creating a new candidate I set the Website entity like this:
Candidate.php:
<?php
use MyProject\Bundle\CoreBundle\Entity\Website;
public function initialize(Website $website)
{
$this->setWebsite($website);
The one to one relation is declared like this:
<?php
/**
* #var Website $website
*
* #ORM\OneToOne(targetEntity="Website")
* #ORM\JoinColumn(name="Website", referencedColumnName="Code")
*/
private $website;
Everything works fine locally. But on out testing and production servers, when creating a new candidate with a given Website, sometimes the associated entity is updated with default values just when persisting and flushing the main Candidate entity, here is the MySQL log:
/*!*/;
# at 11400207
#130628 9:26:32 server id 1 end_log_pos 11400399 Query thread_id=53611133 exec_time=0 error_code=0
SET TIMESTAMP=1372404392/*!*/;
UPDATE website SET Name = NULL, bEnabled = NULL ... WHERE Id = 2 AND Code = 2000
I persist with the method of the FOSUserBundle:
<?php
/**
* Updates a user.
*
* #param UserInterface $user
* #param Boolean $andFlush Whether to flush the changes (default true)
*/
public function updateUser(UserInterface $user, $andFlush = true)
{
$this->updateCanonicalFields($user);
$this->updatePassword($user);
$this->em->persist($user);
if ($andFlush) {
$this->em->flush();
}
}
I don't understand why. What is really weird is that only happens sometimes and it is quiet unpredictable.
Any suggestion or hint would be welcome... Thanks.
PS: The doctrine metadata cache was deactivated.
Edit1: Added persist, note that this is called through a FormHandler service.
Well it seems it was related to the main entity that had 2 #ORM\Id annotations. As it is an old database, it is not "Doctrine" friendly. There is a PK an auto-increment field, but the joins are made on a second column. So I removed the #ORM\Id annotation for the PK field.