Laravel: Stop using UUID as foreign key - laravel

Laravel is trying to use uuid field as foreign key. And I want to use foreign key with the field id. Is there any option there?
Using this trait on Model. And then it is trying to use the uuid as foreign key. But still I want to use id as foreign key.
<?php
namespace App\Library;
use Ramsey\Uuid\Uuid;
trait UsesUuid
{
/**
* #return string
*/
public function getKeyName()
{
return 'uuid';
}
/**
* #return string
*/
public function getKeyType()
{
return 'string';
}
/**
* #return false
*/
public function getIncrementing()
{
return false;
}
/**
* #param $query
* #param $uuid
* #return mixed
*/
public function scopeUuid($query, $uuid)
{
return $query->where($this->getUuidName(), $uuid);
}
/**
* #return string
*/
public function getUuidName()
{
return property_exists($this, 'uuidName') ? $this->uuidName : 'uuid';
}
/**
* #return string
*/
public function getRouteKeyName()
{
return property_exists($this, 'uuidName') ? $this->uuidName : 'uuid';
}
/**
*
*/
protected static function boot()
{
parent::boot();
static::creating(function ($model) {
$model->{$model->getUuidName()} = Uuid::uuid4()->toString();
});
}
}

There is nothing special about this trait. You can make your own trait with id instead of uuid and everything will work fine.

The issue came from methods getIncrementing() and getKeyName(). Laravel calls getKeyName() in amount of built-in functions to interacts with relationships, also other actions like delete(), route bindings, etc.
You should allow any models which uses this trait to custom the Primary key (PK), so that uuid is only common column at all.
Your trait definition is force PK column as uuid.
Below is my recommended for the trait
<?php
namespace App\Library;
use Ramsey\Uuid\Uuid;
trait UsesUuid
{
/* Override this method to set `uuid` as PK */
public function isUuidAsPrimaryKey()
{
return false;
}
/**
* #return string
*/
public function getKeyName()
{
return $this->isUuidAsPrimaryKey() ? $this->getUuidName() : $this->primaryKey;
}
/**
* #return string
*/
public function getKeyType()
{
return $this->isUuidAsPrimaryKey() ? 'string' : $this->keyType;
}
/**
* #return false
*/
public function getIncrementing()
{
return !$this->isUuidAsPrimaryKey();
}
/**
* #param $query
* #param $uuid
* #return mixed
*/
public function scopeUuid($query, $uuid)
{
return $query->where($this->getUuidName(), $uuid);
}
/**
* #return string
*/
public function getUuidName()
{
return property_exists($this, 'uuidName') ? $this->uuidName : 'uuid';
}
/**
* #return string
*/
public function getRouteKeyName()
{
return property_exists($this, 'uuidName') ? $this->uuidName : 'uuid';
}
/**
*
*/
protected static function boot()
{
parent::boot();
static::creating(function ($model) {
$model->{$model->getUuidName()} = Uuid::uuid4()->toString();
});
}
}
If a model need to have uuid as PK, for example Book model
class Book extends Model
{
use UsesUuid;
public function isUuidAsPrimaryKey()
{
return true;
}
}
Please recheck the method isUuidAsPrimaryKey. If it may not be overridden (due to conflict), then use a property instead.

Related

Model event is not triggered on laravel

I have a class that inherits a base class and uses a trait ... I will put the code below ..
The base class is using basically to do a validation before the rescue, using for this the saving event in the boot.
The trait is to tell the class to use uuid in the id attribute .. this trait uses the creating event of the boot.
In the class itself, the boot saving event is used to check if an active record exists.
In this code the trait creating event is not being triggered ... I can not do a save because uuid is not generated ... if I take the boot method in the final class the creating event is executed ...
something I'm not seeing ... does anybody have any idea what may be happening?
MAIN CLASS
class AcademicYear extends BaseModel
{
use UseUuid;
/**
* The "booting" method of the model.
*
* #return void
*/
protected static function boot()
{
parent::boot();
static::saving(function($model)
{
if($model->attributes['disable'] == false){
$model->searchActiveRecord();
}
});
}
public function searchActiveRecord(){
if ($this::where('disable', false)->count() >= 1){
throw new \App\Exceptions\OperationNotAllowed('operation not allowed', 'there is an active record', '422');
}
return true;
}
}
BASE MODEL
class BaseModel extends Model
{
/**
* If the model will be validated in saving
*
* #var bool
*/
protected static $validate = true;
/**
* Rules that will be used to validate the model
*
* #var array
*/
protected $validationRules = [];
/**
* Create a new base model instance.
*
* #param array $attributes
* #return void
*/
public function __construct($attributes = [])
{
parent::__construct($attributes);
}
/**
* The "booting" method of the model.
*
* #return void
*/
protected static function boot()
{
parent::boot();
static::saving(function($model)
{
if ($model::$validate) {
$model->validate();
}
});
}
/**
* Execute validation of model attributes.
*
* #return void
*/
public function validate()
{
$validator = Validator::make($this->attributesToArray(), $this->validationRules);
if($validator->fails()) {
throw new \App\Exceptions\OperationNotAllowed('validation failed', $validator->messages(), '422');
}
return true;
}
}
TRAIT
trait UseUuid
{
/**
* The "booting" method of the model.
*
* #return void
*/
protected static function boot()
{
parent::boot();
static::creating(function ($model)
{
$model->incrementing = false;
$model->keyType = 'string';
$model->{$model->getKeyName()} = Str::uuid()->toString();
});
static::retrieved(function ($model)
{
$model->incrementing = false;
});
}
}
Your model's boot method is conflicting with the trait's boot method, because they have the same name.
From the PHP.net manual on Traits:
An inherited member from a base class is overridden by a member inserted by a Trait. The precedence order is that members from the current class override Trait methods, which in turn override inherited methods.
Current class: AcademicYear
Trait: UseUuid
Inherited class: BaseModel
If you want to use a boot method on an individual model, you'll have to alias the trait's method to something different:
class AcademicYear extends BaseModel
{
use UseUuid {
boot as uuidBoot;
}
// ...
protected static function boot()
{
static::uuidBoot();
// Your model-specific boot code here.
}
}
Be careful with where you place parent::boot(). If you call parent::boot() in both your trait and your model, BaseModel::boot() will be called more than once.

How can I create a controller constructor in Laravel that takes in two concrete implementations of the same interface?

Background
Note: this is using Laravel 5.3, Please don't judge.
We are trying to use dependency injection with our laravel controllers and push as much business logic into repos that are injected to controllers upon the controller instantiation.
We already have this functioning example:
class AcmeController extends Controller
{
protected $repository;
public function __construct(AcmeInterface $repository)
{
$this->repository = $repository;
}
}
inside app/Providers/RepositoryServiceProvider.php we do the binding:
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class RepositoryServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
$this->app->bind(\App\Repositories\Contracts\AcmeInterface::class, \App\Repositories\OpCity\AcmeRepo::class);
}
}
and then the AcmeRepo naturally implements the AcmeInterface:
class AcmeRepo implements AcmeInterface
Question
right now we have a case where some of the data of the same model is persisted in a memory type storage (redis) and the rest is persisted in relational db storage (psql). We would like to have two separate repos where each repo is specific to its storage type, ie RedisAcmeRepo and SqlAcmeRepo
How is it possible to do this in the AcmeController constructor?
public function __construct(AcmeInterface $sqlRepo, AcmeInterface $redisRepo)
{
$this->sqlRepo = $sqlRepo;
$this->redisRepo = $redisRepo;
}
For example you may do this:
$this->app->bind(AcmeController::class, function ($app) {
return new AcmeController($app->make(sqlRepo::class), $app->make(redisRepo::class));
});
Or this:
$this->app->when(AcmeController::class)
->needs('$sqlRepo')
->give($app->make(sqlRepo::class));
$this->app->when(AcmeController::class)
->needs('$redisRepo')
->give($app->make(redisRepo::class));
based on the answers above I came up with this solution, that kind of uses the composite pattern as well (I changed the name of the repos from Acme to ShopperLogs):
<?php
interface ShopperLogInterface
{
public function getLogs($from, $to, $shopper);
}
class ShopperLogsController extends Controller
{
/**
* service
*
* #var \App\Repositories\Contracts\ShopperLogInterface
* #access protected
*/
protected $manager;
public function __construct(ShopperLogInterface $manager)
{
$this->manager = $manager;
}
}
class ShopperLogManager implements ShopperLogInterface
{
protected $sqlRepo;
protected $redisRepo;
public function __construct(ShopperLogInterface $sqlRepo, ShopperLogInterface $redisRepo)
{
$this->sqlRepo = $sqlRepo;
$this->redisRepo = $redisRepo;
}
public function getLogs($from, $to, $shopper)
{
$todayRange = //get the today part of from -- to
/**
* array of ShopperLogs
*/
$todaysLogs;
if ($todayRange) {
$this->redisRepo->getLogs($todayRange->start, $todayRange->finish, $shopper);
}
$legacyRange = //get the part of from -- to that excludes today's range
/**
* array of ShopperLogs
*/
$legacyLogs;
if ($legacyLogs) {
$this->sqlRepo->getLogs($todayRange->start, $todayRange->finish, $shopper);
}
return merge($todayRange, $legacyRange);
}
}
class ShopperLogsSqlRepo implements ShopperLogInterface
{
/**
* #var /Illuminate\Database\Eloquent\Model/ShopperLogs
*/
protected $model;
/**
* #param /Illuminate\Database\Eloquent\Model/ShopperLogs $model
*/
public function __construct(ShopperLogs $model)
{
$this->model = $model;
}
public function getLogs($from, $to, $shopper)
{
$this->model->whereLogs //do eloquent sql stuff here
}
}
class ShopperLogsRedisRepo implements ShopperLogInterface
{
/**
* #var \Redis\Model\Class
*/
protected $model;
/**
* #param \Redis\Model\Class $model
*/
public function __construct(ShopperLogs $model)
{
$this->model = $model;
}
public function getLogs($from, $to, $shopper)
{
$this->model->whereLogs //do redis stuff
}
}
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class RepositoryServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
$this->app->bind(\App\Repositories\Contracts\ShopperLogInterface::class, \App\Managers\ShopperLogManager::class);
$this->app->bind(ShopperLogsController::class, function ($app) {
return new ShopperLogsController($app->make(ShopperLogManager::class));
});
$this->app->bind(\App\Repositories\Contracts\ShopperLogInterface::class, function() {
return new \App\Managers\ShopperLogManager(new \App\Repositories\ShopperLogsSqlRepo(new \App\ShopperLog), new \App\Repositories\ShopperLogsRedisRepo(new \App\ShopperLog));
});
}
}

Symfony 4 : doctrine, relationships and twig

I'm currently working on my first symfony 4 project and I'm having a problem with my twig templating.
First of all here is a UML diagram of my created objects.
And my layout should look sorta like this :
Here are my entities :
Region
<?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\RegionRepository")
*/
class Region
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $designation;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Country", mappedBy="region", orphanRemoval=true)
*/
private $countries;
/**
* #ORM\Column(type="string", length=255)
*/
private $initials;
public function __construct()
{
$this->countries = new ArrayCollection();
}
public function getId()
{
return $this->id;
}
public function getDesignation(): ?string
{
return $this->designation;
}
public function setDesignation(string $designation): self
{
$this->designation = $designation;
return $this;
}
/**
* #return Collection|Country[]
*/
public function getCountries(): Collection
{
return $this->countries;
}
public function addCountry(Country $country): self
{
if (!$this->countries->contains($country)) {
$this->countries[] = $country;
$country->setRegion($this);
}
return $this;
}
public function removeCountry(Country $country): self
{
if ($this->countries->contains($country)) {
$this->countries->removeElement($country);
// set the owning side to null (unless already changed)
if ($country->getRegion() === $this) {
$country->setRegion(null);
}
}
return $this;
}
public function getInitials(): ?string
{
return $this->initials;
}
public function setInitials(string $initials): self
{
$this->initials = $initials;
return $this;
}
public function __toString()
{
return $this->getInitials();
}
}
Continent
<?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\ContinentRepository")
*/
class Continent
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
/**
* #ORM\Column(type="string", length=255)
*/
private $code;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Country", mappedBy="continent", orphanRemoval=true)
*/
private $countries;
public function __construct()
{
$this->countries = new ArrayCollection();
}
public function getId()
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getCode(): ?string
{
return $this->code;
}
public function setCode(string $code): self
{
$this->code = $code;
return $this;
}
/**
* #return Collection|Country[]
*/
public function getCountries(): Collection
{
return $this->countries;
}
public function addCountry(Country $country): self
{
if (!$this->countries->contains($country)) {
$this->countries[] = $country;
$country->setContinent($this);
}
return $this;
}
public function removeCountry(Country $country): self
{
if ($this->countries->contains($country)) {
$this->countries->removeElement($country);
// set the owning side to null (unless already changed)
if ($country->getContinent() === $this) {
$country->setContinent(null);
}
}
return $this;
}
public function __toString()
{
return $this->getName();
}
}
Country
<?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\CountryRepository")
*/
class Country
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
/**
* #ORM\Column(type="string", length=255)
*/
private $iso;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Region", inversedBy="countries")
* #ORM\JoinColumn(name="region_id", referencedColumnName="id")
*/
private $region;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Document", mappedBy="country", orphanRemoval=true)
*/
private $documents;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Continent", inversedBy="countries")
* #ORM\JoinColumn(name="continent_id", referencedColumnName="id")
*/
private $continent;
public function __construct()
{
$this->documents = new ArrayCollection();
}
public function getId()
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getIso(): ?string
{
return $this->iso;
}
public function setIso(string $iso): self
{
$this->iso = $iso;
return $this;
}
public function getRegion(): ?Region
{
return $this->region;
}
public function setRegion(?Region $region): self
{
$this->region = $region;
return $this;
}
/**
* #return Collection|Document[]
*/
public function getDocuments(): Collection
{
return $this->documents;
}
public function addDocument(Document $document): self
{
if (!$this->documents->contains($document)) {
$this->documents[] = $document;
$document->setCountry($this);
}
return $this;
}
public function removeDocument(Document $document): self
{
if ($this->documents->contains($document)) {
$this->documents->removeElement($document);
// set the owning side to null (unless already changed)
if ($document->getCountry() === $this) {
$document->setCountry(null);
}
}
return $this;
}
public function getContinent(): ?Continent
{
return $this->continent;
}
public function setContinent(?Continent $continent): self
{
$this->continent = $continent;
return $this;
}
}
Document
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
use Symfony\Component\HttpFoundation\File\File;
/**
* #ORM\Entity(repositoryClass="App\Repository\DocumentRepository")
* #Vich\Uploadable()
*/
class Document
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Country", inversedBy="documents")
* #ORM\JoinColumn(name="country_id", referencedColumnName="id")
*/
private $country;
/**
* #ORM\Column(type="string")
*/
private $level;
/**
* NOTE: This is not a mapped field of entity metadata, just a simple property.
*
* #Vich\UploadableField(mapping="country_document", fileNameProperty="documentName", size="documentSize")
*
* #var File
*/
private $documentFile;
/**
* #ORM\Column(type="string", length=255)
*
* #var string
*/
private $documentName;
/**
* #ORM\Column(type="integer")
*
* #var integer
*/
private $documentSize;
/**
* #ORM\Column(type="datetime")
*
* #var \DateTime
*/
private $updatedAt;
public function getId()
{
return $this->id;
}
public function getCountry(): ?Country
{
return $this->country;
}
public function setCountry(?Country $country): self
{
$this->country = $country;
return $this;
}
public function getLevel(): ?int
{
return $this->level;
}
public function setLevel(int $level): self
{
$this->level = $level;
return $this;
}
public function setDocumentFile(?File $document = null): void
{
$this->documentFile = $document;
if (null !== $document) {
// It is required that at least one field changes if you are using doctrine
// otherwise the event listeners won't be called and the file is lost
$this->updatedAt = new \DateTimeImmutable();
}
}
public function getDocumentFile(): ?File
{
return $this->documentFile;
}
public function setDocumentName(?string $documentName): void
{
$this->documentName = $documentName;
}
public function getDocumentName(): ?string
{
return $this->documentName;
}
public function setDocumentSize(?int $documentSize): void
{
$this->documentSize = $documentSize;
}
public function getDocumentSize(): ?int
{
return $this->documentSize;
}
}
I have 4 regions. These regions each have multiple countries who are affiliated to a continent and have 2 documents attributed to them.
How should I proceed to get the layout I want ?

How to make text field compulsory only when the checkbox is checked with symfony annotation validations in model classes

It is probably a very simple question but I've run out of juice here. Vat field is compulsory only when isVatable checkbox is check by user otherwise it can be ignored. How do I achieve this with group validation (annotations) in model class, not entity?
I checked Validation Groups and Group Sequence but to be honest didn't get my head around.
FormType
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options = [])
{
$builder
->setMethod($options['method'])
->setAction($options['action'])
->add('vat', 'text')
->add('isVatable', 'checkbox')
;
}
public function getName()
{
return 'user';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(
['data_class' => 'My\FrontendBundle\Model\UserModel']
);
}
}
ModelClass
class UserModel
{
/**
* #Assert\NotBlank(message="Vat is required only when checkbox is checked.")
*/
protected $vat;
/**
* #var bool
*/
protected $isVatable = false;
}
I find that the #Assert\True() constraint on a method usually works well for me for these sorts of validation scenario. You can add some validation constraints to methods as well as properties, which is pretty powerful.
The basic idea is that you can create a method, give it this annotation - if the method returns true the validation passes; if it returns false it fails.
class UserModel
{
/**
* #var string
*/
protected $vat;
/**
* #var bool
*/
protected $isVatable = false;
/**
* #Assert\True(message="Please enter a VAT number")
*/
public function isVatSetWhenIsVatableChecked()
{
// if this property is unchecked we don't
// want to do any validation so return true
if (!$this->isVatable) {
return true;
}
// return true if $this->vat is not null
// you might want to add some additional
// validation here to make sure the
return !is_null($this->vat);
}
}
Additionally, you can map the error message to a specific form field with the error_mapping option in your FormType object, as documented here:
http://symfony.com/blog/form-goodness-in-symfony-2-1#error-mapping-fu
Hope this helps :)
Perhaps something like this in ...\Validator\Constraints:
VAT.php
use Symfony\Component\Validator\Constraint;
class VAT extends Constraint
{
public $message = 'VAT is compulsory for applicable items';
public $vat;
public $isVatable;
}
VATConstraint.php
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\PropertyAccess\PropertyAccess;
class VATValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
$accessor = PropertyAccess::createPropertyAccessor();
$data = $accessor->getValue($this->context->getRoot(), 'data');
$vat = $data['vat'];
$isVatable = $data['isVatable'];
if ($isVatable && empty($vat)) {
$this->context->addViolation($constraint->message, array('%string%' => $value));
return false;
}
return true;
}
}
User model Entity:
use YourBundle\Validator\Constraints as MyAssert;
class UserModel
{
/**
* #MyAssert\NotBlank(message="Vat is required only when checkbox is checked.")
*/
protected $vat;
...
}
You should use a Class Constraint Validator
First make your Constraint class and your validatorClass:
<?php
// AppBundle/Validator/Constraints/NotBlankIfTaxEnabled.php
namespace AppBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class NotBlankIfTaxEnabled extends Constraint
{
public $message = 'If isVat is enabled you have to enter a value in the Vat field.';
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
}
and
<?php
// AppBundle/Validator/Constraints/NotBlankIfTaxEnabledValidator.php
namespace AppBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class NotBlankIfTaxEnabledValidator extends ConstraintValidator
{
public function validate($customer, Constraint $constraint)
{
if($customer->getIsVatable() && strlen($customer->getVat()) == 0)
{
$this->context->buildViolation($constraint->message)
->addViolation();
}
}
}
Then add de classcontraint to your Entity/Model
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use AppBundle\Validator\Constraints as AppAssert;
/**
* Customer
*
* #ORM\Table()
* #ORM\Entity
* #AppAssert\NotBlankIfTaxEnabled
*/
class Customer
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="description", type="string", length=64)
*
*/
private $vat;
/**
* #var boolean
*
* #ORM\Column(name="taxEnabled", type="boolean")
*/
private $isVatable;
and do not forget to switch off the required attribute for both fields in your formType:
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class CustomerType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('vat', NULL, array('required' => FALSE))
->add('isVatable', NULL, array('required' => FALSE))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Customer'
));
}
/**
* #return string
*/
public function getName()
{
return 'appbundle_customer';
}
}

Doctrine 2: cascade persist Oracle "IDENTITY" is returning 0 as last inserted ID

I am using doctrine 2 with oracle, the tables in the database has some triggers that generate the IDs, and my ID mapping of my tables is like the following:
/**
* #orm\Id
* #orm\Column(type="integer");
* #orm\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
and I have a OneToMany relation, with cascade={"persist"} but it is not working, I tried the same code with MySQL and it is working fine, but in oracle the last insert Id seems to always return 0 instead of the real id of the inserted row... and so the cascade persist is not working... is this a bug in doctrine or am I doing something wrong? any help?
After following the code it seems that the method
Doctrine\ORM\Id\IdentityGenerator::generate
is returning 0, I don't know why it is being invoked since the sequenceName is null (there is no sequence in the deffinition!
EDIT: Here are the entities:
The Client Entity:
/**
* #ORM\Entity
* #ORM\Table(name="clients")
**/
class Client {
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
* #ORM\Column(type="integer")
*/
protected $id;
/** #ORM\Column(name="name",type="string",length=255,unique=true) */
protected $name;
/**
* #ORM\OneToMany(targetEntity="ContactInformation", mappedBy="client", cascade={"persist"})
**/
protected $contactInformations;
public function __construct() {
$this->contactInformations = new ArrayCollection();
}
public function getId() {
return $this->id;
}
public function getName() {
return $this->name;
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getContactInformations() {
return $this->contactInformations;
}
public function addContactInformations(Collection $contactInformations)
{
foreach ($contactInformations as $contactInformation) {
$contactInformation->setClient($this);
$this->contactInformations->add($contactInformation);
}
}
/**
* #param Collection $tags
*/
public function removeContactInformations(Collection $contactInformations)
{
foreach ($contactInformations as $contactInformation) {
$contactInformation->setClient(null);
$this->contactInformations->removeElement($contactInformation);
}
}
public function setContactInformations($contactInformations) {
$this->contactInformations = $contactInformations;
return $this;
}
}
The Contact Information Entity:
/**
* #ORM\Entity
* #ORM\Table(name="contact_informations")
**/
class ContactInformation {
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #ORM\OneToOne(targetEntity="ContactInformationType")
* #ORM\JoinColumn(name="type_id", referencedColumnName="id")
**/
protected $type;
/** #ORM\Column(type="text") */
protected $value;
/**
* #ORM\ManyToOne(targetEntity="Client", inversedBy="contact_informations")
* #ORM\JoinColumn(name="client_id", referencedColumnName="id")
**/
private $client;
public function getId() {
return $this->id;
}
public function getType() {
return $this->type;
}
public function setType($type) {
$this->type = $type;
return $this;
}
public function getValue() {
return $this->value;
}
public function setValue($value) {
$this->value = $value;
return $this;
}
public function getClient() {
return $this->client;
}
public function setClient($client = null) {
$this->client = $client;
return $this;
}
}
Oracle doesn't support auto incrementing, so you cannot use the "IDENTITY" strategy in Doctrine. You'll have to use the "SEQUENCE" (or "AUTO") strategy.
When specifying "AUTO", Doctrine will use "IDENTITY" for MySql and "SEQUENCE" for Oracle.
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#identifier-generation-strategies

Resources