Defining extended entities in sonata - sonata-admin

I have some base entity that defines some fields which all (or at least most of) other entities should have. It looks like:
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity()
* #ORM\MappedSuperclass
* #ORM\HasLifecycleCallbacks()
*/
abstract class BaseEntity {
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="boolean")
*/
protected $published;
/**
* #ORM\Column(type="datetime")
*/
protected $createdAt;
/**
* #ORM\Column(type="datetime")
*/
protected $updatedAt;
public function getId(): ?int
{
return $this->id;
}
and so on...getters and setters.
Then I have entity i.e. ArticleCategory extending that BaseEntity:
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\ArticleCategoryRepository")
*/
class ArticleCategory extends BaseEntity
{
/**
* #ORM\Column(type="string", length=255)
*/
private $title;
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(string $title): self
{
$this->title = $title;
return $this;
}
}
So, it's just adding one extra field - title.
Then, I have base admin class:
namespace App\Admin;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use App\Entity\BaseEntity;
class BaseAdmin extends AbstractAdmin
{
/**
* #param BaseEntity $entity
*/
public function prePersist($entity)
{
$now = new \DateTime();
$entity->setCreatedAt($now);
$entity->setUpdatedAt($now);
$entity->setPublished(true);
}
}
And then admin class for that ArticleCategory entity:
namespace App\Admin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Symfony\Component\Form\Extension\Core\Type\TextType;
final class ArticleCategoryAdmin extends BaseAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('title', TextType::class);
}
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper
->add('title');
}
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->addIdentifier('title');
}
}
Problem is when I go to sonata list page instead of displaying all entities in ArticleCategory type I get an sql error:
Unknown column 'b0_.id' in 'field list'
And query looks like this:
SELECT b0_.id AS id_0, b0_.published AS published_1, b0_.created_at AS created_at_2, b0_.updated_at AS updated_at_3, a1_.title AS title_4
FROM article_category a1_
WHERE a1_.id IN (?, ?)
ORDER BY a1_.id ASC' with params [2, 3]
So sonata is getting data from correct table (article_category) and making an alias for it "a1_" and all fields that are directly inside ArticleCategory (title) are collected well, with that alias.
But all other fields that belong to base class (BaseEntity) sonata is trying to get via some un-existing alias "b0_" and query fails of course.
Any idea how to solve this? How to tell sonata that all the fields belong to the same table even they belong to 2 entity classes?

Found the problem. At the beginning where base entity was defined I had:
/**
* #ORM\Entity()
* #ORM\MappedSuperclass
* #ORM\HasLifecycleCallbacks()
*/
Which is not correct because of that "Entity()" row. That had to be removed. It can't be entity and mapped supper class at the same time.
/**
* #ORM\MappedSuperclass
* #ORM\HasLifecycleCallbacks()
*/

Related

How to solve schema update error with doctrine?

I'm having an issue on a pretty simple application made with Symfony 4.3.4
I just updated an entity and I'm trying to update my database with "doctrine:schema:update --force", but I have here an issue : An exception occurred while executing 'ALTER TABLE lead ADD content LONGTEXT DEFAULT NULL'
Second message : Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'lead ADD content LONGTEXT DEFAULT NULL' at line 1
I tried to change the name of the "content" attribute (it's the one I added), but this did not work. Do someone know why this is happening ?
Here is my Lead entity :
<?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\LeadRepository")
*/
class Lead
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="datetime")
*/
private $createdAt;
/**
* #ORM\Column(type="boolean")
*/
private $isAuth = false;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Formation", inversedBy="leads")
* #ORM\JoinColumn(nullable=false)
*/
private $formation;
/**
* #ORM\Column(type="text", nullable=true)
*/
private $content;
public function __construct()
{
$this->createdAt = new \DateTime();
}
public function getId(): ?int
{
return $this->id;
}
public function getCreatedAt(): ?\DateTimeInterface
{
return $this->createdAt;
}
public function setCreatedAt(\DateTimeInterface $createdAt): self
{
$this->createdAt = $createdAt;
return $this;
}
public function getIsAuth(): ?bool
{
return $this->isAuth;
}
public function setIsAuth(bool $isAuth): self
{
$this->isAuth = $isAuth;
return $this;
}
public function getFormation(): ?Formation
{
return $this->formation;
}
public function setFormation(?Formation $formation): self
{
$this->formation = $formation;
return $this;
}
public function getContent(): ?string
{
return $this->content;
}
public function setContent(?string $content): self
{
$this->content = $content;
return $this;
}
}
"lead" seems to be a reserved keyword for MySQL, so you need to escape it with backticks as follows:
/**
* #ORM\Entity(repositoryClass="App\Repository\LeadRepository")
* #ORM\Table(name="`lead`")
*/
You need to do this for other common keywords used as field names such as "order" and "sort" too.

Laravel Virgin: Access inverse model relationship in a model's mutator that participate in the relationship

In my logic I have these tables:
Table Grid:
id PK
width INT
height INT
Table Rover:
id pk
grid_pos_x UNSIGNED INT
grid_pos_y UNSIGNED INT
grid_id UNSINGED BIGINT
NOTE: I kept minimal in order to be more explanatory, for full table specs look on migration scripts below.
And I use the following migration script to create the schemas:
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateGridTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('grid', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedInteger('width');
$table->unsignedInteger('height');
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('grid');
}
}
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateRoverTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('rover', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('grid_id')->unsigned();
$table->string('command');
$table->foreign('grid_id')->references('id')->on('grid');
$table->smallInteger('last_commandPos')->unsigned()->default(0);
$table->smallInteger('grid_pos_x')->unsigned();
$table->smallInteger('grid_pos_y')->unsigned();
$table->enum('rotation', App\Constants\RoverConstants::ORIENTATIONS);
$table->string('last_command');
Schema::enableForeignKeyConstraints();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('rover');
}
}
And I try to model the table rover with the following model:
namespace App\Model;
use Illuminate\Database\Eloquent\Model;
use App\Model\Grid;
class Rover extends Model
{
/**
* Table Name
*/
protected $table='rover';
public function grid()
{
return $this->belongsTo(Grid::class);
}
public function setGridPosXValue($value)
{
}
public function setGridPosYValue($value)
{
}
}
And in a similar fashion the Grid model as well:
namespace App\Model;
use Illuminate\Database\Eloquent\Model;
use App\Model\Rover;
class Grid extends Model
{
/**
* Table Name
*/
protected $table='grid';
public function rovers()
{
return $this->hasMany(Rover::class);
}
}
What I want to achieve is to access the width and height attributes from the Grid in order to check whether the grid_pos_x and grid_pos_y are smaller from the width and height using the methods setGridPosXValue and the setGridPosYValue respectively.
Do you know how to do that?
Looks like you have a one to many relationship setup here: https://laravel.com/docs/5.8/eloquent-relationships#one-to-many
To get the grid for a rover you can access it in multiple ways:
Directly:
$width = $Rover->grid->width;
$height = $Rover->grid->height;
With the builder methods:
$Grid = $Rover->grid()->first();
$width = $Grid->width;
$height = $Grid->height;

Symfony 4 formbuilder loop each rows from database table

I have 2 entities :
Recettes
Categories
I try to build/list a form based on each rows of the table "Recettes" and display it from a controller.
Any ideas?
=======================
FORM_START
Name1 (TypeText) | Category (ChoiceType)
Name2 (TypeText) | Category (ChoiceType)
Name3 (TypeText) | Category (ChoiceType)
[Submit button]
FORM_END
=======================
Entity RECETTES
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Recettes
*
* #ORM\Table(name="recettes", indexes={#ORM\Index(name="categorie", columns={"categorie"})})
* #ORM\Entity
*/
class Recettes
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="nom", type="string", length=50, nullable=false)
*/
private $nom;
/**
* #var \Categories
*
* #ORM\ManyToOne(targetEntity="Categories")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="categorie", referencedColumnName="id")
* })
*/
private $categorie;
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 getCategorie(): ?Categories
{
return $this->categorie;
}
public function setCategorie(?Categories $categorie): self
{
$this->categorie = $categorie;
return $this;
}
}
Entity CATEGORIES
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Categories
*
* #ORM\Table(name="categories")
* #ORM\Entity
*/
class Categories
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="nom", type="string", length=50, nullable=false)
*/
private $nom;
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;
}
}
In symfony/forms each Form is an EventDispatcherInterface.
You can subscribe to one of the Events your form fires 🔥
In your case you want to add fields before your form is beeing processed.
class FriendMessageFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$builder->add('activate_recette_1', ChoiceType::class);
$builder->add('activate_recette_2', ChoiceType::class);
});
}
}
You can inject your RecetteRepository via Dependency-Injection ans use the results.
Listen to the form events via:
Event-Listener
Event-Subscriber

Doctrine Single Table Inheritance: Join on Parent Association from child Entity not working. FOSUserBundle

I have classes
IdentityUser (the user account, which extends the FOSUserBundle)
Person ( person or user of the system which have userAccount (IdentityUser)
Marketer ( a marketer user )
EDIT ( added use statements on classes )
The user account inheritated from FOSUSerBundle
namespace Elite\Security\Identity;
use FOS\UserBundle\Model\User as FOSUser;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="users")
*/
class IdentityUser extends FOSUser
{
/**
* #var integer The id
*
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
}
namespace Elite\Common\Entity;
use FOS\UserBundle\Model\UserInterface;
use Elite\Common\ValueObject\PersonName;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="type", type="string")
* #ORM\Table(name="people")
*/
class Person
{
/**
* #var integer The id
*
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
*
* #var \Elite\Common\ValueObject\PersonName The name of the person
*
* #ORM\Embedded(class="Elite\Common\ValueObject\PersonName", columnPrefix=false)
*/
protected $name;
/**
* #var \ The user identity of the person
*
* #ORM\ManyToOne(targetEntity="Elite\Security\Identity\IdentityUser")
* #ORM\JoinColumn(name="user_account_id", referencedColumnName="id")
*/
protected $userAccount;
public function __construct(UserInterface $account, PersonName $name)
{
$this->userAccount = $account;
$this->name = $name;
}
}
use Elite\Common\Entity\Person;
use Elite\Infrastructure\Doctrine\Adapter\Collection;
use FOS\UserBundle\Model\UserInterface;
use Doctrine\ORM\Mapping as ORM;
use Elite\Common\ValueObject\PersonName;
/**
* Class Marketer
* #package Elite\Emti\Marketing
*
* #ORM\Entity
*/
class Marketer extends Person
{
public function __construct(UserInterface $userAccount, PersonName $name)
{
parent::__construct($userAccount, $name);
$this->assignments = new Collection();
}
}
now what im trying to do is
query a Marketer
where the userAccount's username
is dana
EDIT
here is my query
$doctrine = $this->container->get('doctrine');
$em = $doctrine->getManager('inquiry');
$qb = $doctrine->getRepository('EmtiInquiry:Marketer', 'inquiry')->createQueryBuilder('m');
$dana = $qb->join('m.userAccount', 'account')
->where('account.username = :username')
->setParameter('username', 'dana')
->getQuery()
->getSingleResult();
but I dont get results or error like
[Doctrine\ORM\Query\QueryException]
[Semantical Error] line 0, col 91 near 'username = :': Error: Class Elite\S
ecurity\Identity\IdentityUser has no field or association named username
[Doctrine\ORM\Query\QueryException]
SELECT u FROM Elite\Emti\Inquiry\Marketer u INNER JOIN u.userAccount accoun
t WHERE account.username = :username

ORM Self-referenced entity not persisting into database properly

ive been trying to figure this out for 2 hours now and i cant seem to understand what went wrong.
I am using Symfony2 and FOSUserBundle.
I created a User entity which extends FOSUserBundle's BaseUser class. Within this User entity, i have 3 variables, id, my_mentors and my_mentees. More details are below:
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToMany(targetEntity="User", mappedBy="my_mentees")
*/
protected $my_mentors;
/**
* #ORM\ManyToMany(targetEntity="User", inversedBy="my_mentors")
* #ORM\JoinTable(name="mentor_and_mentee_relationship",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="mentors_or_mentees_user_id", referencedColumnName="id")}
* )
*/
protected $my_mentees;
public function __construct()
{
parent::__construct();
$this->my_mentors = new ArrayCollection();
$this->my_mentees = new ArrayCollection();
}
public function __toString()
{
return $this->getUsername();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Add my_mentors
*
* #param Fitness\FitBundle\Entity\User $myMentors
*/
public function addUser(\Fitness\FitBundle\Entity\User $myMentors)
{
$this->my_mentors[] = $myMentors;
}
/**
* Get my_mentors
*
* #return Doctrine\Common\Collections\Collection
*/
public function getMyMentors()
{
return $this->my_mentors;
}
/**
* Get my_mentees
*
* #return Doctrine\Common\Collections\Collection
*/
public function getMyMentees()
{
return $this->my_mentees;
}
}
I created the self reference because a Mentee(which is a User) will subscribe to a Mentor(which is also a User). I tried to do this using the following function:
public function subscribeAction($menteeID, $mentorID)
{
$em = $this->getDoctrine()
->getEntityManager();
$mentor = $em->getRepository('TestBundle:User')
->find($mentorID);
$mentee = $em->getRepository('TestBundle:User')
->find($menteeID);
$currentMentors = $mentee->getMyMentors();
if ($currentMentors->contains($mentor))
$this->get('session')->setFlash('subscribe-notice', 'You have already signed up to this mentor!');
else
{
$mentee->setIsMentor(false);
$mentee->addUser($mentor);
$mentor->addUser($mentee);
$em->persist($mentee);
$em->persist($mentor);
$em->flush();
$this->get('session')->setFlash('subscribe-notice', 'Subscription succesful!');
}
return $this->redirect($this->generateUrl('TestBundle_testpage', array('id' => $mentor->getMentorProfile()->getId()) ));
}
The problem here is that when i check the database, it does not persist the data. The mentor-mentee relationship information is not stored in the table "mentor_and_mentee_relationship" as declared by the annotation.
I persisted both $mentor and $mentee in an attempt to get it to work, but apparently it doesnt.
Could my ORM annotation be declared wrongly?
You are using the same function (addUser) to add a mentor and to add a mentee. This is wrong. First you need two different setters in your entity (I changed the name of addUser to make it clear)
/**
* Add my_mentors
*
* #param Fitness\FitBundle\Entity\User $myMentors
*/
public function addMentor(\Fitness\FitBundle\Entity\User $myMentors)
{
$this->my_mentors[] = $myMentors;
}
/**
* Add my_mentees
*
* #param Fitness\FitBundle\Entity\User $myMentees
*/
public function addMentee(\Fitness\FitBundle\Entity\User $myMentees)
{
$this->my_mentees[] = $myMentees;
}
Then in your controller do:
$mentee->addMentor($mentor);
$mentor->addMentee($mentee);

Resources