It is throwing an error
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'complete' cannot be null
How do I "tell" the Entity Manager not to consider the fruit object since it is throwing an error?
I have an object like:
class Fruit {
/**
* #Id #Column(type="integer")
* #GeneratedValue(strategy="AUTO")
*/
private $id;
/** #Column(type="string", length=10, nullable=true) */
private $name;
/** #Column(type="boolean") */
private $complete;
/* Omitted to make it simple */
}
And a method like:
public function uploadAction(){
try{
/* Omitted to make it simple */
while ($data = fgetcsv($fic, 1024, $delimiter)) {
try {
$fruit = $fruitManager->createFruit($data);
$outcome_fruit = $fruit->id;
} catch (\Exception $e) {
$outcome_details[] = $e->getMessage();
}
}
fclose($fic);
#unlink($file_path);
$csv_data = $this->view->io->build_csv_file($outcome_details);
$csv_import->output_csv = base64_encode(gzencode($csv_data));
$this->em->persist($csv_import);
// error here
$this->em->flush();
//
} catch (\Exception $e) {
print $e->getMessage();
exit;
}
}
My fruitManager
class FruitManager{
public function createFruit($name){
$fruit = new \Entities\Fruit;
$fruit->name = $name;
throw new Exception("Exception.");
$fruit->complete = 1;
$this->em->persist($fruit);
$this->em->flush();
}
}
You have few options:
update your entity allowing null values using ORM to allow null values for this field ie /** #Column(type="boolean", nullable=true) */
update your entity with default value using ORM ie /** #ORM\Column(type="boolean", nullable=false, options={"default" : false}) */
assign default value in the entity constructor
assign default value using private $complete = false; like Freelancer said
I'd prefer one of the first 2 options depending on logic needs.
Thanks.
try by setting private $complete = false; or true as you want.
Related
I have one entity Photo and another PhotoNote ( 0 to 10 )
A photo can have note but can’t too
When I request a photo entity I get the following error
Entity of type 'App\Entity\PhotoNote' for IDs idPhoto(1737) was not found
Using this in my controller
$photo = $photoRepository->findOneBy(['idPhoto' => $idPhoto]);
// check if there is a note
$note = (null !== $photo->getPhotoNote() ? $photo->getPhotoNote()->getNotePhoto() : 0);
// Also tried following
//$note = (null !== $photo->getPhotoNote()->getNotePhoto() ? $photo->getPhotoNote()->getNotePhoto() : null);
/**
* --> this throws the error : $photo->getPhotoNote()->getNotePhoto()
*/
And here is the dumping of $photo->getPhotoNote() in App\Entity\Photo :
Photo.php on line 443:
Proxies\__CG__\App\Entity\PhotoNote {#5710 ▼
+__isInitialized__: false
-idPhoto: 1737
-notePhoto: null
…2
}
Actually $photo->getPhotoNote() is not null, and photoNote is populated with the photoId. When using $photo->getPhotoNote()->getNotePhoto() doctrine generates the query to get the associated note, but that photo doesn’t have a note. Note is not mandatory.
What I want is ‘getPhotoNote’ returns null or even 0 but seems that one to one relation requires an existing id.
How to say to doctrine returns null ?
Class Photo {
/**
* #var int
*
* #ORM\Column(name="id_photo", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $idPhoto.
// some fields
/**
* #ORM\OneToOne(targetEntity=“App\Entity\PhotoNote”)
* #ORM\JoinColumn(name="id_photo", referencedColumnName="id_photo", nullable=true)
*/
private $photoNote;
public function setPhotoNote(PhotoNote $photoNote = null)
{
$this->photoNote = $photoNote;
return $this;
}
public function getPhotoNote()
{
return $this->photoNote;
}
}
Class PhotoNote {
/**
* #ORM\Id()
* #ORM\Column(name="id_photo", type="integer")
*/
private $idPhoto;
/**
* #ORM\Column(name="note_photo", type="smallint")
*/
private $notePhoto;
public function getNotePhoto(): ?int
{
return $this->notePhoto;
}
public function setNotePhoto(int $notePhoto): self
{
$this->notePhoto = $notePhoto;
return $this;
}
}
Generated query :
SELECT
t0.id_photo AS id_photo_1,
t0.note_photo AS note_photo_2
FROM
photo_note t0
WHERE
t0.id_photo = 1737;
For now the only way that I found is to not use a relationship between these two entities.
I get the photo’s note using the associated repository with something like
$note = $photoNoteRepo->findOneBy([
'idPhoto' => $idPhoto
]);
if ( empty($note))
$note = null;
I think it’s should be better with relation but I’m stuck with this issue.
This is a working workaround but it’s not answering the issue.
Maybe someone here will give me a way to use the relationship.
EDIT
I found a way based on this
public function getPhotoNote()
{
if ($this->photoNote instanceof \Doctrine\ORM\Proxy\Proxy) {
try {
$this->photoNote->__load();
} catch (\Doctrine\ORM\EntityNotFoundException $e) {
return null;
}
}
return $this->photoNote;
}
I have read something about doctrine's events especially postLoad event which seems cleaner but I did not implement it with success yet
suppose I have the entity:
class UserEntity
{
/**
* #var integer
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
* #ORM\Column(type="string")
*/
private $name;
/**
* #var string
* #ORM\Column(type="string")
*/
private $email;
// and contruct, getters and setters..
}
and in the respective service:
class UserService extends BaseService
{
public function update($id, $data)
{
try {
$user= $this->fetch($id);
if (! $user instanceof UserEntity) {
// throw respective exception;
}
$user->setName($data['name']);
$user->setEmail($data['email']);
$this->entityManager->flush($user);
return $user;
} catch (Exception $e) {
throw $e;
}
}
}
if there is a user such that:
{
id: 1,
name: jhon,
email: jhon#domain.com
}
and the data provided to the service is:
$id = 1;
$data = [
'name' => jhon,
'email => jhon#domain,com
]
then, what is the best way of avoid the innecesary query to database in these cases?
since it is unnecessary to invoke the flush method.
or Doctrine internally is responsible for not doing the query?
According to the current docs:
The flush operation applies to a managed entity with the following semantics:
The entity itself is synchronized to the database using a SQL UPDATE
statement, only if at least one persistent field has changed.
No SQL
updates are executed if the entity did not change.
When entities become managed (according to one of their ORMInvalidArgumentExceptions):
An entity is managed if its fetched from the database or registered as new through EntityManager#persist
So yeah, Doctrine will make the decision for you.
I have two entities, for products and translations:
class ProductEntity
{
/**
* #Id
* #var string
* #Column(type="string", length=3)
*/
protected $code;
/**
* #OneToMany(targetEntity="ProductTranslationEntity", mappedBy="product")
*/
private $translations;
public function __construct()
{
$this->translations = new ArrayCollection();
}
/.../ getters and setters
public function addTranslation(ProductTranslationEntity $productTranslation)
{
$this->translations->add($productTranslation);
}
public function clearTranslations()
{
$this->translations->clear();
}
}
.
class ProductTranslationEntity
{
/**
* #ManyToOne(targetEntity="ProductEntity", inversedBy="translations")
* #JoinColumn(name="product_code", referencedColumnName="code", onDelete="CASCADE")
* #Id
*/
private $product;
/**
* #var string
* #Column(type="string", name="language_code", length=5)
* #Id
*/
protected $languageCode;
/**
* #var string
* #Column(type="string", name="product_name", length=128)
*/
protected $productName;
/.../ getters and setters
}
I like to replace all translations with new ones, from array like that:
['en' => ['name' => 'name_en'], 'de' => ['name' => 'name_de']];
Because in this array I have set of all supported languages the bast way I can see is to remove all existing translations and put new ones:
$product // Existing product entity
$product->clearTranslations();
$this->entityManager->flush($product);
foreach ($translations as $code => $translation) {
$t = new ProductTranslationEntity();
$t->setProduct($product);
$t->setLanguageCode($code);
$t->setProductName($translation['name']);
$this->entityManager->persist($t);
$product->addTranslation($t);
}
$this->entityManager->flush($product);
This solution doesn't work because after first $this->entityManager->flush($product); there are still translations in database so i get error about duplicates.
What have I done wrong in my solution? Or maybe there is another way to solve this kind of issue?
As Doctrine documentation refers:
Changes made only to the inverse side of an association are ignored.
Make sure to update both sides of a bidirectional association (or at
least the owning side, from Doctrine's point of view).
So to properly clear the translations of a product you should change the clearTranslations() function inside Product entity to:
public function clearTranslations()
{
foreach ($this->translations as $translation) {
$translation->setProduct(null);
}
$this->translations->clear();
}
so that you also update the owning side of the association before removal.
This maybe a bit overload but still it does not use extra requests to database:
$current_translations = $product->getTranslations();
foreach ($translations as $code => $translation) {
$translation_found = false;
foreach ($current_translations as $current_translation) {
if ($current_translation->getLanguageCode() === $code) {
// you've found the translation - replace value
$current_translation->setProductName($translation['name']);
$translation_found = true;
break;
}
}
if (!$translation_found) {
// translation with such code not found - add a new one
$t = new ProductTranslationEntity();
$t->setProduct($product);
$t->setLanguageCode($code);
$t->setProductName($translation['name']);
$this->entityManager->persist($t);
$product->addTranslation($t);
}
}
$this->entityManager->persist($product);
Use orphanRemoval=true:
/**
* #OneToMany(targetEntity="ProductTranslationEntity", mappedBy="product", orphanRemoval=true)
*/
private $translations;
I've made a simple symfony2 console script which is supposed to convert data from old model to the new one.
Here's what it looks like:
class ConvertScreenshotsCommand extends Command
{
[...]
protected function execute(InputInterface $input, OutputInterface $output)
{
$em = $this->getContainer()->get('doctrine')->getManager();
$output->writeln('<info>Conversion started on ' . date(DATE_RSS) . "</info>");
$output->writeln('Getting all reviews...');
$reviews = $em->getRepository('ACCommonBundle:Review')->findAll(); // Putting all Review entities into an array
$output->writeln('<info>Got ' . count($reviews) . ' reviews.</info>');
foreach ($reviews as $review) {
$output->writeln("<info>Screenshots for " . $review->getTitle() . "</info>");
if ($review->getLegacyScreenshots()) {
foreach ($review->getLegacyScreenshots() as $filename) { // fn returns array of strings
$output->writeln("Found " . $filename);
$screenshot = new ReviewScreenshot(); // new object
$screenshot->setReview($review); // review is object
$screenshot->setFilename($filename); // filename is string
$em->persist($screenshot);
$em->flush(); // this is where it dies
$output->writeln("Successfully added to the database.");
}
} else $output->writeln("No legacy screenshots found.");
}
$output->writeln('<info>Conversion ended on ' . date(DATE_RSS) . "</info>");
}
}
The script breaks on $em->flush(), with the following error:
[ErrorException]
Warning: spl_object_hash() expects parameter 1 to be object, string given in
/[...]/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php line 1324
Obviously I'm doing something wrong, but can't figure out what it is. Thanks in advance!
** Update **
Review Entity mapping:
class Review
{
[...]
/**
* #ORM\OneToMany(targetEntity="ReviewScreenshot", mappedBy="review")
*/
protected $screenshots;
/**
* Won't be stored in the DB
* #deprecated
*/
private $legacyScreenshots;
/**
* New method to get screenshots, currently calls old method for the sake of compatibility
* #return array Screenshot paths
*/
public function getScreenshots()
{
// return $this->getLegacyScreenshots(); // Old method
return $this->screenshots; // New method
}
/**
* Get Screenshot paths
* #return array Screenshot paths
* #deprecated
*/
public function getLegacyScreenshots()
{
$dir=$this->getUploadRootDir();
if (file_exists($dir)) {
$fileList = scandir($dir);
$this->screenshots = array();
foreach ($fileList as $fileName)
{
preg_match("/(screenshot-\d+.*)/", $fileName, $matches);
if ($matches)
$this->screenshots[]=$matches[1];
}
return $this->screenshots;
}
else return null;
}
ReviewScreenshot mapping:
class ReviewScreenshot
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $filename
*
* #ORM\Column(name="filename", type="string", length=255)
*/
private $filename;
/**
* #ORM\ManyToOne(targetEntity="Review", inversedBy="screenshots")
* #ORM\JoinColumn(name="review_id", referencedColumnName="id")
*/
protected $review;
/**
* #var integer $priority
*
* #ORM\Column(name="priority", type="integer", nullable=true)
*/
protected $priority;
/**
* #var string $description
*
* #ORM\Column(name="description", type="string", nullable=true)
*/
protected $description;
/**
* #Assert\File(maxSize="2097152")
*/
public $screenshot_file;
protected $webPath;
UnitOfWork.php
/**
* Gets the state of an entity with regard to the current unit of work.
*
* #param object $entity
* #param integer $assume The state to assume if the state is not yet known (not MANAGED or REMOVED).
* This parameter can be set to improve performance of entity state detection
* by potentially avoiding a database lookup if the distinction between NEW and DETACHED
* is either known or does not matter for the caller of the method.
* #return int The entity state.
*/
public function getEntityState($entity, $assume = null)
{
$oid = spl_object_hash($entity); // <-- Line 1324
if (isset($this->entityStates[$oid])) {
return $this->entityStates[$oid];
}
if ($assume !== null) {
return $assume;
}
// State can only be NEW or DETACHED, because MANAGED/REMOVED states are known.
// Note that you can not remember the NEW or DETACHED state in _entityStates since
// the UoW does not hold references to such objects and the object hash can be reused.
// More generally because the state may "change" between NEW/DETACHED without the UoW being aware of it.
$class = $this->em->getClassMetadata(get_class($entity));
$id = $class->getIdentifierValues($entity);
if ( ! $id) {
return self::STATE_NEW;
}
switch (true) {
case ($class->isIdentifierNatural());
// Check for a version field, if available, to avoid a db lookup.
if ($class->isVersioned) {
return ($class->getFieldValue($entity, $class->versionField))
? self::STATE_DETACHED
: self::STATE_NEW;
}
// Last try before db lookup: check the identity map.
if ($this->tryGetById($id, $class->rootEntityName)) {
return self::STATE_DETACHED;
}
// db lookup
if ($this->getEntityPersister($class->name)->exists($entity)) {
return self::STATE_DETACHED;
}
return self::STATE_NEW;
case ( ! $class->idGenerator->isPostInsertGenerator()):
// if we have a pre insert generator we can't be sure that having an id
// really means that the entity exists. We have to verify this through
// the last resort: a db lookup
// Last try before db lookup: check the identity map.
if ($this->tryGetById($id, $class->rootEntityName)) {
return self::STATE_DETACHED;
}
// db lookup
if ($this->getEntityPersister($class->name)->exists($entity)) {
return self::STATE_DETACHED;
}
return self::STATE_NEW;
default:
return self::STATE_DETACHED;
}
}
I think the problem lies within Review::$screenshots:
You map it as a OneToMany association, so the value should be a Collection of ReviewScreenshot entities. But the method Review::getLegacyScreenshots() will change it into an array of strings.
You're probably using the change-tracking policy DEFERRED_IMPLICIT (which is the default). So when the property Review::$screenshots changes, Doctrine will try to persist that change, encounters strings where it expects entities, so throws the exception.
A Doctrine 2 Entity with a composite key:
/**
* #Entity
*/
class Test
{
/**
* #Id
* #Column (type="integer", length=11, name="id")
*
*/
protected $id = null;
/**
* #Id
* #Column (type="integer", length=11, name="idtwo")
*
*/
protected $idtwo = null;
public function setIdTwo($id)
{
$this->idtwo = $id;
}
public function setId($id)
{
$this->id = $id;
}
}
Saving the Entity
$test = new Test();
$test->setId(1);
$test->setIdTwo(1);
$em->persist($test);
DB Table:
CREATE TABLE `Bella_Test` (
`id` int(11) NOT NULL,
`idtwo` int(11) NOT NULL,
PRIMARY KEY (`id`,`idtwo`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Expected result: a row is added to the db table with two id fields, both with a value of 1.
Actual result: No row is added to the db table. No exception is thrown.
Question: What is going on?
You can use a try catch block to see what happens
try{
$em->flush(); //don't forget flush after persisting an object
}
catch(Exception $e){
echo 'Flush Operation Failed: '.$e->getMessage();
}
Other assumption, in my opinion, your entity table name and DB table name may not match each other. I think it's not a bad idea to give a try
/**
* #Entity
* #Table(name="Bella_Test")
*/
.
.
.