I have an entity called User which has inheritance for Student, Professional and Business.
When a user is registered, is only a User but they must update their profile and choose which kind of user is, I have a form which handles this, a controller which gets the form data, but I can't update the discriminator field type with $userEntity->setType()
This is my mapping stuff
class User
{
const TYPE_BASIC = "Basico";
const TYPE_STUDENT = "Estudiante";
const TYPE_PROFESSIONAL = "Profesional";
const TYPE_BUSINESS = "Empresa";
protected $type = self::TYPE_BASIC;
public function getType()
{
return self::TYPE_BASIC;
}
public function setType($type)
{
$this->type = $type;
}
class Student extends User
{
protected $type = self::TYPE_STUDENT;
And then Professional and Business just like Student (changing const)
<entity name="User" table="user_base" inheritance-type="JOINED">
<discriminator-column name="type" type="string"/>
<discriminator-map>
<discriminator-mapping value="Basico" class="User"/>
<discriminator-mapping value="Estudiante" class="Student"/>
<discriminator-mapping value="Profesional" class="Professional"/>
<discriminator-mapping value="Empresa" class="Business"/>
</discriminator-map>
the child tables are named user_xxx where xxx = Student/Professional/Business
And this is my controller
if($form->isValid())
{
$em = $this->getDoctrine()->getManager();
$data = $form->all();
$type = $data['type']->getData();
$email = $data['email']->getData();
$profile = $data['profile']->all();
$name = $profile['name']->getData();
$lastName = $profile['lastName']->getData();
$birth = $profile['birth']->getData();
$profileEntity = new Profile();
$profileEntity->setBirth($birth);
$profileEntity->setName($name);
$profileEntity->setLastName($lastName);
$profileEntity->setUser($user);
$em->persist($profileEntity);
ladybug_dump($type);
$userEntity = $em->getRepository('User')->find($user);
$userEntity->setProfile($profileEntity);
$userEntity->setType($type);
if($user->getEmail() != $email)
$userEntity->setEmail($email);
$em->persist($userEntity);
$em->flush();
}
Everything is persisted but type field, which remains it's original data. I know when I change discriminator column I need to create a new row inside it's child element, but first I want to know how to change the discriminator column.
it is possible if you use this custom bit of code in the Form of a Trait which you can use inside a Repository.
The Trait:
namespace App\Doctrine\Repository;
use App\Exception\InvalidDiscriminatorClassException;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DBALException;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping\ClassMetadata;
/**
* Discriminator Trait
*/
trait DiscriminatorTrait
{
/**
* #return ClassMetadata
*/
abstract public function getClassMetadata();
/**
* #return EntityManager
*/
abstract public function getEntityManager();
/**
* Update Discriminator Column
*
* #param integer $id
* #param string $class
* #return boolean
* #throws InvalidDiscriminatorClassException
*/
private function updateDiscriminatorColumn($id, $class)
{
/* #var ClassMetadata $classMetadata */
$classMetadata = $this->getClassMetadata();
if (!in_array($class, $classMetadata->discriminatorMap)) {
throw new InvalidDiscriminatorClassException($class);
}
$identifier = $classMetadata->fieldMappings[$classMetadata->identifier[0]]["columnName"];
$column = $classMetadata->discriminatorColumn["fieldName"];
$value = array_search($class, $classMetadata->discriminatorMap);
/* #var Connection $connection */
$connection = $this->getEntityManager()->getConnection();
try {
$connection->update(
$classMetadata->table["name"],
[$column => $value],
[$identifier => $id]
);
}
catch (DBALException $e) {
return false;
}
return true;
}
}
According to the Doctrine documentation on Inheritance mapping, it is not possible to either get or set the type. You may wish take advantage of PUGXMultiUserBundle, which readily handles the mapping. This bundle also makes it possible for your users to register with the appropriate profile.
Related
How to exchange the values in the Unique column of two records?
like below Model
User
id
name
code // this is unique
What I want to do is like...
$user1 = User::find(1);
$user2 = User::find(2);
DB::Transaction();
try {
$user1->code = $user2->code;
$user2->code = $user1->code;
$user1->save();
$user2->save();
} catch()...
of course, I know this code doesn't work by constraint violation error.
Anyway,I want to exchange UserA's code and UserB's code.
But I don't know the best way.
Any one knows?
I tried this code,and it worked.but it seems little dirty.
$user1 = User::find(1);
$user2 = User::find(2);
DB::Transaction();
try {
$user1_code_temp = $user1->code;
$user2_code_temp = $user2->code;
$user1->code = rand();
$user1->save();
$user2->code = $user1_code_temp;
$user2->save();
$user1->code = $user2_code_temp
$user1->save();
} catch()...
What you are doing is correct. And I personally would prefer it being this way as it is more readable what you are doing. You could disable unique checks in mysql temporarily but I would not recommend that.
To clean up the code, you can add this as a method in users model :
<?php
// Inside User.php model
/**
* Exchange user code
*
* #param User $user
*
* #return void
*/
public function exchangeCode(User $user){
$code1 = $this->code;
$code2 = $user->code;
$this->setCode(rand(6));
$user->setCode($code1);
$this->setCode($code2);
return $this;
}
/**
* Set Code
*
* #param string $code
*
* #return void
*/
public function setCode($code)
{
$this->code = $code;
$this->save();
return $this;
}
// And then in controller
$user1->exchangeCode($user2);
On your model define a
protected $primaryKey = 'code';
I have two entities that I'm trying to apply a OneToMany / ManyToOne relationship to (one Game has many GameContent).
Game
/**
* #ORM\OneToMany(targetEntity="GameContent", mappedBy="game")
*/
private $contents;
public function __construct()
{
$this->contents = new ArrayCollection();
}
public function getContents()
{
return $this->contents;
}
GameContent
/**
* #ORM\ManyToOne(targetEntity="Game", inversedBy="contents")
*/
private $game;
And the following code inserts both records into their respective tables:
$game = $form->getData();
$content = new GameContent();
$content->setType('some type');
$game->getContents()->add($content);
$em = $this->getDoctrine()->getManager();
$em->persist($content);
$em->persist($game);
$em->flush();
However, the GameContent's game_id is inserted as null:
INSERT INTO game_content (type, game_id) VALUES (?, ?)
Parameters: { 1: 'some type', 2: null }
I've also tried:
changing the order of persist()
replacing $game->getContents()->add($content) with $game->addContents($content) by doing $this->contents[] = $content;
removing persist($content) and having cascade={"persist"} on the Game entity.
Why is game_id being inserted as null?
My current workaround is:
$em = $this->getDoctrine()->getManager();
$game = $form->getData();
$em->persist($game);
$content = new GameContent();
$content->setType('some type');
$content->setGame($game);
$em->persist($content);
$em->flush();
You have 2 solutions :
Persist children in controller
Without cascade={"persist"}
$em = $this->getDoctrine()->getManager();
// Get data
$game = $form->getData();
// Create new GameContent and hydrate
$content = new GameContent();
$content->setType('some type');
// Associate Game <> GameContent
$content->setGame($game);
// Persist GameContent
$em->persist($content);
// Persist Game and commit
$em->persist($game);
$em->flush();
Persist children in cascade
With cascade={"persist"} in OneToMany relation.
Add in setGame() function, to force association :
$game->addContent($this);
And remove persist :
$em = $this->getDoctrine()->getManager();
// Get data
$game = $form->getData();
// Create new GameContent and hydrate
$content = new GameContent();
$content->setType('some type');
// Associate Game <> GameContent
$content->setGame($game);
// Persist Game and commit
$em->persist($game);
$em->flush();
I think the error was also due to the positioning of the persist on game.
Add in setGame() function, to force association :
$game->addContent($this);
And remove persist :
$em = $this->getDoctrine()->getManager();
// Get data
$game = $form->getData();
// Create new GameContent and hydrate
$content = new GameContent();
$content->setType('some type');
// Associate Game <> GameContent
$content->setGame($game);
// Persist Game and commit
$em->persist($game);
$em->flush();
Note that today (Doctrine 2.7.1), the make:entity utility creates methods that does this stuff for you, in your case you would have had a method like this on your Game entity:
public function addContent(GameContent $content): self
{
$this->contents->add($content);
$content->setGame($this); // <-- IMPORTANT PART IS HERE
return $this;
}
Then, calling this on the game would have done the job:
this->addContent((new GameContent())->setType('some type'));
Further to the accepted answer, my next step was to create a form to handle the GameContent data, which led to further changes and some simplified logic.
I now setGame() in Game::addContent(), and so I've removed $game->addContent($this); in GameContent::setGame().
Game
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="GameContent", mappedBy="game", cascade={"persist"})
*/
private $contents;
public function __construct()
{
$this->contents = new ArrayCollection();
}
public function getContents()
{
return $this->contents;
}
public function addContent(GameContent $content)
{
$this->contents->add($content);
$content->setGame($this);
return $this;
}
public function removeContent(GameContent $content)
{
$this->contents->removeElement($content);
return $this;
}
GameContent
/**
* #ORM\ManyToOne(targetEntity="Game", inversedBy="contents")
*/
private $game;
public function setGame(Game $game)
{
$this->game = $game;
return $this;
}
/**
* #return Game
*/
public function getGame()
{
return $this->game;
}
The real-world form-handling logic will look like this:
$game = $form->getData();
$em = $this->getDoctrine()->getManager();
$em->persist($game);
$em->flush();
More information at: http://symfony.com/doc/2.8/form/form_collections.html (see Doctrine: Cascading Relations and saving the "Inverse" side).
I have three tables in mysql database like this picture below :
Now, with gii, I create those model like this :
For table BuktiPenerimaan
class BuktiPenerimaan extends \yii\db\ActiveRecord{
/**
* #return \yii\db\ActiveQuery
*/
public function getInvoiceReports()
{
return $this->hasMany(InvoiceReport::className(), ['bukti_penerimaan_id' => 'id']);
}
}
And InvoiceReports:
class InvoiceReport extends \yii\db\ActiveRecord{
/**
* #return \yii\db\ActiveQuery
*/
public function getInvoiceReportDetails()
{
return $this->hasMany(InvoiceReportDetail::className(), ['invoice_id' => 'id']);
}
My question is, how to access all record in table invoice_report_detail if I created an object that came from BuktiPenerimaan.
I use like this :
$model = $this->findModel($id); // model Bukti Penerimaan.
$dataInvoice = $model->invoiceReports; //exist
$dataInvoiceDetail = $model->invoiceReports->invoiceReportDetails // failed, always null.
Please advixe
$dataInvoice = $model->invoiceReports; is Array of InvoiceReport object.
You need to loop over each InvoiceReport to get related InvoiceReportDetail.
$model = $this->findModel($id); // model Bukti Penerimaan.
$dataInvoice = $model->invoiceReports; //exist , but array of objects
$dataInvoiceDetail = [];
foreach($dataInvoice as $dInvoice):
$dataInvoiceDetail[] = array_merge($dataInvoiceDetail,$dInvoice->invoiceReportDetails );
endforeach;
// $dataInvoiceDetail contains all invoice_report_detail
How can I update FOS USER user details using PATCH method. So when I pass partial details in Json, only these details are updated.
User entity
/**
* #ORM\Entity
* #ORM\Table(name="user")
*/
abstract class User extends BaseUser
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string]
* #ORM\Column(name="first_name", type="string", length=255, nullable=true)
*/
private $firstName;
/**
* #var string
* #ORM\Column(name="last_name", type="string", length=255, nullable=true)
*/
private $lastName;
// getters and setters are here
}
For example. I have user Jack Nickolson and my json is:
{"firstName": "John"}
Only first name is updated.
This is my controller. How do I set new parameters to user, without specifing which parameters?
/**
* #Route("/update", name="api_user_update")
* #Security("has_role('ROLE_USER')")
* #Method("PATCH")
*/
public function updateAction(Request $request){
$jsonContent = $request->getContent();
$params = json_decode($jsonContent);
$em = $this->getDoctrine()->getEntityManager();
$response = new JsonResponse();
$user = $this->getUser();
// do something to update user details
$this->get('fos_user.user_manager')->updateUser($user);
$em->persist($user);
$em->flush();
$response->setContent([
"status" => "user ". $user->getUsername() ." is updated"
]);
return $response;
}
UPDATE
I tried to use an answer below, so this what I have now
public function updateAction(Request $request){
$em = $this->getDoctrine()->getEntityManager();
$response = new JsonResponse();
$user = $this->getUser();
$editForm = $this->createForm('CoreBundle\Form\UserType', $user);
$requestData = $request->getContent();
$requestData = json_encode($requestData, true);
$editForm->submit($requestData, false);
$this->get('fos_user.user_manager')->updateUser($user);
$em->persist($user);
$em->flush();
$response->setContent([
"status" => "user ". $user->getUsername() ." is updated"
]);
return $response;
}
Well, my entity was not updated. What am I doing wrong?
You need to prepare and create UserType form for partial updated.
Then, when processing a submitted form, you'll need to pass a false $clearMissing option to your form's submit method call in the controller:
$form->submit($request, false);
Thanks to that option the form component will only update the fields passed from the request.
and then flush user:
$em->flush();
Also if you want send data like:
{"firstName": "John"}
your UserType form should have method:
public function getBlockPrefix()
{
return "";
}
You can also use nice and powerfull https://github.com/FriendsOfSymfony/FOSRestBundle
I am planning to make a reverse geocoding based on the BazingaGeocoderBundle. A simple way to do that is write this simple code in the controller:
$result = $this->container
->get('bazinga_geocoder.geocoder')
->using('google_maps')
->reverse(48.79084170157100,2.42479377175290);
return $this->render("MinnAdsBundle:Motors:test.html.twig",
array('result'=>var_dump($result)));
Until here, things are going well.
My objective is to make the code nicer & resuable. So, I used this article to write my own GeocoderEventSubscriber as describer below:
<?php
namespace Minn\AdsBundle\Doctrine\Event;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use Doctrine\ORM\Event\LifecycleEventArgs;
//use Geocoder\Provider\ProviderInterface;
use Bazinga\Bundle\GeocoderBundle\Geocoder\LoggableGeocoder;
/**
* Subscribes to Doctrine prePersist and preUpdate to update an
* the address components of a MotorsAds entity
*
* #author majallouli
*/
class MotorsAdsGeocoderEventSubscriber implements EventSubscriber {
protected $geocoder;
public function __construct(LoggableGeocoder $geocoder){
$this->geocoder = $geocoder;
}
/**
* Specifies the list of events to listen
*
* #return array
*/
public function getSubscribedEvents(){
return array(
'prePersist',
'preUpdate',
);
}
/**
* Sets a new MotorsAds's address components if not present
*
* #param LifecycleEventArgs $eventArgs
*/
public function prePersist(LifecycleEventArgs $eventArgs){
$motorsAds = $eventArgs->getEntity();
if($motorsAds instanceof \Minn\AdsBundle\Entity\MotorsAds){
if( !$motorsAds->getCountry()){
$em = $eventArgs->getEntityManager();
$this->geocodeMotorsAds($motorsAds,$em);
}
}
}
/**
* Sets an updating MotorsAds's address components if not present
* or any part of address updated
*
* #param PreUpdateEventArgs $eventArgs
*/
public function preUpdate(PreUpdateEventArgs $eventArgs){
$motorsAds = $eventArgs->getEntity();
if($motorsAds instanceof \Minn\AdsBundle\Entity\MotorsAds){
if( !$motorsAds->getCountry() ){
$em = $eventArgs->getEntityManager();
$this->geocodeMotorsAds($motorsAds,$em);
$uow = $em->getUnitOfWork();
$meta = $em->getClassMetadata(get_class($motorsAds));
$uow->recomputeSingleEntityChangeSet($meta, $motorsAds);
}
}
}
/**
* Geocode and set the MotorsAds's address components
*
* #param type $motorsAds
*/
private function geocodeMotorsAds($motorsAds,$em){
$result = $this->geocode
->using('google_maps')
->reverse($motorsAds->getLat(),$motorsAds->getLng());
$motorsAds->setCountry(
$em->getRepository("MinnAdsBundle:Country")->findCountryCode($result['countryCode']));
}
}
After that, I declared my EventSubscriber as a service:
services:
# ...
geocoder_motorsads.listener:
class: Minn\AdsBundle\Doctrine\Event\MotorsAdsGeocoderEventSubscriber
arguments: [#bazinga_geocoder.geocoder] # almost sure that the error is here!!
tags:
- { name: doctrine.event_subscriber }
Actually, I get this error:
ContextErrorException: Notice: Undefined property: Minn\AdsBundle\Doctrine\Event\MotorsAdsGeocoderEventSubscriber::$geocode in /home/amine/NetBeansProjects/tuto/src/Minn/AdsBundle/Doctrine/Event/MotorsAdsGeocoderEventSubscriber.php line 78
I am almost sure that error is in the declaration of arguments of the EventSubscriber. Is it #bazinga_geocoder.geocoder?
Thank you for your help!
Your property is $this->geocoder but you're calling $this->geocode, you're spelling it wrong.