I have an one to one relation in my sonata admin bundle.
Person <-> Player.
If i go to my Person admin i can choose the player from the choice list.
If i go to another Person object the player (which is now already assigned) is shown again ... if i choose it, there occurs an error ...
The error is
Failed to update object: AppBundle\Entity\Person
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '18' for key 'UNIQ_9A5FDF45F6B75B26'
Can i change the behaviour that already assigned values do not appear in other objects?
I cant find a option for doing this in sonata admin
You can generate a custom choice list for every Person.
Add method to PlayerRepository:
class PlayerRepository extends EntityRepository
{
public function getNotAssociatedPlayers($person)
{
$qb = $this->createQueryBuilder('p')
->leftJoin('p.person', 'prsn')
->where('prsn.id is null');
if($person !== null)
{
$qb->orWhere('prsn.id = :person')
->setParameter('person', $person);
}
return $qb->getQuery()
->getResult();
}
}
And use it in PersonAdmin:
protected function configureFormFields(FormMapper $formMapper)
{
$person = ($this->getSubject()->getPlayer() !== null)
? $this->getSubject()->getId()
: null;
$em = $this->getConfigurationPool()->getContainer()->get('doctrine.orm.entity_manager');
$choices = $em->getRepository('YourBundle:Player')->getNotAssociatedPlayers($person);
$formMapper
->add('player', null, array(
'choices' => $choices,
));
Please, let me know if something went wrong with that.
EDIT:
Documented solution is a query_builder option for the field:
$formMapper
->add('player', null, array(
'query_builder' => function(EntityRepository $er)
{
$qb = $er->createQueryBuilder('p')
->leftJoin('p.person', 'prsn')
->where('prsn.id is null');
if($this->getSubject()->getPlayer() !== null)
{
$qb->orWhere('prsn.id = :person')
->setParameter('person', $this->getSubject()->getId());
}
return $qb;
}
));
Related
I am working on an action which uses layered Relations.
So i have a Player Entity, which has a Relation OwnedCard. Which has a Relation CardLevel which has a Relation card.
So i am using
/**
* #param Player $player
* #Route("/{id}/cards", name="loki.tuo.ownedcard.cards.show", requirements={"id":"\d+"})
*
* #ParamConverter("player", class="LokiTuoResultBundle:Player", options={"repository_method" = "findWithOwnedCards"})
* #return Response
* #Security("is_granted('view.player', player)")
*/
public function showCardsForPlayerAction(Player $player)
{
$allCards = $player->getOwnedCards();
$allCards = Collection::make($allCards)->sortBy(function (OwnedCard $elem) {
//$elem->getCard() calls the getName() method on CardLevel which delegates it to Card
return $elem->getCard()->getName();
});
$deck = $allCards->filter(function (OwnedCard $item) {
return $item->getAmountInDeck() > 0;
});
$combined = $deck->map(function (OwnedCard $item) {
return $item->toDeckString();
});
$formOptions = ['attr' => ['class' => 'data-remote']];
$ownedCardForm = $this->createForm(OwnedCardType::class, null, $formOptions);
$massOwnedCardForm = $this->createForm(MassOwnedCardType::class, null, [
'action' => $this->generateUrl('loki.tuo.ownedcard.card.add.mass', ['id' => $player->getId()]),
'method' => 'POST',
]);
//Render Template
For this I created a method which Joins and Selects these Relations
public function findWithOwnedCards($id)
{
$qb = $this->createQueryBuilder('player')
->join('player.ownedCards', 'ownedCards')
->join('ownedCards.card', 'cardLevel')
->join('cardLevel.card', 'card')
->addSelect(['ownedCards'])
->addSelect(['cardLevel'])
->addSelect(['card'])
->where('player.id = :id')
->setParameter('id', $id);
return $qb->getQuery()->getSingleResult();
}
But Unfortunately the Symfony Profiler tells me, there are a lot calls like
SELECT * FROM card_level WHERE card_id = ?
(I shortened the Query for better readability)
So this means, at some point Symfony/Doctrine doesnt use the Joined Relationships but somehow thinks they are lazy loaded, and needs to fetch them.
So now my Question: How can I find out, where or when the queries are executed? Is there some point in the Code where I could set a breakpoint or throw an Exception to see a stacktrace to see where this comes from?
Try setting 'fetch' property to "EAGER" for your associations
Here's an example from doctrine docs
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/annotations-reference.html#manytoone
I would need a little help. I'm trying to implement a search bar with symfony, this search bar in the database. What I can do but the problem is that I absolutely have to put the full name to find the person (s) corresponds to that name. Except that I would like to be able to retrieve all the people starting with the beginning of the name I entered. For example :
I type "dub" and I find: "Dubois", "Dubost", "Dubububu", ....
public function searchAction (Request $request)
{
$defaultData = array('search' => 'Type your search here');
$form = $this->createFormBuilder()
->add('search', TextType::class, array('label'=>'Surname ?','required'=>true))
->add('send', SubmitType::class)
->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
$repository = $this
->getDoctrine()
->getManager()
->getRepository('PegasusWebBundle:Clients')
;
//$clients = $repository->findBySurname($form->get('search')->getData()); // Method 1
$clients = $repository->myFindname($form->get('search')->getData()); // Method 2 with Repository
return $this->render('PegasusWebBundle:Default:searchresult.html.twig',array(
'clients'=> $clients ));
}
else
return $this->render('PegasusWebBundle:Default:search.html.twig', array(
'form' => $form->createView(),
));
}
The repository for the method 2
`class ClientsRepository extends \Doctrine\ORM\EntityRepository
{
public function myFindname($name)
{
// Method 2
$qb = $this->createQueryBuilder('a');
$qb->where(
$qb->expr()->like('a.surname' ,':surname'))
->setParameter('surname', $name);
return $qb
->getQuery()
->getResult();
}
}`
Try adding the wildchar eg:
$query = $repo->createQueryBuilder('a')
->where('a.surname LIKE :surname')
->setParameter('surname', '%'.$name.'%')
->getQuery();
Try adding a SQL wildcard character (%) after the surname value.
Also See this as reference.
This is a category table I am using in my project using Laravel.
I have checks applied in the view files, for the category parent selection dropdown, so that the category itself and it's child's will not appear in the dropdown.
But form input fields value can be easily overridden using dev console.
Is there a way in models so that if parent id is equal to the category id itself or parent id is the child of current category then it will stop execution.
I have recently started laravel, a month ago, and still learning and building, so help here will be appreciated.
I was able to resolve the issue by overriding the update method in model -
Controller update method -
public function update(Request $request, $id)
{
$this->validate($request,
['name' => 'required',]);
$data = [];
$data = ['name' => Input::get('name'),
'parent' => !empty(Input::get('parent')) ? Posts_categories::find(Input::get('parent'))->id : NULL,];
$category = Posts_categories::find($id);
if(is_null($category))
{
Session::flash('flash-message', 'Category type with the given id does not exist.');
Session::flash('alert-class', 'alert-warning');
return redirect()->route('admin.post.category.index');
}
if($category->update($data)) {
Session::flash('flash-message', 'Category succesfully updated.');
Session::flash('alert-class', 'alert-success');
}
return redirect()->route('admin.post.category.index');
}
Model update method -
public function update(array $attributes = [], array $options = [])
{
$parent = SELF::find($attributes['parent']);
if($this->id == $parent->id || $this->id == $parent->parent)
{
Session::flash('flash-message', 'Invalid parent selection for category.');
Session::flash('alert-class', 'alert-warning');
return 0;
}
return parent::update($attributes, $options); // TODO: Change the autogenerated stub
}
I am working on yii and want a functionality to auto insert created , modified and user_id(my column names in db). I am currently doing this with following way . I have to add this code in every model .
public function rules()
{
return array(
......
array('created, modified', 'default', 'value'=>new CDbExpression('NOW()'), 'setOnEmpty' => false, 'on' => 'insert'),
array('modified', 'default', 'value' => new CDbExpression('NOW()'), 'setOnEmpty' => false, 'on' => 'update'),
array('user_id', 'default', 'value' => Yii::app()->user->id, 'setOnEmpty' => false,'on' => 'insert'),
array('id, feed_id, user_id, text, created, modified', 'safe', 'on'=>'search'),
..........
);
}
this is working on insert and update, But I want is
That if here is a method so that i have to insert it in one file and
no need to insert this in every model . If it is possible
If you have several models and want to implement common behaviour on them, you can use a custom component and use any of given methods in comments and other answers (behaviors, rules, beforeSave, etc) and extending it for all models.
Create a new file in protected/components called for example MasterModel.php. In this example I want to inherit beforeSave method for all models. Fill MasterModel.php with:
<?php
abstract class MasterModel extends ActiveRecord
{
public function beforeSave()
{
$current_time = date('Y-m-d H:i:s');
if ( $this->isNewRecord )
{
$this->created = $current_time;
$this->created_by = Yii::app()->user->id;
}
if ( ! $this->isNewRecord )
{
$this->updated = $current_time;
$this->updated_by = Yii::app()->user->id;
}
return parent::beforeSave();
}
}
Replace on all your existing and future model definitions:
<?php
class Client extends ActiveRecord
{
....
With:
<?php
class Client extends MasterModel
{
....
Make sure to have on your database tables and models:
created DATETIME
created_by INT
updated DATETIME
updated_by INT
You can do this in three ways:
1) Update via model’s rules:
public function rules()
{
return array(
array('title','length','max'=>255),
array('title, created_at, updated_at', 'required'),
array('updated_at','default',
'value'=>new CDbExpression('NOW()'),
'setOnEmpty'=>false,'on'=>'update'),
array('created_at,updated_at','default',
'value'=>new CDbExpression('NOW()'),
'setOnEmpty'=>false,'on'=>'insert'),
array('user_id','default',
'value'=> Yii::app()->user->id,
'setOnEmpty'=>false,'on'=>'insert')
);
}
2) Another to use beforeSave() as follows:
public function beforeSave() {
if ($this->isNewRecord)
$this->created_at = new CDbExpression('NOW()');
$this->user_id = Yii::app()->user->id;
$this->updated_at = new CDbExpression('NOW()');
return parent::beforeSave();
}
3) Another alternative to use CTimestampBehavior in your models:
public function behaviors()
{
return array(
'CTimestampBehavior'=>array(
'class'=>'zii.behaviors.CTimestampBehavior',
'createAttribute'=>'created_at',
'updateAttribute'=>'updated_at',
'setUpdateOnCreate'=>true,
'timestampExpression'=>new CDbExpression('NOW()');
)
);
}
Make sure your every table has same field name created_at, updated_at and user_id.
I found a very usefull article here how to insert autofill yii model data
you just need to create a class as #Alejandro Quiroz answered . The issue in that answer was if field is not available it throws an exception so here is the best solution i found. you need to check if attribute available with if($this->hasAttribute('modified'))
public function beforeSave()
{
$current_time = date('Y-m-d H:i:s');
if ( $this->isNewRecord )
{
if($this->hasAttribute('created'))
$this->created = $current_time;
if($this->hasAttribute('modified'))
$this->modified = $current_time;
if($this->hasAttribute('user_id')) // make sure user field name i user_id
$this->user_id = Yii::app()->user->id;
}
if ( ! $this->isNewRecord )
{
if($this->hasAttribute('modified'))
$this->modified = $current_time;
/* remove this if want updated by id */
//$this->updated_by = Yii::app()->user->id;
}
return parent::beforeSave();
}
I am trying to do a form in which I will be able to add as many product options as I want (so collection with prototype). For this I need two fields, one Option Definition and second with Option values.
I have got entities like: Product, Options, Options Definitions and Product Options Join (I decided to use two ManyToOne instead of one ManyToMany, so in Product Definitions Join I got ManyToMany to Options and ManyToMany to Products).
When I am adding option to product, I want first to choose Option Definition and then update select box with Options. This is everything working alright, but the problem is that when I want to edit product then it is showing me default option definitions. For example, when I was creating product I added product with option definition size and the value of size was 40. When I going to edit product I am getting option definition color and size 40.
Here is on pictures what I mean:
And in second row there should be a Size instead of Colour.
Here you can also see how I got connected tables:
ProductOptionsType Class
public function buildForm(FormBuilderInterface $builder, array $options)
{
$factory = $builder->getFormFactory();
$builder
->add('optiondefinitions', 'entity', array(
'mapped' => false,
'class' => 'ePOS\ProductsBundle\Entity\OptionsDefinitions',
'query_builder' => function ($repository) { return $repository->createQueryBuilder('p')->orderBy('p.name', 'ASC'); },
'property' => 'name',
'attr' => array('class' => 'option-definitions')))
;
$refreshOptions = function ($form, $option) use ($factory) {
$form->add($factory->createNamed('options', 'entity', null, array(
'class' => 'ePOS\ProductsBundle\Entity\Options',
'property' => 'value',
'label' => false,
'auto_initialize' => false,
'query_builder' => function ($repository) use ($option) {
$qb = $repository->createQueryBuilder('options')
->innerJoin('options.optionDefinition', 'optionDefinitions');
if($option instanceof OptionsDefinitions) {
$qb = $qb->where('options.optionDefinition = :optiondefinitions')
->setParameter('optiondefinitions', $option);
} elseif(is_numeric($option)) {
$qb = $qb->where('options.id = :option_id')
->setParameter('option_id', $option);
} else {
$qb = $qb->where('options.optionDefinition = 1');
}
return $qb;
}
)));
};
$builder->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $event) use ($refreshOptions, $factory) {
$data = $event->getData();
$form = $event->getForm();
if (null === $data) {
return ;
}
$refreshOptions($form, $data->getOptions()->getOptionDefinition());
});
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($refreshOptions) {
$form = $event->getForm();
$data = $event->getData();
if($data == null)
$refreshOptions($form, null); //As of beta2, when a form is created setData(null) is called first
if($data instanceof Location) {
$refreshOptions($form, $data->getOptions()->getOptionDefinition());
}
});
$builder->addEventListener(FormEvents::PRE_BIND, function (DataEvent $event) use ($refreshOptions) {
$form = $event->getForm();
$data = $event->getData();
if(array_key_exists('optiondefinitions', $data)) {
$refreshOptions($form, $data['optiondefinitions']);
}
});
}