Im having problems following this tutorial: https://wildlyinaccurate.com/simple-nested-sets-in-doctrine-2
This is my code. As you can see in the comments, I can not iterate..
<?php
class Item
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="Item", mappedBy="parent", cascade={"remove"})
* #ORM\OrderBy({"position" = "ASC"})
*/
private $children;
/**
* #Gedmo\SortableGroup
* #ORM\ManyToOne(targetEntity="Item", inversedBy="children")
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id", nullable=true)
*/
private $parent;
...
///////////////////////////////////////
namespace Project\BackendBundle\Entity;
use Doctrine\Common\Collections\Collection;
class RecursiveCategoryIterator implements \RecursiveIterator
{
private $_data;
public function __construct(Collection $data)
{
$this->_data = $data;
}
public function hasChildren()
{
return ( ! $this->_data->current()->getChildren()->isEmpty());
}
public function getChildren()
{
return new RecursiveCategoryIterator($this->_data->current()->getChildren());
}
public function current()
{
return $this->_data->current();
}
public function next()
{
$this->_data->next();
}
public function key()
{
return $this->_data->key();
}
public function valid()
{
return $this->_data->current() instanceof Project\BackendBundle\Entity\Item;
}
public function rewind()
{
$this->_data->first();
}
}
/////////////////////////////////
$repository = $this->getDoctrine()->getRepository('ProjectBackendBundle:Item');
$root_categories = $repository->findBy(array('parent' => null));
var_dump(count($root_categories));// this returns 2
$collection = new \Doctrine\Common\Collections\ArrayCollection($root_categories);
$category_iterator = new RecursiveCategoryIterator($collection);
$recursive_iterator = new \RecursiveIteratorIterator($category_iterator);
var_dump($recursive_iterator->hasChildren()); //returns true
foreach ($recursive_iterator as $index => $child_category)
{
die("jfks"); //this is not shown
}
What is shown in this article is not nested set.
The best way to treat any kind of trees in symfony2 is to implement doctrine-extensions tree.
Please run one of those examples:
https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/tree.md
The article that you linked is showing simple adjacent set and is applicable to any kind of application, not only Symfony. But in Symfony2 with Doctrine2 you can depend on doctrineExtensionBundle.
Related
I have a Quiz class.
This class load 10 questions from a database depending on the level and the type of the quiz Object:
level 0 load the ten first, level 1 load the next ten and so on.
So in my test i create in a test database 30 questions.
Then i create quiz object with different level and i check that the first question in the quiz steps array match what i expect.
This test "quiz_contain_steps_depending_on_type_and_level()" failed randomly at least once every 5 launches.
This is the QuizTest class
<?php
namespace App\Tests\Quiz;
use App\Quiz\Question;
use App\Quiz\Quiz;
use App\Quiz\QuizQuestionRepositoryManager;
use App\Quiz\QuizStep;
use App\Quiz\QuizType;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ObjectRepository;
use Faker\Factory;
use Faker\Generator;
use ReflectionException;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Config\Definition\Exception\Exception;
class QuizTest extends KernelTestCase
{
use QuestionLoremTrait;
use PrivatePropertyValueTestTrait;
private Generator $faker;
private ?EntityManagerInterface $em;
private ObjectRepository $questionRepo;
private QuizQuestionRepositoryManager $quizQuestionManager;
protected function setUp(): void
{
$kernel = self::bootKernel();
$this->faker = Factory::create();
$this->em = $kernel->getContainer()->get('doctrine')->getManager();
$this->em->getConnection()->beginTransaction();
$this->questionRepo = $kernel->getContainer()->get('doctrine')->getRepository(Question::class);
$this->quizQuestionManager = new QuizQuestionRepositoryManager($this->questionRepo);
}
protected function tearDown(): void
{
parent::tearDown();
$this->em->getConnection()->rollBack();
$this->em->close();
$this->em = null;
}
/**
* #test
* #dataProvider provideQuizDataAndFirstQuestionExpectedIndex
* #param array $quizData
* #param int $firstQuestionExpectedIndex
* #throws ReflectionException
* #throws \Exception
*/
public function quiz_contain_steps_depending_on_type_and_level(array $quizData, int $firstQuestionExpectedIndex)
{
//We have questions in db
$questions = [];
for ($q = 1; $q <= 30; $q++) {
$question = $this->persistLoremQuestion($this->faker, $this->em);
$questions[] = $question;
}
$this->em->flush();
//When we create Quiz instance $quiz
$quiz = new Quiz($this->quizQuestionManager,quizData: $quizData);
//When we look at this $quiz steps property
$quizSteps = $quiz->getSteps();
/** #var QuizStep $firstStep */
$firstStep = $quizSteps[0];
//We expect
$this->assertNotEmpty($quizSteps);
$this->assertCount(10, $quizSteps);
//We expect if quiz is type normal and level variable questions depends of level:
$this->assertEquals($firstStep->getQuestion(), $questions[$firstQuestionExpectedIndex]);
}
public function provideQuizDataAndFirstQuestionExpectedIndex(): array
{
return [
[[], 0],
[['type' => QuizType::NORMAL, 'level' => '1'], 10],
[['type' => QuizType::NORMAL, 'level' => '2'], 20]
];
}
}
This is the Trait who generate fake question
<?php
namespace App\Tests\Quiz;
use App\Quiz\Question;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use Faker\Generator;
Trait QuestionLoremTrait{
/**
* This function persist a aleatory generated question, you must flush after
* #param Generator $faker
* #param EntityManagerInterface $em
* #return Question
* #throws Exception
*/
public function persistLoremQuestion(Generator $faker, EntityManagerInterface $em): Question
{
$nbrOfProps = random_int(2,4);
$answerPosition = random_int(0, $nbrOfProps - 1);
$props = [];
for ($i = 0; $i < $nbrOfProps; $i++){
$props[$i] = $faker->sentence ;
}
$question = new Question();
$question
->setSharedId(random_int(1, 2147483647))
->setInfo($faker->paragraph(3))
->setStatement($faker->sentence ."?")
->setProps($props)
->setAnswerPosition($answerPosition)
;
$em->persist($question);
return $question;
}
}
This is my Quiz class:
<?php
namespace App\Quiz;
use Symfony\Component\Config\Definition\Exception\Exception;
class Quiz
{
/**
* Quiz constructor.
* #param QuizQuestionManagerInterface $quizQuestionManager
* #param array $quizData
* This array of key->value represent quiz properties.
* Valid keys are 'step','level','type'.
* You must use QuizType constant as type value
* #param string $type
* #param int $level
* #param int $currentStep
* #param array $steps
*/
public function __construct(
private QuizQuestionManagerInterface $quizQuestionManager,
private string $type = QuizType::FAST,
private int $level = 0,
private array $quizData = [],
private int $currentStep = 0,
private array $steps = [])
{
if ($quizData != []) {
$this->hydrate($quizData);
}
$this->setSteps();
}
private function hydrate(array $quizData)
{
foreach ($quizData as $key => $value) {
$method = 'set' . ucfirst($key);
// If the matching setter exists
if (method_exists($this, $method) && $method != 'setQuestions') {
// One calls the setter.
$this->$method($value);
}
}
}
public function getCurrentStep(): int
{
return $this->currentStep;
}
public function getLevel(): int
{
return $this->level;
}
public function getType(): string
{
return $this->type;
}
public function getSteps(): array
{
return $this->steps;
}
private function setCurrentStep($value): void
{
$this->currentStep = $value;
}
private function setLevel(int $level): void
{
$this->level = $level;
}
private function setType($type): void
{
if (!QuizType::exist($type)) {
throw new Exception("This quiz type didn't exist, you must use QuizType constante to define type", 400);
}
$this->type = $type;
}
private function setSteps()
{
$this->steps = [];
$questions = $this->quizQuestionManager->getQuestions($this->type, $this->level);
foreach ($questions as $question) {
$this->steps[] = new QuizStep(question: $question);
}
}
}
This is the Question class:
<?php
namespace App\Quiz;
use App\Repository\QuestionRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity(repositoryClass=QuestionRepository::class)
*/
class Question
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private ?int $id;
/**
* #ORM\Column(type="integer")
*/
private ?int $sharedId;
/**
* #ORM\Column(type="string", length=1000, nullable=true)
* #Assert\Length(max=1000)
*/
private ?string $info;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private ?string $statement;
/**
* #ORM\Column(type="array")
*/
private array $props = [];
/**
* #ORM\Column(type="integer")
*/
private ?int $answerPosition;
public function getId(): ?int
{
return $this->id;
}
public function getSharedId(): ?int
{
return $this->sharedId;
}
public function setSharedId(int $sharedId): self
{
$this->sharedId = $sharedId;
return $this;
}
public function getInfo(): ?string
{
return $this->info;
}
public function setInfo(?string $info): self
{
$this->info = $info;
return $this;
}
public function getStatement(): ?string
{
return $this->statement;
}
public function setStatement(?string $statement): self
{
$this->statement = $statement;
return $this;
}
public function getProps(): ?array
{
return $this->props;
}
public function setProps(array $props): self
{
$this->props = $props;
return $this;
}
public function getAnswerPosition(): ?int
{
return $this->answerPosition;
}
public function setAnswerPosition(int $answerPosition): self
{
$this->answerPosition = $answerPosition;
return $this;
}
}
If anyone understands this behavior. I thank him in advance for helping me sleep better :-)
Thanks to #AlessandroChitolina comments.
The set of questions created in my test was not always recorded in the same order by my in my database.
So instead of testing the expected question from my starting $questions array, i retrieve the questions from the database in a new $dbQuestions array. That solve my problème.
This is the new test:
/**
* #test
* #dataProvider provideQuizDataAndFirstQuestionExpectedIndex
* #param array $quizData
* #param int $firstQuestionExpectedIndex
* #throws \Exception
*/
public function quiz_contain_steps_depending_on_type_and_level(array $quizData, int $firstQuestionExpectedIndex)
{
//We have questions in db
$questions = [];
for ($q = 1; $q <= 30; $q++) {
$question = $this->persistLoremQuestion($this->faker, $this->em);
$questions[] = $question;
}
$this->em->flush();
$dbQuestions = $this->questionRepo->findAll();
//When we create Quiz instance $quiz
$quiz = new Quiz($this->quizQuestionManager,quizData: $quizData);
//When we look at this $quiz steps property
$quizSteps = $quiz->getSteps();
/** #var QuizStep $firstStep */
$firstStep = $quizSteps[0];
//We expect
$this->assertNotEmpty($quizSteps);
$this->assertCount(10, $quizSteps);
//We expect if quiz is type normal and level variable questions depends of level:
$this->assertEquals($firstStep->getQuestion(), $dbQuestions[$firstQuestionExpectedIndex]);
}
I am trying to get composite and foreign keys as primary keys working in Doctrine ORM. I know what I'm trying to do is possible because it is described here: https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/tutorials/composite-primary-keys.html#composite-and-foreign-keys-as-primary-key. This is exactly my use-case: I have some products, an order and an order item.
However, doctrine orm is unable to map this relation unto the database. The current problem is that only one of the annotated \Id primary keys is reflected on the mysql database. So $producto is translated unto the database correctly as producto_id and is both a primary key and a foreign key. However, the $orden property which is annotated in the same way doesn't appear whatsoever on my database.
This seems odd because when I was first testing this feature I tried only with one of the two properties and it worked fine, however, when both properties are annotated only one seems to be parsed by the metadata parser. Furthermore, I tried to revert my project to a usable state by forgetting about the foreign keys and just have a composite primary key (like I had it before), but now the parser doesn't seem to even recognize the primary key. For example, for:
class ProductoOrden
{
/**
* #ORM\Id()
* #ORM\Column(type="integer")
*/
private $idOrden;
/**
* #ORM\Id()
* #ORM\Column(type="integer")
*/
private $idProducto;
I get:
bash-3.2$ php bin/console make:migration
In MappingException.php line 52:
No identifier/primary key specified for Entity "App\Entity\ProductoOrden". Every Entity must
have an identifier/primary key.
So I'm unable to set it up properly or to revert it to the previous state (which is the strangest of all).
I'm about to restart my whole project from scratch, because I cannot make sense of how the metadata parsing works. I am worried I have screwed up the process because I have manually erased the files at 'src\Migrations' because of similar issues before and php bin/console doctrine:migrations:version --delete --all didn't seem to work or I haven't understood the proper use of it.
In conclusion: ¿Could anyone assert if what I am trying to do with ProducoOrden is possible (maybe I'm not understanding the documentation example)? Is there any way to completely wipe out previous cache about the annotations/ schema metadata?
I've looked unto the orm:schema-tool but I don't really get how to configure it properly or why I have to configure it at all of I already have the bin/console tool on my project.
I will show all three involved classes for completeness sake, but the main problem is within ProductoOrden (Order-items).
<?php
//Products
namespace App\Entity;
use App\Repository\ProductosRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* #ORM\Entity(repositoryClass=ProductosRepository::class)
* #UniqueEntity("idProducto", message=" {producto {{ value }}}: llave primaria violada ")
*/
class Productos
{
/**
* #ORM\Id()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $nombreProducto;
/**
* #ORM\Column(type="string", length=255)
*/
private $descripcionProducto;
/**
* #ORM\Column(type="string", length=255)
*/
private $urlImagen;
/**
* #ORM\Column(type="integer")
*/
private $puntosProducto;
public function getIdProducto(): ?int
{
return $this->idProducto;
}
public function getCodProducto(): ?int
{
return $this->idProducto;
}
public function setIdProducto(int $codProducto): self
{
$this->idProducto = $codProducto;
return $this;
}
public function getNombreProducto(): ?string
{
return $this->nombreProducto;
}
public function setNombreProducto(string $nombreProducto): self
{
$this->nombreProducto = $nombreProducto;
return $this;
}
public function getDescripcionProducto(): ?string
{
return $this->descripcionProducto;
}
public function setDescripcionProducto(string $descripcionProducto): self
{
$this->descripcionProducto = $descripcionProducto;
return $this;
}
public function getUrlImagen(): ?string
{
return $this->urlImagen;
}
public function setUrlImagen(string $urlImagen): self
{
$this->urlImagen = $urlImagen;
return $this;
}
public function getPuntosProducto(): ?int
{
return $this->puntosProducto;
}
public function setPuntosProducto(int $puntosProducto): self
{
$this->puntosProducto = $puntosProducto;
return $this;
}
public function __toString(){
$str = '{producto:'.$this->getIdProducto().', nombre: '.$this->getNombreProducto().'}';
return $str;
}
}
<?php
\\Orders
namespace App\Entity;
use App\Repository\OrdenesRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* #ORM\Entity(repositoryClass=OrdenesRepository::class)
* #UniqueEntity("idOrden", message="{orden {{ value }}}: llave primaria violada")
*/
class Ordenes
{
/**
* #ORM\Id()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="integer")
*/
private $totalOrden;
/**
* #ORM\Column(type="string", length=255)
*/
private $estado;
/**
* #ORM\OneToMany(targetEntity=ProductoOrden::class, mappedBy="orden", orphanRemoval=true)
*/
private $productosOrden;
public function __construct()
{
$this->productosOrden = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getIdOrden(): ?int
{
return $this->idOrden;
}
public function setIdOrden(int $idOrden): self
{
$this->idOrden = $idOrden;
return $this;
}
public function getTotalOrden(): ?int
{
return $this->totalOrden;
}
public function setTotalOrden(int $totalOrden): self
{
$this->totalOrden = $totalOrden;
return $this;
}
public function getEstado(): ?string
{
return $this->estado;
}
public function setEstado(string $estado): self
{
$this->estado = $estado;
return $this;
}
public function __toString(){
$str = '{orden:'.$this->getIdOrden().'}';
return $str;
}
/**
* #return Collection|ProductoOrden[]
*/
public function getProductosOrden(): Collection
{
return $this->productosOrden;
}
public function addProductosOrden(ProductoOrden $productosOrden): self
{
if (!$this->productosOrden->contains($productosOrden)) {
$this->productosOrden[] = $productosOrden;
$productosOrden->setOrden($this);
}
return $this;
}
public function removeProductosOrden(ProductoOrden $productosOrden): self
{
if ($this->productosOrden->contains($productosOrden)) {
$this->productosOrden->removeElement($productosOrden);
// set the owning side to null (unless already changed)
if ($productosOrden->getOrden() === $this) {
$productosOrden->setOrden(null);
}
}
return $this;
}
}
<?php
\\Order-items
namespace App\Entity;
use App\Repository\ProductoOrdenRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* #ORM\Entity(repositoryClass=ProductoOrdenRepository::class)
* #UniqueEntity(fields={"idOrden","idProducto"}, message="{prod. orden {{ value }}}: llave primaria violada")
*/
class ProductoOrden
{
/*
* #ORM\Id
* #ORM\ManyToOne(targetEntity=Ordenes::class, inversedBy="productosOrden")
* #ORM\JoinColumn(nullable=false)
*/
private $orden;
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity=Productos::class)
* #ORM\JoinColumn(nullable=false)
*/
private $producto;
/**
* #ORM\Column(type="integer")
*/
private $puntos;
/**
* #ORM\Column(type="integer")
*/
private $cantidad;
public function getId(): ?int
{
return $this->idOrden;
}
public function setIdOrden(int $idOrden): self
{
$this ->idOrden = $idOrden;
return $this;
}
public function getIdProducto(): ?int
{
return $this->idProducto;
}
public function setIdProducto(int $idProducto): self
{
$this->idProducto = $idProducto;
return $this;
}
public function getPuntos(): ?int
{
return $this->puntos;
}
public function setPuntos(int $puntos): self
{
$this->puntos = $puntos;
return $this;
}
public function getCantidad(): ?int
{
return $this->cantidad;
}
public function setCantidad(int $cantidad): self
{
$this->cantidad = $cantidad;
return $this;
}
public function __toString(){
$str = '{productoOrden:'.$this->getId().', '.$this->getIdProducto().'}';
return $str;
}
public function getOrden(): ?Ordenes
{
return $this->orden;
}
public function setOrden(?Ordenes $orden): self
{
$this->orden = $orden;
return $this;
}
}
For the shown classes, the migration it generates is
final class Version20200814210929 extends AbstractMigration
{
public function getDescription() : string
{
return '';
}
public function up(Schema $schema) : void
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('ALTER TABLE producto_orden ADD puntos INT NOT NULL');
}
public function down(Schema $schema) : void
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('ALTER TABLE producto_orden DROP puntos');
}
}
As you can see, small changes like changing the type of a property work; but it doesn't seem to take on the id() and the association annotations.
Many thanks
I have a constructor and route in my custom ProfileController
private $userManager;
public function __construct(UserManagerInterface $userManager)
{
$this->userManager = $userManager;
}
/**
* #Route("/profile/bookings", name="profile_bookings")
*/
public function bookings()
{
$user = $this->getUser();
return $this->render('profile/bookings/bookings.html.twig', array('user'=>$user));
}
And in my template I reference
{{ user.first_name }}
But I get the error:
HTTP 500 Internal Server Error
Neither the property "first_name" nor one of the methods "first_name()", "getfirst_name()"/"isfirst_name()"/"hasfirst_name()" or "__call()" exist and have public access in class "App\Entity\User".
How do I get the user info from db and display in sub pages of profile?
Edit: User Entity ...
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use FOS\UserBundle\Model\User as BaseUser;
/**
* #ORM\Entity
* #ORM\Table(name="`user`")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #ORM\Column(type="string", length=190)
*/
private $first_name;
/**
* #ORM\Column(type="string", length=190)
*/
private $last_name;
/**
* #ORM\Column(type="string", length=190, nullable=true)
*/
private $phone_number;
/**
* #ORM\Column(type="integer", nullable=true)
*/
private $profile_height;
/**
* #ORM\Column(type="integer", nullable=true)
*/
private $profile_weight;
/**
* #ORM\Column(type="date", nullable=true)
*/
private $profile_dob;
/**
* #ORM\Column(type="string", length=190, nullable=true)
*/
private $profile_gender;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Booking", mappedBy="user")
*/
private $bookings;
public function __construct()
{
parent::__construct();
$this->bookings = new ArrayCollection();
}
/**
* Overridde setEmail method so that username is now optional
*
* #param string $email
* #return User
*/
public function setEmail($email)
{
$this->setUsername($email);
return parent::setEmail($email);
}
public function getFirstName()
{
return $this->first_name;
}
public function setFirstName($first_name)
{
$this->first_name = $first_name;
}
public function getLastName()
{
return $this->last_name;
}
public function setLastName($last_name)
{
$this->last_name = $last_name;
}
public function getPhoneNumber(): ?string
{
return $this->phone_number;
}
public function setPhoneNumber(string $phone_number): self
{
$this->phone_number = $phone_number;
return $this;
}
public function getProfileHeight(): ?int
{
return $this->profile_height;
}
public function setProfileHeight(?int $profile_height): self
{
$this->profile_height = $profile_height;
return $this;
}
public function getProfileDob(): ?\DateTimeInterface
{
return $this->profile_dob;
}
public function setProfileDob(?\DateTimeInterface $profile_dob): self
{
$this->profile_dob = $profile_dob;
return $this;
}
public function getProfileWeight(): ?int
{
return $this->profile_weight;
}
public function setProfileWeight(?int $profile_weight): self
{
$this->profile_weight = $profile_weight;
return $this;
}
public function getProfileGender(): ?string
{
return $this->profile_gender;
}
public function setProfileGender(?string $profile_gender): self
{
$this->profile_gender = $profile_gender;
return $this;
}
/**
* #return Collection|Booking[]
*/
public function getBookings(): Collection
{
return $this->bookings;
}
public function addBooking(Booking $booking): self
{
if (!$this->bookings->contains($booking)) {
$this->bookings[] = $booking;
$booking->setUser($this);
}
return $this;
}
public function removeBooking(Booking $booking): self
{
if ($this->bookings->contains($booking)) {
$this->bookings->removeElement($booking);
// set the owning side to null (unless already changed)
if ($booking->getUser() === $this) {
$booking->setUser(null);
}
}
return $this;
}
}
Thanks.
#Franck Gamess is right but you can also get rid of the get.
If you write {{ user.firstName }}, twig will associate that to your method getFirstName() automatically.
I don't know why you write your properties with snake_case but you could change it to camelCase and access your properties via their "real" name.
Just use in your twig template:
{{ user.getFirstName }}
It works fine. Normally what Twig does is quite simple on the PHP Layer:
check if user is an array and first_name a valid element;
if not, and if user is an object, check that first_name is a valid property;
if not, and if user is an object, check that first_name is a valid method (even if first_name is the constructor - use __construct() instead);
if not, and if user is an object, check that getfirst_name is a valid method;
if not, and if user is an object, check that isfirst_name is a valid method;
if not, and if user is an object, check that hasfirst_name is a valid method;
if not, return a null value.
See Twig variables.
By the way you should follow the Symfony Coding Standard for your variable, because it can be difficult for twig to find value of properties written in snake_case.
I don't think you should construct the UserManagerInterface in your controller. Also, like Franck says, use the coding standard if you can, it will save a lot of time and frustration in the future!
Here is the controller I use in a Symfony 4 project:
namespace App\Controller;
use FOS\UserBundle\Model\UserInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
/**
* #Route("/profile/bookings", name="profile_bookings")
*/
public function bookings()
{
$user = $this->getUser();
if (!is_object($user) || !$user instanceof UserInterface) {
throw new AccessDeniedException('This user does not have access to this section.');
}
return $this->render('profile/bookings/bookings.html.twig', array(
'user' => $user,
));
}
}
I am not writing "what did I try" or "what is not working" since I can think of many ways to implement something like this. But I cannot believe that no one did something similar before and that is why I would like to ask the question to see what kind of Doctrine2 best practices show up.
What I want is to trigger an event on a property change. So let's say I have an entity with an $active property and I want a EntityBecameActive event to fire for each entity when the property changes from false to true.
Other libraries often have a PropertyChanged event but there is no such thing available in Doctrine2.
So I have some entity like this:
<?php
namespace Application\Entity;
class Entity
{
/**
* #var int
* #ORM\Id
* #ORM\Column(type="integer");
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var boolean
* #ORM\Column(type="boolean", nullable=false)
*/
protected $active = false;
/**
* Get active.
*
* #return string
*/
public function getActive()
{
return $this->active;
}
/**
* Is active.
*
* #return string
*/
public function isActive()
{
return $this->active;
}
/**
* Set active.
*
* #param bool $active
* #return self
*/
public function setActive($active)
{
$this->active = $active;
return $this;
}
}
Maybe ChangeTracking Policy is what you want, maybe it is not!
The NOTIFY policy is based on the assumption that the entities notify
interested listeners of changes to their properties. For that purpose,
a class that wants to use this policy needs to implement the
NotifyPropertyChanged interface from the Doctrine\Common namespace.
Check full example in link above.
class MyEntity extends DomainObject
{
private $data;
// ... other fields as usual
public function setData($data) {
if ($data != $this->data) { // check: is it actually modified?
$this->onPropertyChanged('data', $this->data, $data);
$this->data = $data;
}
}
}
UPDATE
This is a full example but silly one so you can work on it as you wish. It just demonstrates how you do it, so don't take it too serious!
entity
namespace Football\TeamBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="country")
*/
class Country extends DomainObject
{
/**
* #var int
*
* #ORM\Id
* #ORM\Column(type="smallint")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
*
* #ORM\Column(type="string", length=2, unique=true)
*/
protected $code;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set code
*
* #param string $code
* #return Country
*/
public function setCode($code)
{
if ($code != $this->code) {
$this->onPropertyChanged('code', $this->code, $code);
$this->code = $code;
}
return $this;
}
/**
* Get code
*
* #return string
*/
public function getCode()
{
return $this->code;
}
}
domainobject
namespace Football\TeamBundle\Entity;
use Doctrine\Common\NotifyPropertyChanged;
use Doctrine\Common\PropertyChangedListener;
abstract class DomainObject implements NotifyPropertyChanged
{
private $listeners = array();
public function addPropertyChangedListener(PropertyChangedListener $listener)
{
$this->listeners[] = $listener;
}
protected function onPropertyChanged($propName, $oldValue, $newValue)
{
$filename = '../src/Football/TeamBundle/Entity/log.txt';
$content = file_get_contents($filename);
if ($this->listeners) {
foreach ($this->listeners as $listener) {
$listener->propertyChanged($this, $propName, $oldValue, $newValue);
file_put_contents($filename, $content . "\n" . time());
}
}
}
}
controller
namespace Football\TeamBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Football\TeamBundle\Entity\Country;
class DefaultController extends Controller
{
public function indexAction()
{
// First run this to create or just manually punt in DB
$this->createAction('AB');
// Run this to update it
$this->updateAction('AB');
return $this->render('FootballTeamBundle:Default:index.html.twig', array('name' => 'inanzzz'));
}
public function createAction($code)
{
$em = $this->getDoctrine()->getManager();
$country = new Country();
$country->setCode($code);
$em->persist($country);
$em->flush();
}
public function updateAction($code)
{
$repo = $this->getDoctrine()->getRepository('FootballTeamBundle:Country');
$country = $repo->findOneBy(array('code' => $code));
$country->setCode('BB');
$em = $this->getDoctrine()->getManager();
$em->flush();
}
}
And have this file with 777 permissions (again, this is test) to it: src/Football/TeamBundle/Entity/log.txt
When you run the code, your log file will have timestamp stored in it, just for demonstration purposes.
Hi everybody Im using doctrine ODM and have trouble with hydrator. I can't make extract on document with embed collection nor reference Class. the extract result for these class give me object and i really need to have them in array for rest module which is consumed by backbone implementation.
Here a example class :
Analyse.php Document
<?php
namespace Application\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Doctrine\Common\Collections\Collection;
/**
* Application\Document\Analyse
*
* #ODM\Document(collection="analyse")
*/
class Analyse extends BaseDocument
{
/**
* #ODM\Id
*/
protected $id;
/**
* #ODM\Field(type="string")
*/
protected $nom;
/**
* #ODM\Field(type="string")
* #ODM\Index
*/
protected $alias;
/**
* #ODM\EmbedMany(targetDocument="ElementsAnalyse")
*/
protected $elements = array();
public function __construct()
{
parent::__construct();
$this->elements = new \Doctrine\Common\Collections\ArrayCollection();
}
public function getId()
{
return $this->id;
}
public function setNom($nom)
{
$this->nom = $nom;
return $this;
}
public function getNom()
{
return $this->nom;
}
public function setAlias($alias)
{
$this->alias = $alias;
return $this;
}
public function getAlias()
{
return $this->alias;
}
public function addElements(Collection $elements)
{
foreach ($elements as $element) {
$this->elements->add($element);
}
}
public function removeElements(Collection $elements)
{
foreach ($elements as $item) {
$this->elements->removeElement($item);
}
}
public function getElements()
{
return $this->elements;
}
}
ElementAnalyse.php Collection
<?php
namespace Application\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Doctrine\Common\Collections\Collection;
/**
* Application\Document\Valeurnormales
*
* #ODM\EmbeddedDocument
*
*/
class ElementsAnalyse
{
/**
* #ODM\Field(type="string")
*/
protected $nom;
/**
* #ODM\Field(type="string")
*/
protected $unite;
/**
* #ODM\EmbedMany(targetDocument="ValeurNormales")
*/
protected $valeurnormales = array();
/**
* #ODM\Field(type="string")
*/
protected $type;
public function __construct()
{
$this->valeurnormales = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Set nom
*/
public function setNom($nom)
{
$this->nom = $nom;
return $this;
}
/**
* Get nom
*/
public function getNom()
{
return $this->nom;
}
/**
* Set unite
*/
public function setUnite($unite)
{
$this->unite = $unite;
return $this;
}
/**
* Get unite
*
* #return string
*/
public function getUnite()
{
return $this->unite;
}
/**
* add valeurnormales
*/
public function addValeurnormales(Collection $vn)
{
foreach ($vn as $item) {
$this->valeurnormales->add($item);
}
}
public function removeValeurnormales(Collection $vn)
{
foreach ($vn as $item) {
$this->valeurnormales->removeElement($item);
}
}
/**
* Get valeurnormales
*/
public function getValeurnormales()
{
return $this->valeurnormales;
}
/**
* Set type
*
* #param $type
* #return Analyse
*/
public function setType($type)
{
$this->type = $type;
return $this;
}
/**
* Get type
*
* #return Type
*/
public function getType()
{
return $this->type;
}
/**
* toArray function
*/
public function toArray()
{
return get_object_vars($this);
}
/**
* fromArray function
*
*/
public function fromArray(array $array)
{
$objects = $this->toArray();
foreach($array as $item => $value)
{
if(array_key_exists($item, $objects))
{
$this->$item = $value;
}
}
}
}
Here my getList Method
public function getList()
{
$hydrator = new DoctrineHydrator($entityManager, 'Application\Document\Analyse');
$service = $this->getAnalyseService();
$results = $service->findAll();
$data = $hydrator->extract($results);
return new JsonModel($data);
}
And obviously var_dump($data['elements']) return object Collection or proxy class
Can You help me. Anything will be appreciated it been 2 weeks i can't make it work.
Read about Hydrator Strategy out there but i don't knnow how to implement it.
Currently, the Doctrine ODM implementation does not provide recursion for embedded objects and references.
If you use var_dump() on your $hydrator->extract($results), you'll see that all your embeds/references are there in their original object format.
What you can do here is to use Zend\Stdlib\Hydrator\Strategy, and define your own logic for extraction/hydration. Doctrine extends Zend Framework 2's hydrators and strategies.