I would like to create a ManyToMany relationship with Symfony3/doctrine (The entities are 'Categorie' and 'Service')
So I have two forms to create this entites.
The first form (Categorie) works properly but not the second (Service) : The new service is not related to categories, i don't understand :
Categorie.php
/**
* Categorie
*
* #ORM\Table(name="categorie")
* #ORM\Entity(repositoryClass="GestionBundle\Repository\CategorieRepository")
*/
class Categorie
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity="Service", inversedBy="categories")
*/
private $services;
/**
* Categorie constructor.
*/
public function __construct()
{
$this->services = new ArrayCollection();
}
[...]
}
Service.php
/**
* Service
*
* #ORM\Table(name="service")
* #ORM\Entity(repositoryClass="GestionBundle\Repository\ServiceRepository")
*/
class Service
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity="Categorie", mappedBy="services")
*/
private $categories;
/**
* #var string
*
* #ORM\Column(name="nom", type="string", length=255, unique=true)
*/
private $nom;
/**
* Categorie constructor.
*/
public function __construct()
{
$this->categories = new ArrayCollection();
}
[...]
}
CategorieType.php
class CategorieType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('nom')
->add('services', EntityType::class, array(
'class' => 'GestionBundle:Service',
'choice_label' => 'nom',
'multiple' => true,
'required' => false))
->add('Ajouter', SubmitType::class)
;
}
[...]
}
ServiceType.php
class ServiceType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('nom')
->add('categories', EntityType::class, array(
'class' => 'GestionBundle:Categorie',
'choice_label' => 'nom',
'multiple' => true,
'required' => false))
->add('Ajouter', SubmitType::class)
;
}
[...]
}
Controller :
/**
* #Route("/Categories/Creation", name="route_gestion_eltcoord_categories_creation")
* #Template()
*/
public function CreationCategorieAction(Request $request)
{
$Categorie = new Categorie();
$form = $this->createForm(CategorieType::class, $Categorie);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($Categorie);
$em->flush();
return $this->redirectToRoute('route_gestion_eltcoord_categories');
}
return array('form' => $form->createView());
}
/**
* #Route("/Services/Creation", name="route_gestion_eltcoord_services_creation")
* #Template()
*/
public function CreationServiceAction(Request $request)
{
$Service = new Service();
$form = $this->createForm(ServiceType::class, $Service);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($Service);
$em->flush();
return $this->redirectToRoute('route_gestion_eltcoord_services');
}
return array('form' => $form->createView());
}
Thank you.
As I understand you wan't nested form from category root form ?
Try CollectionType::class instead of EntityType::class in service form builder, configure options (at less entry_type to map entity class).
Related documentation for configuration here.
And here there is example from Symfony cookbook.
In a ManyToMany relations you must create a joined table like in this post, so in that way doctrine can create a third entity for describe the relation.
Hope this help you.
I already have the joined table.
Here is an example :
New 'Service' S1 (Form Service) :
Service Categorie categorie_service
+------+-------+ +------+-------+ +--------+--------+
| id | Name | | id | Name | | id_c | id_s |
+------+-------+ +------+-------+ +--------+--------+
| 1 | S1 |
+------+-------+
New 'Categorie' C1 related to S1 (Form Categorie) :
Service Categorie categorie_service
+------+-------+ +------+-------+ +--------+--------+
| id | Name | | id | Name | | id_c | id_s |
+------+-------+ +------+-------+ +--------+--------+
| 1 | S1 | | 1 | C1 | | 1 | 1 |
+------+-------+ +------+-------+ +--------+--------+
New 'Service' S2 related to C1 (Form Service) :
Service Categorie categorie_service
+------+-------+ +------+-------+ +--------+--------+
| id | Name | | id | Name | | id_c | id_s |
+------+-------+ +------+-------+ +--------+--------+
| 1 | S1 | | 1 | C1 | | 1 | 1 |
+------+-------+ +------+-------+ +--------+--------+
| 2 | S2 |
+------+-------+
doesn't work ...
I should have this:
categorie_service
+--------+--------+
| id_c | id_s |
+--------+--------+
| 1 | 1 |
+--------+--------+
| 1 | 2 |
+--------+--------+
;p
I added this :
$categories = $form->getData()->getCategories();
foreach ($categories as $categorie) {
$categorie->addService($service);
}
into 'CreationServiceAction' and before call $em->persist...
Now it's ok.
Related
I have a concern with the persistence of my entities for a ManyToOne Unidirectionnal relationship. In fact I have an entity Order linked to a ticket entity, in the order form I have a choices to select the ticket.
This is foreign key in Command Entity
/**
* #ORM\Entity(repositoryClass="App\Repository\CommandRepository")
*/
class Command
{
….
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Ticket")
* #ORM\JoinColumn(nullable=false, name="Ticket_id", referencedColumnName="id")
*/
private $ticket;
...
the Builder of CommandForm is
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('datevisite', DateType::class,['widget' => 'choice',
'format' => 'dd-MM-yyyy', 'html5' => false,'label'=>'Date de la visite', 'attr'=>['class' => 'js-datepicker'] ])
->add('ticket', EntityType::class,['required'=>true, 'class' => Ticket::class,'choice_label'=>'nombillet', 'attr'=>['placeholder'=>'Choisissez le type de billet']])
->add('email', EmailType::class,['required'=>true, 'label'=>'Votre mail', 'attr'=>['placeholder'=>'Entrez votre adresse mail ']])
;
When I save (persist) an Command with
$manager->persist($command);
$manager->flush();
instead of registering the command with the selected ticket in the drop-down list, a new ticket is automatically created and assigned to the command.
please help me to persist only the Command with the foreign key of existing ticket (selected)
Thanks
my controller
<?php
namespace App\Controller;
use App\Entity\Command;
use App\Notification\NotificationContact;
use App\Entity\Typebillet;
use App\Entity\Typetarif;
use App\Entity\Visiteur;
use App\Form\Type\CommandType;
use App\Module\Module;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\CountryType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
class LouvreController extends AbstractController
{
private $lacommand ;
private $session;
public function __construct()
{
$this->lacommand = new Command();
}
/**
* #Route("/billet/reservation", name="louvre_billet")
*/
public function billet(Request $request, SessionInterface $session){
// $this->lacommand = new command();
//$form = $this->createFormBuilder($command)
$form = $this->createForm(CommandType::class, $this->lacommand);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->lacommand->setDatecommande(new \DateTime());
$today = new \DateTime();
if ( Module::CommandeJourPasse($this->lacommand->getDatevisite()) == -1 ) {
return $this->rediriger('danger', 'Attention vous ne pouvez commander un billet pour un jour passé!!!', 'louvre_billet');
}
if ( Module::AuDela14H($this->lacommand->getTypebillet()->getId(), $this->lacommand->getDatevisite() ) == -1 ) {
return $this->rediriger('danger', 'Attention vous ne pouvez commander un billet journée au delà de 14 le jour même', 'louvre_billet');
}
if ( $this->recherchetarif($this->lacommand) === -1) {
$this->addFlash('danger', 'Attention vous devez enregistrer au moins un visiteur ' );
return $this->redirectToRoute('louvre_billet', array(), 301);
}
$nbrebilletDuJourDeVisite = $this->getDoctrine()
->getRepository(Command::class)
->sumNumberVisite( $this->lacommand->getDatevisite()->format('Y-m-d') );
if (($nbrebilletDuJourDeVisite + $this->lacommand->getNombrebillet()) > 1000) {
$dispo = 1000 - $nbrebilletDuJourDeVisite;
$this->addFlash('danger', 'Attention votre commande ne peut être effectuée, car la capacité d\' accueil journalière est limitée à 1000 visites. Actuellemnt '.$dispo.' billet(s) disponible(s)' );
return $this->redirectToRoute('louvre_billet', array(), 301);
}
$verif = $this->verifJourOuvrables($this->lacommand->getDatevisite());
if($verif['error'] >0) {
$this->addFlash('danger', $verif['message'] );
return $this->redirectToRoute('louvre_billet', array(), 301);
}
$session->set('command', $this->lacommand);
return $this->paie($this->lacommand);
}
return $this->render('louvre/resa.html.twig', array(
'formCommand' => $form->createView(),
));
}
public function paie(Command $command){
return $this->render('louvre/paiement.html.twig', [
'datevisite'=> $command->getDatevisite()->format('d-M-Y'),
'nombrebillet' => $command->getNombrebillet(),
'montantnet' => $command->getMontantnet(),
'email' => $command->getEmail() ,
'command'=>$command] );
}
/**
* #Route("/billet/paiement", name="le_paiement")
*/
public function paiement( Request $request, ObjectManager $manager, SessionInterface $session , \Swift_Mailer $mailer){
if ($request->request->get('stripeToken') !== null) {
$command = $session->get('command');
/* dump($command);
die();*/
$montantnetCent = $command->getMontantnet() * 100;
try{
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
\Stripe\Stripe::setApiKey("sk_test_0thjJ32tl0y6X8kc5Wdz0XSt");
// Token is created using Checkout or Elements!
// Get the payment token ID submitted by the form:
$token = $request->request->get('stripeToken');
$charge = \Stripe\Charge::create([
'amount' => $montantnetCent,
'currency' => 'eur',
'description' => 'Achat billet musée de Louvre -- '.$command->getEmail(),
'source' => $token,
]);
}
catch (Exception $e) {
$error = $e->getMessage();
$this->addFlash('danger', $error );
return $this->redirectToRoute('louvre_billet', array(), 301);
}
/**
* ici on va générer et insérer le code
*/
$code = $command->getDatevisite()->format('ymd')."-".substr($command->getTypebillet()->getNombillet(),0,4)."-".uniqid();
$command->setCode($code);
$manager->persist($command);
$manager->flush($command);
/**
* envoie de mail
*/
$message = (new \Swift_Message('Musée de LOUVRE : Votre reservation '))
->setFrom('louvre#museelouvre.com')
->setTo($command->getEmail())
->setBody(
$this->renderView(
// templates/emails/registration.html.twig
'emails/mail.html.twig',
array('datevisite' => $command->getDatevisite(),
'typebillet' => $command->getTypebillet()->getNombillet(),
'montantnet' => $command->getMontantnet(),
'visiteurs' => $command->getVisiteurs(),
'code' => $command-> getCode(),
)
),
'text/html'
);
$mailer->send($message);
dump($mailer->send($message));
/**
* fin envoie
*/
return $this->render(
// templates/emails/registration.html.twig
'emails/mail.html.twig',
array('datevisite' => $command->getDatevisite(),
'typebillet' => $command->getTypebillet()->getNombillet(),
'montantnet' => $command->getMontantnet(),
'visiteurs' => $command->getVisiteurs(),
'code' => $command->getCode()
));
/*$this->addFlash('success', 'Paiement effectué avec succes ' );
return $this->redirectToRoute('louvre_billet', array(), 301); */
}
else{
$this->addFlash('danger', 'Un probleme est survenu lors du paiement ' );
return $this->redirectToRoute('louvre_billet', array(), 301);
}
}
public function recherchetarif(Command $command){
$tarif = new Typetarif();
$today = new \DateTime();
$reduction = 0;
$montantbrut = 0;
$lesvisiteurs= new ArrayCollection();
$manager = $this->getDoctrine()->getManager();
if ($command->getVisiteurs()->count() == 0) {
return -1;
}
foreach ($command->getVisiteurs() as $visiteur) {
# code...
/**
* On calcule l'age de chaque visiteur et on recupère le tarif correspondant
*/
$datenais = $visiteur->getDatenaissance()->format('Y-m-d');
$datenais = new \DateTime($datenais);
$age = date_diff($today, $datenais);
$tarif = $this->getDoctrine()
->getRepository(Typetarif::class)
->findCostByAge($age->y);
// $command->removeVisiteur($visiteur);
$visiteur->setTypetarif($tarif);
$montantbrut = $montantbrut + $tarif->getTarifmontant();
$lesvisiteurs->add($visiteur);
//$manager->persist($visiteur->getTypetarif());
}
// on calcul le montant brut de la facture brut = PUBillet x Nbre de billet
//$montantbrut = $tarif->getTarifmontant() * $command->getNombrebillet();
$command->setMontantbrut ($montantbrut);
$command->setNombrebillet($command->getVisiteurs()->count());
// On impute une éventuelle reduction de 10 euros
if ( $command->getTarifreduit()==true) {
$reduction = 10;
}
$command->setMontantreduit($reduction);
// on applique le montant net = montant brut - reduction
$command->setMontantnet($montantbrut - $reduction);
return 0;
}
public function verifJourOuvrables($datevisite)
{ $error = 0;
$message = '';
if ($datevisite->format('N') == 2) {
$error = $error + 1;
$message = 'Désolé le musée n\' pas oiuvert le Mardi !!!';
return ['error'=>$error, 'message'=>$message];
}
if ($datevisite->format('j') == 1 and $datevisite->format('m')==5) {
$error = $error + 1;
$message = 'Désolé le musée n\' ouvre pas le 1er MAI !!!';
return ['error'=>$error, 'message'=>$message];
}
if ($datevisite->format('j') == 1 and $datevisite->format('m')==11) {
$error = $error + 1;
$message = 'Désolé le musée n\' ouvre pas le 1er Novembre !!!';
return ['error'=>$error, 'message'=>$message];
}
if ($datevisite->format('j') == 25 and $datevisite->format('m')==12) {
$error = $error + 1;
$message = 'Désolé le musée n\' ouvre pas le 25 Decembre !!!';
return ['error'=>$error, 'message'=>$message];
}
}
public function rediriger($type, $message, $route){
$this->addFlash($type , $message );
return $this->redirectToRoute($route, array(), 301);
}
}
In this Controller, the function use to persist all the Entities is
/**
* #Route("/billet/paiement", name="le_paiement")
*/
public function paiement( Request $request, ObjectManager $manager, SessionInterface $session , \Swift_Mailer $mailer){
if ($request->request->get('stripeToken') !== null) {
$command = $session->get('command');
/* dump($command);
die();*/
$montantnetCent = $command->getMontantnet() * 100;
try{
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
\Stripe\Stripe::setApiKey("sk_test_0thjJ32tl0y6X8kc5Wdz0XSt");
// Token is created using Checkout or Elements!
// Get the payment token ID submitted by the form:
$token = $request->request->get('stripeToken');
$charge = \Stripe\Charge::create([
'amount' => $montantnetCent,
'currency' => 'eur',
'description' => 'Achat billet musée de Louvre -- '.$command->getEmail(),
'source' => $token,
]);
}
catch (Exception $e) {
$error = $e->getMessage();
$this->addFlash('danger', $error );
return $this->redirectToRoute('louvre_billet', array(), 301);
}
/**
* ici on va générer et insérer le code
*/
$code = $command->getDatevisite()->format('ymd')."-".substr($command->getTypebillet()->getNombillet(),0,4)."-".uniqid();
$command->setCode($code);
$manager->persist($command);
$manager->flush($command);
/**
* envoie de mail
*/
$message = (new \Swift_Message('Musée de LOUVRE : Votre reservation '))
->setFrom('louvre#museelouvre.com')
->setTo($command->getEmail())
->setBody(
$this->renderView(
// templates/emails/registration.html.twig
'emails/mail.html.twig',
array('datevisite' => $command->getDatevisite(),
'typebillet' => $command->getTypebillet()->getNombillet(),
'montantnet' => $command->getMontantnet(),
'visiteurs' => $command->getVisiteurs(),
'code' => $command-> getCode(),
)
),
'text/html'
);
$mailer->send($message);
dump($mailer->send($message));
/**
* fin envoie
*/
return $this->render(
// templates/emails/registration.html.twig
'emails/mail.html.twig',
array('datevisite' => $command->getDatevisite(),
'typebillet' => $command->getTypebillet()->getNombillet(),
'montantnet' => $command->getMontantnet(),
'visiteurs' => $command->getVisiteurs(),
'code' => $command->getCode()
));
/*$this->addFlash('success', 'Paiement effectué avec succes ' );
return $this->redirectToRoute('louvre_billet', array(), 301); */
}
else{
$this->addFlash('danger', 'Un probleme est survenu lors du paiement ' );
return $this->redirectToRoute('louvre_billet', array(), 301);
}
}
Thanks
How to write right test, in order to test the below csv data stored in database table. In the input other than item, anything could be optional.
In this, item is key, rest all goes as part of json format typically it looks like this in database {"brand": "Brand6", "category": "Category6", "subcategory": "Sub-Category6"}
Input:
item,category,subcategory,brand,type,feature
TEST-ITEM6,Category6,Sub-Category6,Brand6
TEST-ITEM7,Category7,Sub-Category7,Brand7,TYPE7,FEATURE7
TEST-ITEM8,Category8,Sub-Category8,Brand8,TYPE8,FEATURE8
Test case tried:
def "Case 3a. Verify New 2 records with two more additional fields along with earlier fields to the same tenant"() {
expect:
sql().eachRow("SELECT * FROM item WHERE item IN ('"+item+"')") { row ->
def dbItem = row[0]
def dbAttributes = getJsonToObject(row[1])
def dbCategory = dbAttributes.getAt("category").toString()
def dbSubCategory = dbAttributes.getAt("subcategory").toString()
def dbBrand = dbAttributes.getAt("brand").toString()
def dbType = dbAttributes.getAt("type").toString()
def dbFeature = dbAttributes.getAt("feature").toString()
assert dbItem == item
assert category == dbCategory
assert subcategory == dbSubCategory
assert brand == dbBrand
assert type == dbType
assert feature == dbFeature
}
where:
item << ['TEST-ITEM6', 'TEST-ITEM7', 'TEST-ITEM8']
category << ['Category6','Category7', 'Category8']
subcategory << ['Sub-Category6','Sub-Category7', 'Sub-Category8']
brand << ['Brand6','Brand7', 'Brand8']
type << ['TYPE7', 'TYPE8']
feature << ['FEATURE7', 'FEATURE8']
}
Error:
Condition not satisfied:
type == dbType
| | |
TYPE8| TYPE7
false
1 difference (80% similarity)
TYPE(8)
TYPE(7)
Expected :TYPE7
Actual :TYPE8
In this case I would recommend to use Data Tables as it becomes more readable and resembles your input more closely.
And while type and feature are optional, you need to provide some value for it. It could be null or it could be an empty List or Map (if an Item can have more than one type/feature)
So you where block might look like this:
item | category | subcategory | brand | typeFeatureMap
'TEST-ITEM6' | 'Category6' | 'Sub-Category6' | 'Brand6' | [:] // empty
'TEST-ITEM7' | 'Category7' | 'Sub-Category7' | 'Brand7' | ['TYPE7':'FEATURE7']
'TEST-ITEM8' | 'Category8' | 'Sub-Category8' | 'Brand8' | ['TYPE8':'FEATURE8']
I would also recommend to collect the data and then compare it, so you get around ordering issues.
So bofore your eachRow do something like
def itemFeatures = [:]
In your eachRow do something like
itemFeatures.put(dbAttributes.getAt("type").toString(), dbAttributes.getAt("feature").toString())
And afterwards
itemFeatures == typeFeatureMap
While not answering your question, I would recommend to think about separating the tests from your database if possible.
If you create separate tests for an database abstraction layer and your business logic, you'll be more happy in the long run ;)
For the optional fields you can use the Elvis operator ?: like this (sorry, long code, I modeled your database and two new test cases, one with many optional fields and one failing test):
package de.scrum_master.stackoverflow
import spock.lang.Specification
import spock.lang.Unroll
class DataTableWithOptionalItemsTest extends Specification {
#Unroll
def "Case 3a. Verify record '#item' with possibly optional fields"() {
expect:
testData[item].each { row ->
def dbItem = row["item"]
def dbCategory = row["category"]
def dbSubCategory = row["subcategory"]
def dbBrand = row["brand"]
def dbType = row["type"]
def dbFeature = row["feature"]
assert dbItem == item
assert (category ?: dbCategory) == dbCategory
assert (subcategory ?: dbSubCategory) == dbSubCategory
assert (brand ?: dbBrand) == dbBrand
assert (type ?: dbType) == dbType
assert (feature ?: dbFeature) == dbFeature
}
where:
item | category | subcategory | brand | type | feature
'TEST-ITEM6' | 'Category6' | 'Sub-Category6' | 'Brand6' | null | null
'TEST-ITEM7' | 'Category7' | 'Sub-Category7' | 'Brand7' | 'TYPE7' | 'FEATURE7'
'TEST-ITEM8' | 'Category8' | 'Sub-Category8' | 'Brand8' | 'TYPE8' | 'FEATURE8'
'TEST-ITEM9' | null | null | null | null | null
'TEST-FAIL' | 'CategoryX' | 'Sub-CategoryX' | 'BrandX' | 'TYPEX' | 'FEATUREX'
}
static final testData = [
'TEST-ITEM6': [
[
item : 'TEST-ITEM6',
category : 'Category6',
subcategory: 'Sub-Category6',
brand : 'Brand6',
type : 'dummy',
feature : null
],
[
item : 'TEST-ITEM6',
category : 'Category6',
subcategory: 'Sub-Category6',
brand : 'Brand6',
type : null,
feature : "foo"
]
],
'TEST-ITEM7': [
[
item : 'TEST-ITEM7',
category : 'Category7',
subcategory: 'Sub-Category7',
brand : 'Brand7',
type : 'TYPE7',
feature : 'FEATURE7'
],
[
item : 'TEST-ITEM7',
category : 'Category7',
subcategory: 'Sub-Category7',
brand : 'Brand7',
type : 'TYPE7',
feature : 'FEATURE7'
]
],
'TEST-ITEM8': [
[
item : 'TEST-ITEM8',
category : 'Category8',
subcategory: 'Sub-Category8',
brand : 'Brand8',
type : 'TYPE8',
feature : 'FEATURE8'
],
[
item : 'TEST-ITEM8',
category : 'Category8',
subcategory: 'Sub-Category8',
brand : 'Brand8',
type : 'TYPE8',
feature : 'FEATURE8'
]
],
'TEST-ITEM9': [
[
item : 'TEST-ITEM9',
category : 'Category1',
subcategory: 'Sub-Category1',
brand : 'Brand1',
type : 'TYPE1',
feature : 'FEATURE1'
],
[
item : 'TEST-ITEM9',
category : null,
subcategory: null,
brand : null,
type : null,
feature : null
]
],
'TEST-FAIL' : [
[
item : 'TEST-FAIL',
category : 'CategoryX',
subcategory: 'Sub-CategoryX',
brand : 'BrandY',
type : 'TYPEX',
feature : 'FEATUREX'
]
]
]
}
I have a simple Unit test in which I'm trying to create a new record in "orders" table . So when the test is run it throws an exception :
[yii\db\IntegrityException] SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'created_by' cannot be null
I guess this is due to the so called "BlamemableBehavior" trying to update the "created_by" column . So I tried to detach it and to manually pass the "created_by" value . Neither of both worked . Can you please help ?
<?php
namespace frontend\tests\unit\models;
// use common\fixtures\UserFixture;
use frontend\components\Payments;
use common\models\order\Order;
class RandomTest extends \Codeception\Test\Unit
{
/**
* #var \frontend\tests\UnitTester
*/
protected $tester;
protected $order;
public function _before(){
//this doesn't work
$this->order = $this->tester->haveRecord(Order::class,
[
'id' => 577,
'payment_type' => 4,
'status' => 1,
'amount' => 1,
'created_by' => 561,
'updated_by' => 561,
]);
}
public function testRandom(){
//this does not work either
/*$model = new Order;
$model->detachBehavior('BlameableBehavior');
$model->payment_type = 4;
$model->status = 1;
$model->amount = 1;
$model->created_by = 561;
$model->updated_by = 561;
$model->save();*/
}
}
The "Order" model :
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors[] = [
'class' => \common\components\behaviors\modelLog\ActiveRecordHistoryBehavior::className(),
'manager' => '\common\components\behaviors\modelLog\managers\DBManager',
'ignoreFields' => ['updated_at', 'created_at']
];
return $behaviors;
}
/**
* #inheritdoc
*/
public function scenarios()
{
$scenarios = parent::scenarios();
$scenarios['paid'] = [
'status', 'invoice_number', 'paid_date', 'is_used', 'allocation'];
return $scenarios;
}
/**
* #inheritdoc
*/
public function rules()
{
return [
[['status', 'payment_type', 'invoice_reference', 'invoice_number'], 'integer'],
[['payment_type', 'amount', 'vat', 'total_amount', 'credit', 'invoice_reference'], 'required'],
[['amount', 'vat', 'total_amount', 'credit', 'discount'], 'number'],
[['reason'], 'trim'],
[['allocation'], 'string']
];
}
I have 2 entities with a relation ManyToMany
class User{
/**
* Owning Side
*
* #ORM\ManyToMany(targetEntity="Aire\AppBundle\Entity\Project", inversedBy="projectFollowed")
* #ORM\JoinTable(name="project_followed",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="project_id", referencedColumnName="id")}
* )
**/
private $projectFollowed;
}
class Project{
/**
* Inverse Side
*
* #ORM\ManyToMany(targetEntity="Application\Sonata\UserBundle\Entity\User", mappedBy="projectFollowed")
**/
private $projectFollowed;
}
I need to check in my project_followed table if there is already a user assigned to this project.
$dql = '
SELECT user
FROM Application\Sonata\UserBundle\Entity\User user
INNER JOIN user.projectFollowed project_followed
WHERE project_followed.project_id = :project_id
AND project_followed.user_id = :user_id
';
$em = $this->getDoctrine()->getManager();
$query = $em
->createQuery( $dql )
->setParameter( 'project_id', $project_id )
->setParameter( 'user_id', $user_id )
;
It seems my DQL is not correct :
Class AppBundle\Entity\Project has no field or association named project_id
So if i change
WHERE project_followed.project_id = :project_id
to
WHERE project_followed.id = :project_id
It is working but how can i make the relation with the user id?
The reason of this is because i need to check if there is no duplicate entries.
Is it possible to create an external field in Association in Doctrine2. The main purpose is to have a type of association.
For example,
We have Contacts and Opportunities. I need the association between Contacts and Opportunities with a type of this association.
Example of data:
contact_id | opportunity_id | association_type
------------------------------------------------------
<contact_id> | <opportunity_id> | <Executive Sponsor>
<contact_id> | <opportunity_id> | <Business Evaluator>
Is it possible to implement in Doctrine2?
Here is my association (YAML):
Opportunity:
type: entity
table: opportinity
...
...
...
manyToMany:
contacts:
targetEntity: Contact
joinTable:
name: opportinities_contacts
joinColumns:
opportunity_id:
referencedColumnName: id
inverseJoinColumns:
contact_id:
referencedColumnName: id
Thanks
The best practice in this case is to create an Entity Association Class.
Basically, split your Many-To-Many relationship into a pair of Many-to-one with a new class inbetween
Create a new class "ContactOpportunities" (In my organization we name them ToMap => ContactToOpportunityMap that sits between the classes.
class ContactOpportunity {
/**
* #var <FQN>\Contact
*
* #ORM\Id
* #ORM\ManyToOne(targetEntity="<FQN>\Contact", inversedBy='opportunities')
* #ORM\JoinColumns({
* #ORM\JoinColumn(name='Contact_ID', referencedColumnName="id")
* })
protected $contact;
/**
* #var <FQN>\Opportunity
*
* #ORM\Id
* #ORM\ManyToOne(targetEntity="<FQN>\Opportunity", inversedBy='contacts')
* #ORM\JoinColumns({
* #ORM\JoinColumn(name='Opportunity_ID', referencedColumnName="id")
* })
protected $opportunity;
/*
* #var string type
*
* #ORM\Column(name="Association_Type", type="string")
protected $type;
}
Or in yml...
ContactOpportunity
type: entity
table: opportunities_contacts
...
...
...
manyToOne:
targetEntity: Contact
inversedBy: opportunities
joinColumn:
name: contact_id
referencedColumnName: id
manyToOne:
targetEntity: Opportunity
inversedBy: contacts
joinColumn:
name: opportunity_id
referencedColumnName: id
Then convert your existing classes to target this new class:
Opportunity:
type: entity
table: opportunity
...
...
...
oneToMany:
contacts:
targetEntity: ContactOpportunity
mappedBy: opportunity
Contact:
type: entity
table: contact
...
...
...
oneToMany:
opportunities:
targetEntity: ContactOpportunity
mappedBy: contact