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).
Related
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
This has got to be a simple fix, as I have done this many times before. But as it stands I am completely stumped. I use the following code to save a parent object Unknown_Tag and its many children.
Method:
public function saveUnknown(Request $request)
{
$url = $request->url;
$tag = new Unknown_Tag();
$tag->url = $url;
$protocol =
substr($url, 0, strpos($url, ':'));
$tag->protocol = $protocol;
$domain =
parse_url($url, PHP_URL_HOST);
$tag->domain = $domain;
$tag->save();
//get the path
$Path = parse_url($url, PHP_URL_PATH);
if ($Path) {
$splitPath = substr($Path, 1);
$paths = explode('/', $splitPath);
foreach ($paths as $p) {
$path = new Path();
$path->path = $p;
$tag->Paths()->save($path);
}
}
//get Queries
$splitQuery = parse_url($url, PHP_URL_QUERY);
if ($splitQuery) {
$queries = explode('&', $splitQuery);
foreach ($queries as $q) {
$query = new Query();
$q = substr($q, 0, strpos($q, '='));
IF (SUBSTR($q, -1) != ' ') {
$q .= ' ';
}
$query->var = $q;
$value = $q = preg_replace('/^[^=]*:/', '', $q);
$query->value = $value;
$tag->Queries()->save($query);
}
}
}
The Parent Object
class Unknown_Tag extends Model
{
protected $table = 'unknown_tags';
public $timestamps = false;
public function Paths()
{
return $this->hasMany('App\Path', 'tag_id', 'ID');
}
public function Queries()
{
return $this->hasMany('App\Query', 'tag_id', 'ID');
}
}
The Child objects
class Query extends Model
{
protected $table = 'queries';
public $timestamps = false;
public function Tag()
{
return $this->belongsTo('App\Unknown_Tag', 'tag_id', 'ID');
}
}
class Path extends Model
{
protected $table = 'paths';
public $timestamps = false;
public function Tag()
{
return $this->belongsTo('App\Unknown_Tag', 'tag_id', 'ID');
}
}
When I run all this via a post request, The Parent and all the children are saved properly, but all the child objects have a foreign key that is set to null. If I manually change the foreign key to what it should be, everything works just fine, so I am fairly sure this is not a problem with my database. Can anyone see the obvious that I am missing here?
EDIT:Just to be clear, this returns no errors
If anyone ever sees this, laravel assumes the default primary key is 'id'. I had set mine to 'ID', so I had to let laravel know by using
protected $primaryKey = 'ID';
in my Unknown_tag definition
I am using Doctrine 2 and Zend framework since a few days.
I am generating my entities across yaml files.
Now I met an issue to convert my entities Doctrine into Json format (in order to use it through AJAX).
Here is the code used :
$doctrineobject = $this->entityManager->getRepository('\Entity\MasterProduct')->find($this->_request->id);
$serializer = new \Symfony\Component\Serializer\Serializer(array(new Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer()), array('json' => new Symfony\Component\Serializer\Encoder\JsonEncoder()));
$reports = $serializer->serialize($doctrineobject, 'json');
below is the return I get :
Fatal error: Maximum function nesting level of '100' reached, aborting! in /Users/Sites/library/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php on line 185
the issue seems to be the same than here :
http://comments.gmane.org/gmane.comp.php.symfony.symfony2/2659
but there is not proper solution proposed.
Any idea how can I do it ?
Cheers
I solved the same problem by writing my own GetSetNormalizer my class. Defined static variable in a class for branching
class LimitedRecursiveGetSetMethodNormalizer extends GetSetMethodNormalizer
{
public static $limit=2;
/**
* {#inheritdoc}
*/
public function normalize($object, $format = null)
{
$reflectionObject = new \ReflectionObject($object);
$reflectionMethods = $reflectionObject->getMethods(\ReflectionMethod::IS_PUBLIC);
$attributes = array();
foreach ($reflectionMethods as $method) {
if ($this->isGetMethod($method)) {
$attributeName = strtolower(substr($method->name, 3));
$attributeValue = $method->invoke($object);
if (null !== $attributeValue && !is_scalar($attributeValue) && LimitedRecursiveGetSetMethodNormalizer::$limit>0) {
LimitedRecursiveGetSetMethodNormalizer::$limit--;
$attributeValue = $this->serializer->normalize($attributeValue, $format);
LimitedRecursiveGetSetMethodNormalizer::$limit++;
}
$attributes[$attributeName] = $attributeValue;
}
}
return $attributes;
}
/**
* Checks if a method's name is get.* and can be called without parameters.
*
* #param ReflectionMethod $method the method to check
* #return Boolean whether the method is a getter.
*/
private function isGetMethod(\ReflectionMethod $method)
{
return (
0 === strpos($method->name, 'get') &&
3 < strlen($method->name) &&
0 === $method->getNumberOfRequiredParameters()
);
}
}
And usage
LimitedRecursiveGetSetMethodNormalizer::$limit=3;
$serializer = new Serializer(array(new LimitedRecursiveGetSetMethodNormalizer()), array('json' => new
JsonEncoder()));
$response =new Response($serializer->serialize($YOUR_OBJECT,'json'));
JMSSerializerBundle seems to handle circular references fine.
I have a lumen application where I need to store incoming JSON Request. If I write a code like this:
public function store(Request $request)
{
if ($request->isJson())
{
$data = $request->all();
$transaction = new Transaction();
if (array_key_exists('amount', $data))
$transaction->amount = $data['amount'];
if (array_key_exists('typology', $data))
$transaction->typology = $data['typology'];
$result = $transaction->isValid();
if($result === TRUE )
{
$transaction->save();
return $this->response->created();
}
return $this->response->errorBadRequest($result);
}
return $this->response->errorBadRequest();
}
It works perfectly. But use Request in that mode is boring because I have to check every input field to insert them to my model. Is there a fast way to send request to model?
You can do mass assignment to Eloquent models, but you need to first set the fields on your model that you want to allow to be mass assignable. In your model, set your $fillable array:
class Transaction extends Model {
protected $fillable = ['amount', 'typology'];
}
This will allow the amount and typology to be mass assignable. What this means is that you can assign them through the methods that accept arrays (such as the constructor, or the fill() method).
An example using the constructor:
$data = $request->all();
$transaction = new Transaction($data);
$result = $transaction->isValid();
An example using fill():
$data = $request->all();
$transaction = new Transaction();
$transaction->fill($data);
$result = $transaction->isValid();
You can either use fill method or the constructor. First you must include all mass assignable properties in fillable property of your model
Method 1 (Use constructor)
$transaction = new Transaction($request->all());
Method 2 (Use fill method)
$transaction = new Transaction();
$transaction->fill($request->all());
Create your TransactionRequest with rules extends FormRequest
public function store(TransactionRequest $request)
{
$transaction = new Transaction($request->validated());
$transaction->save();
}
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.