It worked some time ago but now for some reason it produces exception. I have custom User entity and it extends FOS user:
namespace AppBundle\Entity;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
* #ORM\Table(name="fos_user")
*/
class User extends BaseUser
{
...
}
There is no setSalt() in my custom class. And as I can see in dumped SQL query other not custom fields (email_canonical, enabled, password, ...) are set properly. What else could I check?
UPDATE
I did composer update. Here is my composer.json
"require": {
"php": ">=5.5.9",
"symfony/symfony": "3.2.*",
"doctrine/orm": "^2.5",
"doctrine/doctrine-bundle": "^1.6",
"doctrine/doctrine-cache-bundle": "^1.2",
"symfony/swiftmailer-bundle": "^2.3",
"symfony/monolog-bundle": "^2.8",
"symfony/polyfill-apcu": "^1.0",
"sensio/distribution-bundle": "^5.0",
"sensio/framework-extra-bundle": "^3.0.2",
"incenteev/composer-parameter-handler": "^2.0",
"friendsofsymfony/user-bundle": "~2.0#dev",
"hwi/oauth-bundle": "^0.5.1",
"twig/extensions": "^1.4"
},
You must drop your schema and recreate it again. Your salt column should be allowed to be null, because when using the bcryp algorithm it is indeed null as the salt is directly included in the password (hash). Moreover here is the mapping declaration:
<field name="salt" column="salt" type="string" nullable="true" />
PS: And other advices, updates all bundle, clear the cache, the database and its data...
UPDATE:
Per dMedia, they changed the doctrine mapping in a recent update (Nov 2016)
https://github.com/FriendsOfSymfony/FOSUserBundle/commit/a9a08c2daf3db38697a8bd4b6e00f42c9a33dd79#diff-36e2e6fca6f6ce7118933033f9ce8bff
Related
I am using DataFixtures to populate my test db with data for unittests.
For entities, which are not using other entities I can set the primary key with setId. For entities, which are used by other entities I can set it, but it is ignored.
E.g. I am setting my users like this:
/** #var Gender $genderW */
$genderM = $this->getReference(GenderFixtures::TEST_GENDER_W);
$date = new DateTime('now');
/** #var User $user */
$user = new User();
$user
->setId(9)
->setFirstName('Hermione')
->setLastName('Granger')
->setEmail('test9#example.com')
->setGender($genderM)
->setPassword('odsf3_!45sr-f')
->setCreated($date);
$manager->persist($user);
$manager->flush();
$this->addReference(self::TEST_USER_REFERENCE_9, $user);
As you see I set the Id for Hermione, but if I do a var_dump in my tests I see that the user Id gets incremented by my number of users for each test I am running. (9, 18, 27....)
The result is that in my tests I have to fetch the users by the unique email address, which is possible, but very annoying:
$userRepo = $this->em->getRepository(User::class);
$this->user = $userRepo->findOneBy(['email' => 'test9#example.com']);
var_dump($this->user->getId());
Is there a possibility to change this, so I can get my users by Id?
config:
"require": {
"php": "7.2.*",
"ext-ctype": "*",
"ext-iconv": "*",
"cache/predis-adapter": "^1.0",
"doctrine/doctrine-bundle": "^1.6.10",
"doctrine/doctrine-migrations-bundle": "^1.3",
"doctrine/orm": "^2.5.11",
"eightpoints/guzzle-bundle": "~7.3.1",
"friendsofsymfony/rest-bundle": "^2.3",
"guzzlehttp/guzzle": "^6.3",
"jms/serializer-bundle": "^2.4",
"predis/predis": "^1.1",
"sensio/framework-extra-bundle": "^5.2",
"snc/redis-bundle": "3.x-dev",
"symfony/apache-pack": "^1.0",
"symfony/console": "^4.1",
"symfony/flex": "^1.0",
"symfony/framework-bundle": "^4.1",
"symfony/lts": "^4#dev",
"symfony/monolog-bundle": "^3.1",
"symfony/orm-pack": "^1.0",
"symfony/polyfill-apcu": "^1.5",
"symfony/security-bundle": "^4.0",
"symfony/swiftmailer-bundle": "^3.2",
"symfony/twig-bundle": "^4.1",
"symfony/validator": "^4.1",
"symfony/yaml": "^4.1"
},
"require-dev": {
"diablomedia/phpunit-pretty-printer": "2.0.*",
"doctrine/doctrine-fixtures-bundle": "^3.0",
"friendsofphp/php-cs-fixer": "*",
"phpmd/phpmd": "^2.6",
"sensiolabs/security-checker": "^4.1",
"squizlabs/php_codesniffer": "*",
"symfony/dotenv": "^4.1",
"symfony/maker-bundle": "^1.5",
"symfony/phpunit-bridge": "^4.1",
"symfony/var-dumper": "^4.1"
},
The correct way would be to create a setUp and tearDown Method in each class/for your Testsuite.
With these methods you can create/truncate the testdata in your database each time it is run.
According to the Phpunit documentation for Databasetesting the setup will clean up before the test is run
PHPUnit will execute a TRUNCATE against all the tables you specified to reset their status to empty.
With only a truncate, not resetting the autoincrement values.
To clean your database keys you should use the tearDown for the test to reset the keys
ALTER TABLE tablename AUTO_INCREMENT = 1
This will reset the autoincrement values after each test.
To achieve this only with symfony you could create a command which will do the following things:
Reset the testdatabase ./app/console doctrine:fixtures:load --purge-with-truncate found this command on SO
Create fixtures
Execute your testrun
You will have 1 command to run your tests only using symfony.
I have a complex/nested object created by automatic hydration from Zend\Form data. Now I want to save it with Doctrine 2. The best case would be just one persist(...) and one flush(...) call on the top level. But it doesn't work like this. So now I have following problem:
There are objects User and Order. The relationship is 1:n (so, 1 User has n Orders). The User exists already. When a User Joe tries to save more than one Order (e.g. its second order), an error occurs:
A new entity was found through the relationship '...\Order#user' that was not configured to cascade persist operations for entity: ...\User#000000003ba4559d000000005be8d831. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example #ManyToOne(..,cascade={"persist"}). If you cannot find out which entity causes the problem implement '...\User#__toString()' to get a clue.
Allright, I add cascade={"persist"} (though it doesn't make sense here, but anyway, just to try it out):
class Order
{
...
/**
* #var User
*
* #ORM\ManyToOne(targetEntity="User", cascade={"persist"})
*/
protected $user;
...
}
Now it works, if the given User doesn't exist: An Order and a User is created.
But if the User exists, an error occurs:
An exception occurred while executing 'INSERT INTO user (username, role, created, updated) VALUES (?, ?, ?, ?)' with params ["myusername", "member", null, null]:
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'myusername' for key 'username_UNIQUE'
How to handle the saving so, that the User only gets saved, if it doesn't exist yet?
The solution is to persist the User before saving the referencing entity. If it doesn't exist yet, it needs to be created (persisted and flushed) first:
$user = $this->entityManager->getRepository(User::class)->findOneBy(
['username' => $dataObject->getUser()->getUsername()]
);
if (! $user) {
$this->entityManager->persist($dataObject->getUser());
$this->entityManager->flush($dataObject->getUser());
$user = $this->entityManager->getRepository(User::class)->findOneBy(
['username' => $dataObject->getUser()->getUsername()]
);
}
$dataObject->setUser($user);
$this->entityManager->persist($dataObject);
$this->entityManager->flush();
And the cascade={"persist"} should not be used, since it actually doesn't make sense in this case.
EDIT
Or even easier:
$user = $this->entityManager->getRepository(User::class)->findOneBy(
['username' => $dataObject->getUser()->getUsername()]
);
if ($user) {
$dataObject->setUser($user);
} else {
$this->entityManager->persist($dataObject->getUser());
}
$this->entityManager->persist($dataObject);
$this->entityManager->flush();
I've got a Doctrine Entity defined that maps to a View in my database. All works fine, the Entity relations work fine as expected.
Problem now is that when running orm:schema-manager:update on the CLI a table gets created for this entity which is something I want to prevent. There already is a view for this Entity, no need to create a table for it.
Can I annotate the Entity so that a table won't be created while still keeping access to all Entity related functionality (associations, ...)?
Based on the original alswer of ChrisR inspired in Marco Pivetta's post I'm adding here the solution if you're using Symfony2:
Looks like Symfony2 doesn't use the original Doctrine command at:
\Doctrine\ORM\Tools\Console\Command\SchemaTool\UpdateCommand
Instead it uses the one in the bundle:
\Doctrine\Bundle\DoctrineBundle\Command\Proxy\UpdateSchemaDoctrineCommand
So basically that is the class that must be extended, ending up in having:
src/Acme/CoreBundle/Command/DoctrineUpdateCommand.php:
<?php
namespace App\Command;
use Doctrine\Bundle\DoctrineBundle\Command\Proxy\UpdateSchemaDoctrineCommand;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Tools\SchemaTool;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class DoctrineUpdateCommand extends UpdateSchemaDoctrineCommand
{
protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas, SymfonyStyle $ui): ?int
{
$ignoredEntities = [
'App\Entity\EntityToIgnore',
];
$metadatas = array_filter($metadatas, static function (ClassMetadata $classMetadata) use ($ignoredEntities) {
return !in_array($classMetadata->getName(), $ignoredEntities, true);
});
return parent::executeSchemaCommand($input, $output, $schemaTool, $metadatas, $ui);
}
}
Eventually it was fairly simple, I just had to subclass the \Doctrine\ORM\Tools\Console\Command\SchemaTool\UpdateCommand into my own CLI Command. In that subclass filter the $metadatas array that's being passed to executeSchemaCommand() and then pass it on to the parent function.
Just attach this new subclassed command to the ConsoleApplication you are using in your doctrine cli script and done!
Below is the extended command, in production you'll probably want to fetch the $ignoredEntities property from you config or something, this should put you on the way.
<?php
use Doctrine\ORM\Tools\Console\Command\SchemaTool\UpdateCommand;
use Doctrine\ORM\Tools\SchemaTool;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class My_Doctrine_Tools_UpdateCommand extends UpdateCommand
{
protected $name = 'orm:schema-tool:myupdate';
protected $ignoredEntities = array(
'Entity\Asset\Name'
);
protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas, SymfonyStyle $ui)
{
/** #var $metadata \Doctrine\ORM\Mapping\ClassMetadata */
$newMetadata = [];
foreach ($metadatas as $metadata) {
if (!in_array($metadata->getName(), $this->ignoredEntities)) {
$newMetadata[] = $metadata;
}
}
return parent::executeSchemaCommand($input, $output, $schemaTool, $newMetadata, $ui);
}
}
PS: credits go to Marco Pivetta for putting me on the right track. https://groups.google.com/forum/?fromgroups=#!topic/doctrine-user/rwWXZ7faPsA
Quite old one but there is also worth nothing solution using Doctrine2: postGenerateSchema event listener - for me it's better than overriding
Doctrine classes:
namespace App\Doctrine\Listener;
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
/**
* IgnoreTablesListener class
*/
class IgnoreTablesListener
{
private $ignoredTables = [
'table_name_to_ignore',
];
public function postGenerateSchema(GenerateSchemaEventArgs $args)
{
$schema = $args->getSchema();
$tableNames = $schema->getTableNames();
foreach ($tableNames as $tableName) {
if (in_array($tableName, $this->ignoredTables)) {
// remove table from schema
$schema->dropTable($tableName);
}
}
}
}
Also register listener:
# config/services.yaml
services:
ignore_tables_listener:
class: App\Doctrine\Listener\IgnoreTablesListener
tags:
- {name: doctrine.event_listener, event: postGenerateSchema }
No extra hooks is necessary.
In Doctrine 2.7.0 it was introduced the new SchemaIgnoreClasses entity manager config option that basically ignores the configured classes from any schema action.
To use it with Symfony we only need to add the schema_ignore_classes key in the Doctrine entity manager configuration like this:
doctrine:
dbal:
# your dbal configuration
orm:
default_entity_manager: default
entity_managers:
default:
connection: default
mappings:
Main:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/src/Entity/Main'
prefix: 'App\Entity\Main'
alias: Main
schema_ignore_classes:
- Reference\To\My\Class
- Reference\To\My\OtherClass
$schema->getTableNames() was not working (I don't know why).
So:
<?php
namespace AppBundle\EventListener;
use Doctrine\Bundle\DoctrineBundle\Command\Proxy\UpdateSchemaDoctrineCommand;
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
class IgnoreTablesListener extends UpdateSchemaDoctrineCommand
{
private $ignoredEntities = [
'YourBundle\Entity\EntityYouWantToIgnore',
];
/**
* Remove ignored tables /entities from Schema
*
* #param GenerateSchemaEventArgs $args
*/
public function postGenerateSchema(GenerateSchemaEventArgs $args)
{
$schema = $args->getSchema();
$em = $args->getEntityManager();
$ignoredTables = [];
foreach ($this->ignoredEntities as $entityName) {
$ignoredTables[] = $em->getClassMetadata($entityName)->getTableName();
}
foreach ($schema->getTables() as $table) {
if (in_array($table->getName(), $ignoredTables, true)) {
// remove table from schema
$schema->dropTable($table->getName());
}
}
}
}
And Register a service
# config/services.yaml
services:
ignore_tables_listener:
class: AppBundle\EventListener\IgnoreTablesListener
tags:
- {name: doctrine.event_listener, event: postGenerateSchema }
Worked fine! ;)
If problem is only with producing errors in db_view, when calling doctrine:schema:update command, why not simplest way:
remove # from #ORM\Entity annotation
execute doctrine:schema:update
add # to ORM\Entity annotation
;-)
Is there any way to get an entity ID before the persist/flush?
I mean:
$entity = new PointData();
$form = $this->createForm(new PointDataType(), $entity);
If I try $entity->getId() at this point, it returns nothing.
I can get it working by:
$em->persist($entity);
$em->flush();
(supposing $em = $this->getDoctrine()->getEntityManager();)
How can I achieve this?
If you want to know the ID of an entity before it's been persisted to the database, then you obviously can't use generated identifiers. You'll need to find some way to generate unique identifiers yourself (perhaps some kind of hash function can produce unique-enough values).
This is rarely a good idea, though, so you should be careful.
I would think very carefully about why I need to know the identifier before flush. Doctrine is quite good at letting you build up a big object graph, and persist/flush it all at once. It seems likely that you've got something ugly in your architecture that you're trying to work around. It might be a good idea to review that before going down the application-generated-id route.
You can use the #PostPersist annotation. A method annotated with that will be executed just before the flush terminate and the entity Id is available already.
https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/events.html
postPersist - The postPersist event occurs for an entity after the entity has been made persistent. It will be invoked after the database insert operations. Generated primary key values are available in the postPersist event.
<?php
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
*/
class PointData
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
...
/**
* #ORM\PostPersist
*/
public function onPostPersist()
{
// Put some simple logic here that required the auto-generated Id.
$this->doSomething($this->id);
}
...
}
you can use an auto generate ID to get a key like universally unique identifiers (UUID) or you can take the events of symfony:
postFlush - The postFlush event occurs at the end of a flush operation.
Doctrine best practices says,
You should avoid auto-generated identifiers. because:
Your DB operations will block each other
You are denying bulk inserts
You cannot make multi-request transactions
Your object is invalid until saved
Your object does not work without the DB
So you can use UUIDS instead
public function __construct() {
$this->id = Uuid::uuid4();
}
Also, Doctrine supports the UUID generation strategy since version 2.3.
Not sure why you need the ID before flushing, but, if you really need to persist the entity without saving to the database you can try using Transactions.
Try something like this:
$em->beginTransaction();
$em->persist($entity);
$em->flush();
$id = $entity->getId();
//do some stuff and save when ready
$em->commit();
$em = $this->getDoctrine()->getManager();
$entity = new PointData();
$em->persist($entity);
$entity->getId() <-- return <int>
$em->flush();
after persist you can get id
i tried to implement the file upload via doctrine/lifecycle callbacks as described here:
http://symfony.com/doc/current/cookbook/doctrine/file_uploads.html#using-lifecycle-callbacks
So far it works, but the PrePersist/PreUpdate Event is not fired, the function "preUpload" is not called.
Functions like "upload" and "removeUpload" triggered by other lifecycle events are called correctly.
Does anyone have an idea why the event is not fired or a solution for this problem?
Thanks
I have another solution to this problem:
My entity has a field "updatedAt" which is a timestamp of the last update. Since this field gets set anyway (by the timestampable extension of Gedmo) I just use this field to trick doctrine into believing that the entitiy was updated.
Before I persist the entity I set this field manually doing
if( $editForm['file']->getData() )
$entity->setUpdateAt(new \DateTime());
This way the entity gets persisted (because it has changed) and the preUpdate and postUpdate functions are called properly.
Of course this only works if your entity has a field that you can exploit like that.
You need to change tracking policies.
Full explanation.
there's a much simpler solution compared with changing tracking policies and other solutions:
in controller:
if ($form->isValid()) {
...
if ($form->get('file')->getData() != NULL) {//user have uploaded a new file
$file = $form->get('file')->getData();//get 'UploadedFile' object
$news->setPath($file->getClientOriginalName());//change field that holds file's path in db to a temporary value,i.e original file name uploaded by user
}
...
}
this way you have changed a persisted field (here it is path field), so PreUpdate() & PostUpdate() are triggered then you should change path field value to any thing you like (i.e timestamp) in PreUpdate() function so in the end correct value is persisted to DB.
A trick could be to modify the entity no matter what..on postLoad.
1 Create an updatedAt field.
/**
* Date/Time of the update
*
* #var \Datetime
* #ORM\Column(name="updated_at", type="datetime")
*/
private $updatedAt;
2 Create a postLoad() function that will modify your entity anyway:
/**
* #ORM\PostLoad()
*/
public function postLoad()
{
$this->updatedAt = new \DateTime();
}
3 Just update that field correctly on prePersist:
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function preUpload()
{
$this->updatedAt = new \DateTime();
//...update your picture
}
This is basically a slight variation of #philipphoffmann's answer:
What i do is that i modify an attribute before persisting to trigger the preUpdate event, then i undo this modification in the listener:
$entity->setToken($entity->getToken()."_tmp");
$em->flush();
In my listener:
public function preUpdate(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if ($entity instanceof MyEntity) {
$entity->setToken(str_replace('_tmp', '', $entity->getToken()));
//...
}
}
Another option is to display the database field where the filename is stored as a hidden input field and when the file upload input changes set that to empty so it ends up triggering doctrine's update events. So in the form builder you could have something like this:
->add('path', 'text', array('required' => false,'label' => 'Photo file name', 'attr' => array('class' => 'invisible')))
->add('file', 'file', array('label' => 'Photo', 'attr' => array('class' => 'uploader','data-target' => 'iddp_rorschachbundle_institutiontype_path')))
Path is a property managed by doctrine (equal to the field name in the db table) and file is the virtual property to handle uploads (not managed by doctrine). The css class simply sets the display to none. And then a simple js to change the value of the hidden input field
$('.uploader').change(function(){
var t = $(this).attr('data-target');
//clear input value
$("#"+t).val('');
});
For me, it worked good when I just manually called these methods in the controller.
Do you have checked your metadata cache driver option in your config.yml file?If it exists, just try to comment this line:
metadata_cache_driver: whateverTheStorage
Like this:
#metadata_cache_driver: whateverTheStorage