I updated this entity adding some fields.
Now I'd like to regenerate getters and setters but whe I execute php bin/console make:entity --regenerate App I have no results; my entites are listet but it says "no change". I'v tried event with --overwrite option.
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\ProfessionalRepository")
*/
class Professional
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $nom;
/**
* #ORM\Column(type="boolean")
*/
private $actiu;
/**
* #ORM\ManyToOne(targetEntity="Especialitat")
* #ORM\JoinColumn(referencedColumnName="id")
*/
private $especialitat;
public function getId(): ?int
{
return $this->id;
}
public function getNom(): ?string
{
return $this->nom;
}
public function setNom(string $nom): self
{
$this->nom = $nom;
return $this;
}
public function getActiu(): ?bool
{
return $this->actiu;
}
public function setActiu(bool $actiu): self
{
$this->actiu = $actiu;
return $this;
}
}
I'm using symfony 4.3.8
In previous versions I did it executring 'php bin/console doctrine:genetrate:entities App', I'm not sure if I can use this command in symfony 4, anyway it neither doesn't work.
I'm don't know what else to try...
I have solved it clearing the cache after each change.
php bin/console cache:clear
Related
Here is the problem:
Two entities Books and Auteurs. A book has only one auteur, an auteur can have multiple books.
When the base is populated if I create a book with an existing auteur in the Auteurs table, this one creates (wrongly) a duplicate in the table.
The presence of the cascade = "persist" annotation is at the origin of this duplicate. If I delete this annotation I get the error:
{"error": {"code": 500, "message": "Internal Server Error", "exception": [{"message": "A new entity was found through the relationship 'App \ Entity \ Books # auteur' .....
How to deal with this question?
My code:
Controller:
<?php
namespace App\Controller;
use App\Entity\Books;
use FOS\RestBundle\Controller\Annotations as Rest;
use FOS\RestBundle\Controller\FOSRestController;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
class EcritureController extends FOSRestController
{
/**
* #Rest\Post(
* path = "/creer-book")
* #Rest\View(StatusCode = 201)
* #ParamConverter("book", converter="fos_rest.request_body")
*/
public function creerBook(Books $book)
{
$em->persist($book);
$em->flush();
return $book;
}
}
Entity Books:
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\BooksRepository")
*/
class Books
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $titre;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Books", inversedBy="Books", cascade={"persist"})
* #ORM\JoinColumn(nullable=false)
*/
private $auteur;
public function getId(): ?int
{
return $this->id;
}
public function getTitre(): ?string
{
return $this->Titre;
}
public function setTitre(string $titre): self
{
$this->titre = $titre;
return $this;
}
public function getAuteur(): ?Auteurs
{
return $this->auteur;
}
public function setAuteur(?Auteurs $auteur): self
{
$this->auteur = $auteur;
return $this;
}
}
Entity Auteurs:
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\AuteursRepository")
*/
class Auteurs
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=10)
*/
private $nom;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Books", mappedBy="auteur")
*/
private $books;
public function __construct()
{
$this->books = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getNom(): ?string
{
return $this->nom;
}
public function setNom(string $nom): self
{
$this->nom = $nom;
return $this;
}
/**
* #return Collection|Books[]
*/
public function getBooks(): Collection
{
return $this->books;
}
public function addBook(Books $book): self
{
if (!$this->books->contains($book)) {
$this->books[] = $book;
$book->setBook($this);
}
return $this;
}
public function removeBook(Books $book): self
{
if ($this->books->contains($book)) {
$this->sujets->removeElement($book);
// set the owning side to null (unless already changed)
if ($book->getAuteur() === $this) {
$book->setAuteur(null);
}
}
return $this;
}
}
The error was to "persist" the "Many" side. It's necessary persist the "One" side, as follows:
$auteurId = $book->getAuteur()->getId();
$auteur= $em->getRepository('App:Auteurs')->find($auteurId);
$response = new Response();
if ($auteur) {
$auteur->addBook($book);
$em->persist($auteur);
$em->flush();
$response->setContent('Chain of your choice', Response::HTTP_OK);
} else {
$response->setContent('Auteur selected doesn\'t exist', Response::HTTP_NOT_ACCEPTABLE);
}
return $response;
I have problem with my test. I learn how to write phpunit test and how i can mock object, services etc.. I have this method on my ProductService:
<?php
namespace App\Service;
use App\Entity\Product;
use App\Repository\ProductRepository;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\ORMException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class ProductService
{
/**
* #var ProductRepository
*/
private $productRepository;
/**
* #var EntityManager
*/
private $entityManager;
/**
* #var ValidatorInterface
*/
private $validator;
/**
* ProductService constructor.
* #param ProductRepository $productRepository
* #param EntityManagerInterface $entityManager
* #param ValidatorInterface $validator
*/
public function __construct(ProductRepository $productRepository, EntityManagerInterface $entityManager, ValidatorInterface $validator)
{
$this->productRepository = $productRepository;
$this->entityManager = $entityManager;
$this->validator = $validator;
}
/**
* #param $productData
* #return Product|string
*/
public function createProduct($productData)
{
$name = $productData['name'];
$quantity = $productData['quantity'];
$sku = $productData['sku'];
$product = new Product();
$product->setName($name);
$product->setQuantity($quantity);
$product->setProductSerial($sku);
$errors = $this->validator->validate($product);
if (count($errors) > 0) {
$errorsString = (string)$errors;
return $errorsString;
}
try {
$this->entityManager->persist($product);
$this->entityManager->flush();
return $product;
} catch (\Exception $ex) {
return $ex->getMessage();
}
}
}
and i write this test:
<?php
namespace App\Tests\Service;
use App\Entity\Product;
use App\Repository\ProductRepository;
use App\Service\ProductService;
use Doctrine\Common\Persistence\ObjectRepository;
use PHPUnit\Framework\TestCase;
class ProductServiceTest extends TestCase
{
/**
* Create product test
*/
public function testCreateProduct()
{
$product = new Product();
$product->setName('tester');
$product->setQuantity(2);
$product->setProductSerial('Examplecode');
$productService = $this->createMock(ProductService::class);
$productService->method('createProduct')->will($this->returnSelf());
$this->assertSame($productService, $productService->createProduct($product));
}
}
When i run phpunit test, then i always have success but my database is empty. How can I be sure that the test works correctly? What is worth fixing and what is not? I wanted to make the launch of tests result in, for example, adding records to the test database, but I have no idea how to do it and how to properly mock it. I using phpunit + Symfony 4.
I used to write tests, but those that asked the endpoint API, and here I want to test services and repositories without endpoints.
I would like to learn how to test and mock websites, repositories, various classes etc.
When i apply answer then i have:
PHPUnit 7.5.17 by Sebastian Bergmann and contributors.
Testing Project Test Suite
?[31;1mE?[0m 1 / 1 (100%)
Time: 542 ms, Memory: 10.00 MB
There was 1 error:
1) App\Tests\Service\ProductServiceTest::testCreateProduct
Doctrine\Common\Persistence\Mapping\MappingException: The class 'App\Repository\ProductRepository' was not found in the chain configured namespaces App\Entity, Gesdinet\JWTRefreshTokenBundle\Entity
D:\warehouse-management-api\vendor\doctrine\persistence\lib\Doctrine\Common\Persistence\Mapping\MappingException.php:22
D:\warehouse-management-api\vendor\doctrine\persistence\lib\Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain.php:87
D:\warehouse-management-api\vendor\doctrine\orm\lib\Doctrine\ORM\Mapping\ClassMetadataFactory.php:151
D:\warehouse-management-api\vendor\doctrine\persistence\lib\Doctrine\Common\Persistence\Mapping\AbstractClassMetadataFactory.php:304
D:\warehouse-management-api\vendor\doctrine\orm\lib\Doctrine\ORM\Mapping\ClassMetadataFactory.php:78
D:\warehouse-management-api\vendor\doctrine\persistence\lib\Doctrine\Common\Persistence\Mapping\AbstractClassMetadataFactory.php:183
D:\warehouse-management-api\vendor\doctrine\orm\lib\Doctrine\ORM\EntityManager.php:283
D:\warehouse-management-api\vendor\doctrine\doctrine-bundle\Repository\ContainerRepositoryFactory.php:44
D:\warehouse-management-api\vendor\doctrine\orm\lib\Doctrine\ORM\EntityManager.php:713
D:\warehouse-management-api\vendor\doctrine\persistence\lib\Doctrine\Common\Persistence\AbstractManagerRegistry.php:215
D:\warehouse-management-api\tests\Service\ProductServiceTest.php:28
?[37;41mERRORS!?[0m
?[37;41mTests: 1?[0m?[37;41m, Assertions: 0?[0m?[37;41m, Errors: 1?[0m?[37;41m.?[0m
My Product entity
<?php
namespace App\Entity;
use DateTime;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity(repositoryClass="App\Repository\ProductRepository")
*/
class Product
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
* #Assert\NotBlank()
*/
private $name;
/**
* #ORM\Column(type="integer")
* #Assert\NotBlank()
*/
private $quantity;
/**
* #Gedmo\Mapping\Annotation\Timestampable(on="create")
* #ORM\Column(type="datetime")
*/
private $createdAt;
/**
* #Gedmo\Mapping\Annotation\Timestampable(on="update")
* #ORM\Column(type="datetime")
*/
private $updatedAt;
/**
* #ORM\Column(type="string")
* #Assert\NotBlank()
*/
private $product_serial;
public function __construct() {
$this->setCreatedAt(new \DateTime());
$this->setUpdatedAt();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getQuantity(): ?int
{
return $this->quantity;
}
public function setQuantity(int $quantity): self
{
$this->quantity = $quantity;
return $this;
}
public function getCreatedAt(): ?\DateTimeInterface
{
return $this->createdAt;
}
public function setCreatedAt(\DateTimeInterface $createdAt): self
{
$this->createdAt = $createdAt;
return $this;
}
public function getUpdatedAt(): ?\DateTimeInterface
{
return $this->updatedAt;
}
public function setUpdatedAt(): self
{
$this->updatedAt = new \DateTime();
return $this;
}
public function getProductSerial(): ?string
{
return $this->product_serial;
}
public function setProductSerial(string $product_serial): self
{
$this->product_serial = $product_serial;
return $this;
}
}
ProductRepository
<?php
namespace App\Repository;
use App\Entity\Product;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Common\Persistence\ManagerRegistry;
class ProductRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Product::class);
}
}
doctrine.yaml
doctrine:
dbal:
# configure these for your database server
driver: 'pdo_mysql'
server_version: '5.7'
charset: utf8mb4
default_table_options:
charset: utf8mb4
collate: utf8mb4_unicode_ci
url: '%env(resolve:DATABASE_URL)%'
orm:
auto_generate_proxy_classes: true
naming_strategy: doctrine.orm.naming_strategy.underscore
auto_mapping: true
mappings:
App:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App
First of all, when you mock a method the original method doesn't exist any more, in this test. In your case you substitute ProductService::createProduct with something like this:
// This is your mock
class ProductService
{
// ...
public function createProduct($productData)
{
return $this;
}
}
Your test doesn't check anything.
If you want to test the real functionality then
namespace App\Tests\Service;
use App\Repository\ProductRepository;
use App\Service\ProductService;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class ProductServiceTest extends KernelTestCase
{
/**
* Create product test
*/
public function testCreateProduct(): void
{
// We load the kernel here (and $container)
self::bootKernel();
$productData = [
'name' => 'foo',
'quantity' => 1,
'sku' => 'bar',
];
$productRepository = static::$container->get('doctrine')->getRepository(ProductRepository::class);
$entityManager = static::$container->get('doctrine')->getManager();
// Here we mock the validator.
$validator = $this->getMockBuilder(ValidatorInterface::class)
->disableOriginalConstructor()
->setMethods(['validate'])
->getMock();
$validator->expects($this->once())
->method('validate')
->willReturn(null);
$productService = new ProductService($productRepository, $entityManager, $validator);
$productFromMethod = $productService->createProduct($productData);
// Here is you assertions:
$this->assertSame($productData['name'], $productFromMethod->getName());
$this->assertSame($productData['quantity'], $productFromMethod->getQuantity());
$this->assertSame($productData['sku'], $productFromMethod->getSku());
$productFromDB = $productRepository->findOneBy(['name' => $productData['name']]);
// Here we test that product in DB and returned product are same
$this->assertSame($productFromDB, $productFromMethod);
}
}
Given are the following two entity classes
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity()
* #ORM\Table()
*/
class Tree
{
/**
* #ORM\Id()
* #ORM\Column(type="guid")
* #ORM\GeneratedValue(strategy="UUID")
* #var string
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="Apple", mappedBy="tree", cascade={"persist"})
* #var Collection
*/
private $apples;
public function __construct()
{
$this->setApples(new ArrayCollection());
}
public function toArray(): array
{
return [
'id' => $this->getId(),
];
}
public function getId(): string
{
return $this->id;
}
public function setId(string $id): void
{
$this->id = $id;
}
public function getApples(): Collection
{
return $this->apples;
}
public function setApples(Collection $apples): void
{
$this->apples = $apples;
}
}
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity()
* #ORM\Table()
*/
class Apple
{
/**
* #ORM\Id()
* #ORM\Column(type="guid")
* #ORM\GeneratedValue(strategy="UUID")
* #var string
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Tree", inversedBy="apples")
* #var Tree
*/
private $tree;
public function toArray(): array
{
return [
'id' => $this->getId(),
];
}
public function getId(): string
{
return $this->id;
}
public function setId(string $id): void
{
$this->id = $id;
}
public function getTree(): Tree
{
return $this->tree;
}
public function setTree(Tree $tree): void
{
$this->tree = $tree;
}
}
The database schema looks good except for apple.tree_id being nullable. Is that already an issue in this case?
I'm persisting entries like the following:
<?php
declare(strict_types = 1);
namespace App\Service;
use App\Entity\Apple;
use App\Entity\Tree;
use Doctrine\ORM\EntityManager;
class Gardener
{
private $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function plantTree(): array
{
$entityManager = $this->entityManager;
$tree = new Tree();
$blueApple = new Apple();
$redApple = new Apple();
$tree->getApples()->add($blueApple);
$tree->getApples()->add($redApple);
$entityManager->persist($tree);
$entityManager->flush();
return (array) $tree;
}
}
When executing the persist and flush there are no errors or warnings. A tree an two apple entries are beingt store, the apple.tree_id is however always null.
It seems like I have a misconfiguration on the entity classes, but am not sure what it is. I also tried adding a JoinColumn annotation #ORM\JoinColumn(name="tree_id", referencedColumnName="id"), but it did not make any difference.
What changes do I need to make, to have appe.tree_id being set properly?
Your missing the adder & remover functions on the *ToMany side.
If your using Symfony >4 then replace setApples function with:
public function addApple(Apple $apple): Tree
{
$this->apples->add($apple);
return $this;
}
public function removeApple(Apple $apple): Tree
{
$this->apples->removeElement($apple);
return $this;
}
If you're using Zend Framework 3, then replace setApples function with:
public function addApples(array $apples): Tree
{
foreach ($apples as $apple) {
if (! $this->apples->contains($apple) {
$this->apples->add($apple)
$apple->setTree($this);
}
}
return $this;
}
public function removeApples(array $apples): Tree
{
foreach ($apples as $apple) {
if($this->apples->contains($apple) {
$this->apples->remove($apple);
}
}
return $this;
}
Have a read of the Working with Association docs, which show examples and explain how to update back 'n' forth.
I'm using SonataAdmin and SonataDoctrineORMAdmin bundles to manage entities.
The problem is I can't figure out how to eager fetch the related entities in the list view and as the number of listed entities increase the number of queries executed increasing rapidly as well.
I tried adding `fetch="EAGER" to the relation annotations but the profiles show that Sonata executes the separate queries anyway.
Here's one relation worth of code:
Post
<?php
namespace Acme\AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Table()
* #ORM\Entity
*/
class Post
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(name="name", type="string", length=255)
**/
private $name;
/**
* #ORM\ManyToMany(targetEntity="Acme\AppBundle\Entity\Tag", fetch="EAGER")
* #ORM\JoinTable(name="join_post_to_tag",
* joinColumns={#ORM\JoinColumn(name="post_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="tag_id", referencedColumnName="id")}
* )
**/
private $tags;
public function getId()
{
return $this->id;
}
public function setName($names)
{
$this->name = $name;
return $this;
}
public function getName()
{
return $this->name;
}
public function setTags($tags)
{
$this->tags = $tags;
return $this;
}
public function __toString()
{
return $this->getName();
}
}
Tag
<?php
namespace Acme\AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table()
* #ORM\Entity
*/
class Tag
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(name="value", type="string", length=255)
*/
private $value;
public function getId()
{
return $this->id;
}
public function setValue($value)
{
$this->value = $value;
return $this;
}
public function getValue()
{
return $this->value;
}
public function __toString()
{
return ($this->getValue()) ? : '';
}
}
The first related query that is run is fetching all the posts:
SELECT DISTINCT p0_.id AS id0, p0_.id AS id1
FROM Post p0_
LEFT JOIN join_post_to_tag j1_ ON p0_.id = j1_.post_id
LEFT JOIN Tag p1_ ON p1_.id = j1_.target_id
ORDER BY p0_.id ASC
But this does not fetch the related tags or even if it does, it still queries it again:
SELECT t0.id AS id1, t0.value AS value2
FROM Tag t0
INNER JOIN join_post_to_tag ON t0.id = join_post_to_tag.tag_id
WHERE join_post_to_tag.post_id = ?
I tried to mess with the createQuery method in the admin class but could not really find a way to make the related entities fetched correctly.
Is there a way to force the list view to eager fetch the required related entities?
You are on the right track, using the createQuery($context) method.
I have achieved eager loading as following:
public function createQuery($context = 'list')
{
$query = parent::createQuery($context); // let sonata build it's default query for the entity
$rootEntityAlias = $query->getRootAlias(); // get the alias defined by sonata for the root entity
$query->join($rootEntityAlias.'.relationFieldName', 'relationFieldAlias'); // manualy define the join you need
$query->addSelect('relationFieldAlias'); // this is the key line. It is not enough to join a table. You have to also add it to the select list of the query, so that it's actualy fetched
// $query->join(...) // repeat the process of joining and selecting for each relation field you need
// $query->addSelect(...)
return $query; // return the altered query to sonata. this will only work for the "list" action.
}
If you're having trouble using this, let me know:)
Further reads on this topic:
SO question
docs
I'm using Doctrine 2 with ZF2 and the Doctrine-Module.
I've written an Entity that needs a PreUpdate|PrePersist, because Doctrine doesn't allow
Date|Datetime in a primary key:
<?php
namespace Application\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
*
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
* #ORM\Table(name="sample")
*/
class Sample
{
/**
*
* #ORM\Id
* #ORM\Column(type="string")
* #var integer
*/
protected $runmode;
/**
*
* #ORM\Id
* #ORM\Column(type="date")
* #var DateTime
*/
protected $date;
public function getRunmode()
{
return $this->runmode;
}
public function setRunmode($runmode)
{
$this->runmode = $runmode;
return $this;
}
public function getDate()
{
return $this->date;
}
public function setDate($date)
{
$this->date = $date;
return $this;
}
/**
*
* #ORM\PreUpdate
* #ORM\PrePersist
*/
protected function formatDate()
{
die('preUpdate, prePersist');
if ($this->date instanceof \DateTime) {
$this->date = $this->date->format('Y-m-d');
}
return $this;
}
}
The Problem is now, if i set a DateTime as a Date I get the message:
"Object of class DateTime could not be converted to string"
because it doesn't walk into the formatDate.
First of all, since you mapped the field Sample#date as datetime, it should always be either null or an instance of DateTime.
Therefore, you should typehint your setDate method as following:
public function setDate(\DateTime $date = null)
{
$this->date = $date;
return $this;
}
Also, your lifecycle callback is not invoked because the visibility of method formatDate is protected, therefore not accessible by the ORM. Change it into public and it will work. No conversion should be necessary anyway, so you can get rid of it.