I have three models related in Yii2, with attributes:
AyudanteSituacionLaboral (ID, FechaInicio, State, Ayudante_ID)
Ayudante(ID, Name )
HRutaDistribucion (ID, Fecha, Ayudante_ID)
I need to check uniqueness of Fecha and Ayudante_ID against AyudanteSituacionLaboral=>FechaInicio and AyudanteSituacionLaboral=>Ayudante_ID respectively. So when I choose from HRutaDistribucion form an Ayudante instance that already has an entry in AyudanteSituacionLaboral with Fecha like FechaInicio field, it launches an error message.
Here's HRutaDistribucion Model:
<?php
namespace backend\models;
use backend\controllers\AyudanteSituacionLaboralController;
use Yii;
/**
* This is the model class for table "hruta_distribucion".
*
* #property integer $HRuta_ID
* #property integer $Carro_DistribucionCarroID
* #property integer $ChoferChofer_ID
* #property integer $AyudanteAyudante_ID
* #property integer $HojaRuta
* #property string $Fecha
* #property string $FechaEmision
* #property integer $Capacidad_Transportada
*
* #property Ayudante $ayudanteAyudante
* #property CarroDistribucion $carroDistribucionCarro
* #property Chofer $choferChofer
* #property ViajeDistribucion[] $viajeDistribucions
*/
class HrutaDistribucion extends \yii\db\ActiveRecord
{
/**
* #inheritdoc
*/
public static function tableName()
{
return 'hruta_distribucion';
}
/**
* #inheritdoc
*/
public function rules()
{
return [
// [[ 'HojaRuta', 'Fecha', 'FechaEmision'], 'required'],
[['Fecha', 'FechaEmision','Carro_DistribucionCarroID', 'ChoferChofer_ID', 'AyudanteAyudante_ID', 'HojaRuta','Capacidad_Transportada'], 'safe', 'on'=>'search'],
['Fecha', 'unique', 'message' => ("Este trabajador ya esta en esta fecha")]
];
}
/**
* #inheritdoc
*/
public function attributeLabels()
{
return [
'HRuta_ID' => 'Hruta ID',
'Carro_DistribucionCarroID' => 'Carro Distribucion Carro ID',
'ChoferChofer_ID' => 'Chofer Chofer ID',
'AyudanteAyudante_ID' => 'Ayudante Ayudante ID',
'HojaRuta' => 'Hoja Ruta',
'Fecha' => 'Fecha',
'FechaEmision' => 'Fecha Emision',
'Capacidad_Transportada' => 'Capacidad Transportada',
];
}
/**
* #return \yii\db\ActiveQuery
*/
public function getAyudanteAyudante()
{
return $this->hasOne(Ayudante::className(), ['Ayudante_ID' => 'AyudanteAyudante_ID']);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getCarroDistribucionCarro()
{
return $this->hasOne(CarroDistribucion::className(), ['CarroDistID' => 'Carro_DistribucionCarroID']);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getChoferChofer()
{
return $this->hasOne(Chofer::className(), ['Chofer_ID' => 'ChoferChofer_ID']);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getViajeDistribucions()
{
return $this->hasMany(ViajeDistribucion::className(), ['HRuta_DistribucionHRuta_ID' => 'HRuta_ID']);
}
}
Here's AyudanteSituacionLaboral Model:
<?php
namespace backend\models;
use Yii;
/**
* This is the model class for table "ayudante_situacion_laboral".
*
* #property integer $AyudanteAyudante_ID
* #property integer $Situacion_LaboralSitL_ID
* #property string $Fecha
* #property integer $Cant_Dias
* #property integer $Cant_Horas
* #property string $Descripcion
* #property string $Fecha_Creacion
*
* #property Ayudante $ayudanteAyudante
* #property SituacionLaboral $situacionLaboralSitL
*/
class AyudanteSituacionLaboral extends \yii\db\ActiveRecord
{
/**
* #inheritdoc
*/
public static function tableName()
{
return 'ayudante_situacion_laboral';
}
/**
* #inheritdoc
*/
public function rules()
{
return [
[['AyudanteAyudante_ID', 'Situacion_LaboralSitL_ID', 'Fecha_Creacion'], 'required'],
[['AyudanteAyudante_ID', 'Situacion_LaboralSitL_ID','FechaInicio','FechaFin', 'Fecha_Creacion','Cant_Horas'], 'safe'],
[['Descripcion'], 'string', 'max' => 255],
['Cant_Horas', 'required', 'when'=>function($model){
return (empty($model->FechaInicio))? true:false;
}, 'whenClient'=>"function(){
if ( $('#FechaInicio').val()=== undefined)
{ false;
}else{
true;}
}" ],
];
}
/**
* #inheritdoc
*/
public function attributeLabels()
{
return [
'AyudanteAyudante_ID' => 'Nombre Ayudante',
'Situacion_LaboralSitL_ID' => 'Situación Laboral',
'FechaInicio' => 'Fecha Inicio',
'FechaFin' => 'Fecha Fin',
'Cant_Horas' => 'Cant. Horas',
'Descripcion' => 'Observ.',
'Fecha_Creacion' => 'Fecha Creacion',
];
}
/**
* #return \yii\db\ActiveQuery
*/
public function getAyudanteAyudante()
{
return $this->hasOne(Ayudante::className(), ['Ayudante_ID' => 'AyudanteAyudante_ID']);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getSituacionLaboralSitL()
{
return $this->hasOne(SituacionLaboral::className(), ['SitL_ID' => 'Situacion_LaboralSitL_ID']);
}
}
And finally Ayudante Model:
<?php
namespace backend\models;
use Yii;
/**
* This is the model class for table "ayudante".
*
* #property integer $Ayudante_ID
* #property integer $Registro
* #property string $Nombre
*
* #property AyudanteSituacionLaboral[] $ayudanteSituacionLaborals
* #property SituacionLaboral[] $situacionLaboralSitLs
* #property ViajeDistribucion[] $viajeDistribucions
* #property ViajeGlp[] $viajeGlps
*/
class Ayudante extends \yii\db\ActiveRecord
{
/**
* #inheritdoc
*/
public static function tableName()
{
return 'ayudante';
}
/**
* #inheritdoc
*/
public function rules()
{
return [
[['Registro','Nombre'],'required'],
[['Registro'], 'integer'],
[['CI'], 'string', 'max' => 11],
[['Nombre'], 'string', 'max' => 50]
];
}
/**
* #inheritdoc
*/
public function attributeLabels()
{
return [
'Ayudante_ID' => 'ID',
'Registro' => 'Registro',
'CI'=>'CI',
'Nombre' => 'Nombre',
];
}
/**
* #return \yii\db\ActiveQuery
*/
public function getAyudanteSituacionLaborals()
{
return $this->hasMany(AyudanteSituacionLaboral::className(), ['AyudanteAyudante_ID' => 'Ayudante_ID']);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getSituacionLaboralSitLs()
{
return $this->hasMany(SituacionLaboral::className(), ['SitL_ID' => 'Situacion_LaboralSitL_ID'])->viaTable('ayudante_situacion_laboral', ['AyudanteAyudante_ID' => 'Ayudante_ID']);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getViajeDistribucions()
{
return $this->hasMany(ViajeDistribucion::className(), ['AyudanteAyudante_ID' => 'Ayudante_ID']);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getViajeGlps()
{
return $this->hasMany(ViajeGlp::className(), ['AyudanteAyudante_ID' => 'Ayudante_ID']);
}
}
Could be you need exist validator
http://www.yiiframework.com/doc-2.0/yii-validators-existvalidator.html
In your HRutaDistribucion model you should add this to your rules
[['Date', 'Ayudante_ID '], 'exist',
'targetClass' => AyudanteSituacionLaboral::ClassName() ,
'targetAttribute' => ['Date', 'Ayudante_ID ']]
Related
This is a follow-up question to this one.
In my Typo3 plugin I have a model:
<?php
namespace Homeinfo\SysMon2\Domain\Model;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
class CheckResults extends AbstractEntity
{
/**
* #var int $id
*/
public $id;
/**
* #var DateTime $timestamp
*/
public $timestamp;
/**
* #var int $system
*/
public $system;
/**
* #var bool $icmp_request
*/
public $icmp_request;
/**
* #var string $ssh_login
*/
public $ssh_login ;
/**
* #var string $http_request
*/
public $http_request;
/**
* #var string $application_state
*/
public $application_state;
/**
* #var string $smart_check
*/
public $smart_check;
/**
* #var string $baytrail_freeze
*/
public $baytrail_freeze;
/**
* #var string $fsck_repair
*/
public $fsck_repair;
/**
* #var bool $application_version
*/
public $application_version;
/**
* #var int $ram_total
*/
public $ram_total;
/**
* #var int $ram_free
*/
public $ram_free;
/**
* #var int $ram_available
*/
public $ram_available;
/**
* #var string $efi_mount_ok
*/
public $efi_mount_ok;
/**
* #var int $download
*/
public $download;
/**
* #var int $upload
*/
public $upload;
/**
* #var string $root_not_ro
*/
public $root_not_ro;
/**
* #var string $sensors
*/
public $sensors;
/**
* #var bool $in_sync
*/
public $in_sync;
/**
* #var int $recent_touch_events
*/
public $recent_touch_events;
/**
* #var DateTime $offline_since
*/
public $offline_since;
/**
* #var DateTime $blackscreen_since
*/
public $blackscreen_since;
}
Which represents a table of a foreign database.
I configured that database via:
'Default' => [
...
],
'Sysmon' => [
'charset' => 'utf8mb4',
'dbname' => 'sysmon',
'driver' => 'mysqli',
'host' => 'REDACTED',
'password' => 'REDACTED',
'port' => 3306,
'tableoptions' => [
'charset' => 'utf8mb4',
'collate' => 'utf8mb4_general_ci',
],
'user' => 'sysmon_ro',
],
],
'TableMapping' => [
'checkresults' => 'Sysmon',
'offlinehistory' => 'Sysmon',
],
How do I let Typo3 know, that the 'checkresults' => 'Sysmon' mapping should use the aforementioned model class?
The official documentation does not mention how to do this.
I have crud with 3 entities. Meal, Product and ProductsQuantity. Between Meal and ProductQuantity is relation many to many. Adding data is working fine, all data are saving to entities but problem is when in want to edit form. Then I got error:
The form's view data is expected to be an instance of class MealBundle\Entity\ProductsQuantity, but is an instance of class Doctrine\ORM\PersistentCollection. You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms an instance of class Doctrine\ORM\PersistentCollection to an instance of MealBundle\Entity\ProductsQuantity.
I tried with data_class option to null, and setting fetch="EAGER" on the relation but it doesn't solved the problem.
Meal:
/**
* #Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #var string
* #ORM\Column(name="name", type="string", length=255)
*/
protected $name = "";
/**
* #var ProductsQuantity[]|Collection
* #ORM\ManyToMany(targetEntity="ProductsQuantity", inversedBy="meal", cascade={"persist"}, fetch="EAGER")
* #ORM\JoinTable(name="meal_products_quantity_relations",
* joinColumns={#ORM\JoinColumn(name="meal_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="products_quantity_id", referencedColumnName="id")}
* )
*/
protected $productsQuantity;
/**
* Meal constructor.
*/
public function __construct()
{
$this->productsQuantity = new ArrayCollection();
}
/**
* #return int
*/
public function getId(): int
{
return $this->id;
}
/**
* #param int $id
* #return Meal
*/
public function setId(int $id): Meal
{
$this->id = $id;
return $this;
}
/**
* #return string
*/
public function getName(): string
{
return $this->name;
}
/**
* #param string $name
* #return Meal
*/
public function setName(string $name): Meal
{
$this->name = $name;
return $this;
}
/**
* #return Collection|ProductsQuantity[]
*/
public function getProductsQuantity()
{
return $this->productsQuantity;
}
/**
* #param Collection|ProductsQuantity[] $productsQuantity
* #return Meal
*/
public function setProductsQuantity($productsQuantity)
{
$this->productsQuantity = $productsQuantity;
return $this;
}
ProductQuantity:
/**
* #Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #var Product
* #ORM\JoinColumn(name="product_id", referencedColumnName="id")
* #ORM\ManyToOne(targetEntity="Product", inversedBy="productsQuantity")
*/
protected $product;
/**
* #var integer
* #ORM\Column(name="amount", type="integer")
*/
protected $amount;
/**
* #var $meal
* #ORM\ManyToMany(targetEntity="MealBundle\Entity\Meal", mappedBy="productsQuantity")
*/
protected $meal;
/**
* #return mixed
*/
public function getId()
{
return $this->id;
}
/**
* #param mixed $id
* #return ProductsQuantity
*/
public function setId($id)
{
$this->id = $id;
return $this;
}
/**
* #return Product
*/
public function getProduct(): ?Product
{
return $this->product;
}
/**
* #param Product $product
* #return ProductsQuantity
*/
public function setProduct(Product $product): ProductsQuantity
{
$this->product = $product;
return $this;
}
/**
* #return int
*/
public function getAmount(): ?int
{
return $this->amount;
}
/**
* #param int $amount
*/
public function setAmount(int $amount): void
{
$this->amount = $amount;
}
/**
* #return mixed
*/
public function getMeal()
{
return $this->meal;
}
/**
* #param mixed $meal
* #return ProductsQuantity
*/
public function setMeal($meal)
{
$this->meal = $meal;
return $this;
}
Meal form:
class MealType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options = [])
{
parent::buildForm($builder, $options);
/** #var Meal $meal */
$meal = $options['data'];
$data = null;
if(!empty($meal)) {
$data = $meal->getProductsQuantity();
} else {
$data = new ProductsQuantity();
}
$builder
->add('name', TextType::class,[
'label' => 'Nazwa Dania',
'required' => true
])->add('productsQuantity', CollectionType::class, [
'data_class' => null,
'label' => 'Produkty',
'entry_type' => ProductsQuantityType::class,
'allow_add' => true,
'data' => ['productsQuantity' => $data],
'prototype_name' => '__product__',
'entry_options' => [
'allow_extra_fields' => true,
'label' => false
],
'prototype' => true
])->add('addProduct', ButtonType::class, [
'label' => 'Dodaj kolejny produkt',
'attr' => [
'class' => 'btn-default addProductEntry'
]
])->add('submit', SubmitType::class, [
'label' => 'Dodaj'
]);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setRequired('productsQuantity');
$resolver->setDefaults([
'data_class' => Meal::class
]);
}
}
ProductsQuantity form:
class ProductsQuantityType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
$builder
->add('product', EntityType::class,[
'class' => Product::class,
'label' => 'Nazwa produktu',
])
->add('amount', NumberType::class, [
'label' => 'Ilość',
'required' => true,
'attr' => [
'placeholder' => 'ilość'
]
])
->add('removeProduct', ButtonType::class, [
'label' => 'X',
'attr' => [
'class' => 'btn-danger removeProductEntry'
]
]);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => ProductsQuantity::class,
]);
}
}
If I change 'data' => ['productsQuantity' => $data] to 'data' => ['productsQuantity' => new ProductsQuantity()] there is no error but have empty ProductsQuantity part of MealType form. Can anyone tell me how to fix this?
I'm using Zend Framework 3 with Doctrine and I'm trying to save an Entity "Cidade" related to another Entity "Estado" witch is already stored in the database. However, Doctrine is trying to persist Entity "Estado", and the only attribute I have from Estado is the primary key in a HTML combo.
My view forms are built under Zend forms and fieldsets, which means POST data is automatically converted to the target entities using ClassMethods hydrator.
The problem is that if I set the attribute $estado with cascade={"persist"} in Cidade Entity, Doctrine tries to persist Estado Entity missing all required attributes but the primary key ID, which comes from POST request (HTML combo). I also considered using cascade={"detach"} ir order to Doctrine ignore Estado Entity in the EntityManager. But I get this error:
A new entity was found through the relationship 'Application\Entity\Cidade#estado' that was not configured to cascade persist operations for entity: Application\Entity\Estado#000000007598ee720000000027904e61.
I found a similar doubt here and the only way I could find for this matter was first retrieving Estado Entity and setting it on Cidade Entity before saving. If this is the only way, can I tell my form structure won't work unless I retrieve all relationships before saving the dependant entities?
In other words, what is the best way of doing such thing in Doctrine (for example):
<?php
/*I'm simulating the creation of Estado Entity representing an
existing Estado in database, so "3" is the ID rendered in HTML combo*/
$estado = new Entity\Estado();
$estado->setId(3);
$cidade = new Entity\Cidade();
$cidade->setNome("City Test");
$cidade->setEstado($estado); //relationship here
$entityManager->persist($cidade);
$entityManager->flush();
How to do that without having to retrieve an Estado all the time I need to save a Cidade? Wouldn't affect performance?
My Cidade Entity:
<?php
namespace Application\Entity;
use Zend\InputFilter\Factory;
use Zend\InputFilter\InputFilterInterface;
use Doctrine\ORM\Mapping as ORM;
/**
* Class Cidade
* #package Application\Entity
* #ORM\Entity
*/
class Cidade extends AbstractEntity
{
/**
* #var string
* #ORM\Column(length=50)
*/
private $nome;
/**
* #var Estado
* #ORM\ManyToOne(targetEntity="Estado", cascade={"detach"})
* #ORM\JoinColumn(name="id_estado", referencedColumnName="id")
*/
private $estado;
/**
* Retrieve input filter
*
* #return InputFilterInterface
*/
public function getInputFilter()
{
if (!$this->inputFilter) {
$factory = new Factory();
$this->inputFilter = $factory->createInputFilter([
"nome" => ["required" => true]
]);
}
return $this->inputFilter;
}
/**
* #return string
*/
public function getNome()
{
return $this->nome;
}
/**
* #param string $nome
*/
public function setNome($nome)
{
$this->nome = $nome;
}
/**
* #return Estado
*/
public function getEstado()
{
return $this->estado;
}
/**
* #param Estado $estado
*/
public function setEstado($estado)
{
$this->estado = $estado;
}
}
My Estado Entity:
<?php
namespace Application\Entity;
use Doctrine\ORM\Mapping as ORM;
use Zend\InputFilter\Factory;
use Zend\InputFilter\InputFilterInterface;
/**
* Class Estado
* #package Application\Entity
* #ORM\Entity
*/
class Estado extends AbstractEntity
{
/**
* #var string
* #ORM\Column(length=50)
*/
private $nome;
/**
* #var string
* #ORM\Column(length=3)
*/
private $sigla;
/**
* #return string
*/
public function getNome()
{
return $this->nome;
}
/**
* #param string $nome
*/
public function setNome($nome)
{
$this->nome = $nome;
}
/**
* #return string
*/
public function getSigla()
{
return $this->sigla;
}
/**
* #param string $sigla
*/
public function setSigla($sigla)
{
$this->sigla = $sigla;
}
/**
* Retrieve input filter
*
* #return InputFilterInterface
*/
public function getInputFilter()
{
if (!$this->inputFilter) {
$factory = new Factory();
$this->inputFilter = $factory->createInputFilter([
"nome" => ["required" => true],
"sigla" => ["required" => true]
]);
}
return $this->inputFilter;
}
}
Both entities extend my superclass AbstractEntity:
<?php
namespace Application\Entity;
use Doctrine\ORM\Mapping\MappedSuperclass;
use Doctrine\ORM\Mapping as ORM;
use Zend\InputFilter\InputFilterAwareInterface;
use Zend\InputFilter\InputFilterInterface;
/**
* Class AbstractEntity
* #package Application\Entity
* #MappedSuperClass
*/
abstract class AbstractEntity implements InputFilterAwareInterface
{
/**
* #var int
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #var InputFilterAwareInterface
*/
protected $inputFilter;
/**
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* #param int $id
*/
public function setId($id)
{
$this->id = $id;
}
/**
* #param InputFilterInterface $inputFilter
* #return InputFilterAwareInterface
* #throws \Exception
*/
public function setInputFilter(InputFilterInterface $inputFilter)
{
throw new \Exception("Método não utilizado");
}
}
My HTML inputs are rendered as it follows:
<input name="cidade[nome]" class="form-control" value="" type="text">
<select name="cidade[estado][id]" class="form-control">
<option value="3">Bahia</option>
<option value="2">Espírito Santo</option>
<option value="1">Minas Gerais</option>
<option value="9">Pará</option>
</select>
Each option above is an Estado Entity retrieved from database. My POST data comes as the following example:
[
"cidade" => [
"nome" => "Test",
"estado" => [
"id" => 3
]
]
]
On Zend Form's isValid() method, this POST data is automatically converted to the target Entities, which makes me crash on this Doctrine issue. How do I move on?
You should bind an object to your form and use the Doctrine Hydrator. In the form the field names should exactly match that of the Entity. So Entity#name is Form#name.
With Separation of Concerns I'm absolutely against placing the InputFilter for an Entity within the Entity itself. As such, I'll give you an example with everything separated, if you decide to mash it back together, that's up to you.
AbstractEntity for ID
/**
* #ORM\MappedSuperclass
*/
abstract class AbstractEntity
{
/**
* #var int
* #ORM\Id
* #ORM\Column(name="id", type="integer")
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
// getter/setter
}
Cicade Entity
/**
* #ORM\Entity
*/
class Cidade extends AbstractEntity
{
/**
* #var string
* #ORM\Column(length=50)
*/
protected $nome; // Changed to 'protected' so can be used in child classes - if any
/**
* #var Estado
* #ORM\ManyToOne(targetEntity="Estado", cascade={"persist", "detach"}) // persist added
* #ORM\JoinColumn(name="id_estado", referencedColumnName="id")
*/
protected $estado;
// getters/setters
}
Estado Entity
/**
* #ORM\Entity
*/
class Estado extends AbstractEntity
{
/**
* #var string
* #ORM\Column(length=50)
*/
protected $nome;
//getters/setters
}
So, above is the Entity setup for Many to One - Uni-direction relation.
You want to manage this, easily, with forms. So we need to create InputFilters for both.
Having InputFilters separately from the Entity allows us to nest them. This in turn allows us to create structured and nested forms.
For example, you could create a new Estado on-the-fly. If this were a bi-directional relation, you could create multiple Cicade Entity objects on-the-fly from/during the creation of Estado.
First: InputFilters. In the spirit of abstraction, which you started with your Entities, let's do that here as well:
AbstractDoctrineInputFilter
source AbstractDoctrineInputFilter & source AbstractDoctrineFormInputFilter
This gives a nice clean setup and a requirement to fulfill. I'm glossing over the more complex elements added in the source files, feel free to look those up though.
Both objects (Estado & Cicade) require an ObjectManager (they're Doctrine entities after all), so I'm assuming you might have more. The below should come in handy.
<?php
namespace Application\InputFilter;
use Doctrine\Common\Persistence\ObjectManager;
use Zend\InputFilter\InputFilter;
abstract class AbstractInputFilter extends InputFilter
{
/**
* #var ObjectManager
*/
protected $objectManager;
/**
* AbstractFormInputFilter constructor.
*
* #param array $options
*/
public function __construct(array $options)
{
// Check if ObjectManager|EntityManager for FormInputFilter is set
if (isset($options['object_manager']) && $options['object_manager'] instanceof ObjectManager) {
$this->setObjectManager($options['object_manager']);
}
}
/**
* Init function
*/
public function init()
{
$this->add(
[
'name' => 'id',
'required' => false, // Not required when adding - should also be in route when editing and bound in controller, so just additional
'filters' => [
['name' => ToInt::class],
],
'validators' => [
['name' => IsInt::class],
],
]
);
// If CSRF validation has not been added, add it here
if ( ! $this->has('csrf')) {
$this->add(
[
'name' => 'csrf',
'required' => true,
'filters' => [],
'validators' => [
['name' => Csrf::class],
],
]
);
}
}
// getters/setters for ObjectManager
}
Estado InputFilter
class EstadoInputFilter extends AbstractInputFilter
{
public function init()
{
parent::init();
$this->add(
[
'name' => 'nome', // <-- important, name matches entity property
'required' => true,
'allow_empty' => true,
'filters' => [
['name' => StringTrim::class],
['name' => StripTags::class],
[
'name' => ToNull::class,
'options' => [
'type' => ToNull::TYPE_STRING,
],
],
],
'validators' => [
[
'name' => StringLength::class,
'options' => [
'min' => 2,
'max' => 255,
],
],
],
]
);
}
}
Cicade InputFilter
class EstadoInputFilter extends AbstractInputFilter
{
public function init()
{
parent::init(); // Adds the CSRF
$this->add(
[
'name' => 'nome', // <-- important, name matches entity property
'required' => true,
'allow_empty' => true,
'filters' => [
['name' => StringTrim::class],
['name' => StripTags::class],
[
'name' => ToNull::class,
'options' => [
'type' => ToNull::TYPE_STRING,
],
],
],
'validators' => [
[
'name' => StringLength::class,
'options' => [
'min' => 2,
'max' => 255,
],
],
],
]
);
$this->add(
[
'name' => 'estado',
'required' => true,
]
);
}
}
So. Now we have 2 InputFilters, based on an AbstractInputFilter.
EstadoInputFilter filters just the nome property. Add additional if you want ;)
CicadeInputFilter filters the nome property and has a required estado field.
The names match those of the Entity definition in the respective Entity classes.
Just to be complete, below is the CicadeForm, take what you need to create the EstadoForm.
class CicadeForm extends Form
{
/**
* #var ObjectManager
*/
protected $objectManager;
/**
* AbstractFieldset constructor.
*
* #param ObjectManager $objectManager
* #param string $name Lower case short class name
* #param array $options
*/
public function __construct(ObjectManager $objectManager, string $name, array $options = [])
{
parent::__construct($name, $options);
$this->setObjectManager($objectManager);
}
public function init()
{
$this->add(
[
'name' => 'nome',
'required' => true,
'type' => Text::class,
'options' => [
'label' => _('Nome',
],
]
);
// #link: https://github.com/doctrine/DoctrineModule/blob/master/docs/form-element.md
$this->add(
[
'type' => ObjectSelect::class,
'required' => true,
'name' => 'estado',
'options' => [
'object_manager' => $this->getObjectManager(),
'target_class' => Estado::class,
'property' => 'id',
'display_empty_item' => true,
'empty_item_label' => '---',
'label' => _('Estado'),
'label_attributes' => [
'title' => _('Estado'),
],
'label_generator' => function ($targetEntity) {
/** #var Estado $targetEntity */
return $targetEntity->getNome();
},
],
]
);
//Call parent initializer. Check in parent what it does.
parent::init();
}
/**
* #return ObjectManager
*/
public function getObjectManager() : ObjectManager
{
return $this->objectManager;
}
/**
* #param ObjectManager $objectManager
*
* #return AbstractDoctrineFieldset
*/
public function setObjectManager(ObjectManager $objectManager) : AbstractDoctrineFieldset
{
$this->objectManager = $objectManager;
return $this;
}
}
Config
Now that the classes are there, how to use them? Slap 'em together with module config!
In your module.config.php file, add this config:
'form_elements' => [
'factories' => [
CicadeForm::class => CicadeFormFactory::class,
EstadoForm::class => EstadoFormFactory::class,
// If you create separate Fieldset classes, this is where you register those
],
],
'input_filters' => [
'factories' => [
CicadeInputFilter::class => CicadeInputFilterFactory::class,
EstadoInputFilter::class => EstadoInputFilterFactory::class,
// If you register Fieldsets in form_elements, their InputFilter counterparts go here
],
],
From this config we read we need a Factory for both the Form and for the InputFilter of a set.
Below the CicadeInputFilterFactory
class CicadeInputFilterFactory implements FactoryInterface
{
/**
* #param ContainerInterface $container
* #param string $requestedName
* #param array|null $options
*
* #return CicadeInputFilter
* #throws \Psr\Container\ContainerExceptionInterface
* #throws \Psr\Container\NotFoundExceptionInterface
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
/** #var ObjectManager|EntityManager $objectManager */
$objectManager = $this->setObjectManager($container->get(EntityManager::class));
return new CicadeInputFilter(
[
'object_manager' => objectManager,
]
);
}
}
Matching CicadeFormFactory
class CicadeFormFactory implements FactoryInterface
{
/**
* #param ContainerInterface $container
* #param string $requestedName
* #param array|null $options
*
* #return CicadeForm
* #throws \Psr\Container\ContainerExceptionInterface
* #throws \Psr\Container\NotFoundExceptionInterface
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null) : CicadeForm
{
$inputFilter = $container->get('InputFilterManager')->get(CicadeInputFilter::class);
// Here we creazte a new Form object. We set the InputFilter we created earlier and we set the DoctrineHydrator. This hydrator can work with Doctrine Entities and relations, so long as data is properly formatted when it comes in from front-end.
$form = $container->get(CicadeForm::class);
$form->setInputFilter($inputFilter);
$form->setHydrator(
new DoctrineObject($container->get(EntityManager::class))
);
$form->setObject(new Cicade());
return $form;
}
}
Massive preparation done, time to use it
Specific EditController to Edit an existing Cicade Entity
class EditController extends AbstractActionController // (Zend's AAC)
{
/**
* #var CicadeForm
*/
protected $cicadeForm;
/**
* #var ObjectManager|EntityManager
*/
protected $objectManager;
public function __construct(
ObjectManager $objectManager,
CicadeForm $cicadeForm
) {
$this->setObjectManager($objectManager);
$this->setCicadeForm($cicadeForm);
}
/**
* #return array|Response
* #throws ORMException|Exception
*/
public function editAction()
{
$id = $this->params()->fromRoute('id', null);
if (is_null($id)) {
$this->redirect()->toRoute('home'); // Do something more useful instead of this, like notify of id received from route
}
/** #var Cicade $entity */
$entity = $this->getObjectManager()->getRepository(Cicade::class)->find($id);
if (is_null($entity)) {
$this->redirect()->toRoute('home'); // Do something more useful instead of this, like notify of not found entity
}
/** #var CicadeForm $form */
$form = $this->getCicadeForm();
$form->bind($entity); // <-- This here is magic. Because we overwrite the object from the Factory with an existing one. This pre-populates the form with value and allows us to modify existing one. Assumes we got an entity above.
/** #var Request $request */
$request = $this->getRequest();
if ($request->isPost()) {
$form->setData($request->getPost());
if ($form->isValid()) {
/** #var Cicade $cicade */
$cicade = $form->getObject();
$this->getObjectManager()->persist($cicade);
try {
$this->getObjectManager()->flush();
} catch (Exception $e) {
throw new Exception('Could not save. Error was thrown, details: ', $e->getMessage());
}
$this->redirect()->toRoute('cicade/view', ['id' => $entity->getId()]);
}
}
return [
'form' => $form,
'validationMessages' => $form->getMessages() ?: '',
];
}
/**
* #return CicadeForm
*/
public function getCicadeForm() : CicadeForm
{
return $this->cicadeForm;
}
/**
* #param CicadeForm $cicadeForm
*
* #return EditController
*/
public function setCicadeForm(CicadeForm $cicadeForm) : EditController
{
$this->cicadeForm = $cicadeForm;
return $this;
}
/**
* #return ObjectManager|EntityManager
*/
public function getObjectManager() : ObjectManager
{
return $this->objectManager;
}
/**
* #param ObjectManager|EntityManager $objectManager
*
* #return EditController
*/
public function setObjectManager(ObjectManager $objectManager) : EditController
{
$this->objectManager = $objectManager;
return $this;
}
}
So, felt like giving a really expanded answer. Covers the whole thing really.
If you have any questions about the above, let me know ;-)
I'm receiving this error on Symfony 2.7.9:
Neither the property "puestos" nor one of the methods "addPuesto()"/"removePuesto()", "setPuestos()", "puestos()", "__set()" or "__call()" exist and have public access in class "UserBundle\Entity\UsuarioTrabajador".
<?php
namespace UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use PUGX\MultiUserBundle\Validator\Constraints\UniqueEntity;
/**
* #ORM\Entity
* #ORM\Table(name="usuario_trabajador")
* #UniqueEntity(fields = "username", targetClass = "UserBundle\Entity\Usuario", message="fos_user.username.already_used")
* #UniqueEntity(fields = "email", targetClass = "UserBundle\Entity\Usuario", message="fos_user.email.already_used")
*
* #UniqueEntity(fields = {"nit","dpi"}, targetClass = "UserBundle\Entity\UsuarioTrabajador", message="El dpi o nit debe ser único")
* Esta entidad cubre los tipos de Asistente, Supervisor y Gerente.
*
* #author Pablo Díaz soporte#newtonlabs.com.gt
*/
class UsuarioTrabajador extends Usuario
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
* #ORM\Column(name="direccion",type="string",length=255)
*/
private $direccion;
/**
* #var date fecha de egreso de la empresa.
* #ORM\Column(name="fechaEgreso", type="date",nullable=true)
*/
private $fechaEgreso;
/**
* #var string DPI del trabajador
* #ORM\Column(name="dpi",type="string",length=20, unique=true)
*/
private $dpi;
/**
* Número de identificación tributaria.
*
* #var string
* #ORM\Column(name="nit", type="string",length=20,unique=true)
*/
private $nit;
/**
* #var int
* #ORM\Column(name="telefono", type="string",length=15,nullable=true)
*/
private $telefono;
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="DatosPrestaciones", mappedBy="usuario")
*/
private $datosPrestaciones;
/**
* #var string Tipo de Usuarios(Asistente, Supervisor, Gerente)
* #ORM\OneToMany(targetEntity="UserBundle\Entity\Puesto", mappedBy="usuarioPuesto")
*/
private $puestos;
/**
* Constructor.
*/
public function __construct()
{
$this->datosPrestaciones = new \Doctrine\Common\Collections\ArrayCollection();
$this->puestos = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Set direccion
*
* #param string $direccion
*
* #return UsuarioTrabajador
*/
public function setDireccion($direccion)
{
$this->direccion = $direccion;
return $this;
}
/**
* Get direccion
*
* #return string
*/
public function getDireccion()
{
return $this->direccion;
}
/**
* Set fechaEgreso
*
* #param \DateTime $fechaEgreso
*
* #return UsuarioTrabajador
*/
public function setFechaEgreso($fechaEgreso)
{
$this->fechaEgreso = $fechaEgreso;
return $this;
}
/**
* Get fechaEgreso
*
* #return \DateTime
*/
public function getFechaEgreso()
{
return $this->fechaEgreso;
}
/**
* Set dpi
*
* #param string $dpi
*
* #return UsuarioTrabajador
*/
public function setDpi($dpi)
{
$this->dpi = $dpi;
return $this;
}
/**
* Get dpi
*
* #return string
*/
public function getDpi()
{
return $this->dpi;
}
/**
* Set nit
*
* #param string $nit
*
* #return UsuarioTrabajador
*/
public function setNit($nit)
{
$this->nit = $nit;
return $this;
}
/**
* Get nit
*
* #return string
*/
public function getNit()
{
return $this->nit;
}
/**
* Set telefono
*
* #param string $telefono
*
* #return UsuarioTrabajador
*/
public function setTelefono($telefono)
{
$this->telefono = $telefono;
return $this;
}
/**
* Get telefono
*
* #return string
*/
public function getTelefono()
{
return $this->telefono;
}
/**
* Add datosPrestacione
*
* #param \UserBundle\Entity\DatosPrestaciones $datosPrestacione
*
* #return UsuarioTrabajador
*/
public function addDatosPrestacione(\UserBundle\Entity\DatosPrestaciones $datosPrestacione)
{
$this->datosPrestaciones[] = $datosPrestacione;
return $this;
}
/**
* Remove datosPrestacione
*
* #param \UserBundle\Entity\DatosPrestaciones $datosPrestacione
*/
public function removeDatosPrestacione(\UserBundle\Entity\DatosPrestaciones $datosPrestacione)
{
$this->datosPrestaciones->removeElement($datosPrestacione);
}
/**
* Get datosPrestaciones
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getDatosPrestaciones()
{
return $this->datosPrestaciones;
}
/**
* Add puesto
*
* #param \UserBundle\Entity\Puesto $puesto
*
* #return UsuarioTrabajador
*/
public function addPuesto(\UserBundle\Entity\Puesto $puesto)
{
$this->puestos[] = $puesto;
return $this;
}
/**
* Remove puesto
*
* #param \UserBundle\Entity\Puesto $puesto
*/
public function removePuesto(\UserBundle\Entity\Puesto $puesto)
{
$this->puestos->removeElement($puesto);
}
/**
* Get puestos
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getPuestos()
{
return $this->puestos;
}
public function __toString()
{
return $this->nombre.' '.$this->apellidos;
}
}
This is the other Entity
<?php
namespace UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Puesto
*
* #ORM\Table()
* #ORM\Entity
*/
class Puesto
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="tipoPuesto",type="string",length=100)
*/
private $tipoPuesto;
/**
* #var string
*
* #ORM\Column(name="nombrePuesto", type="string", length=255)
*/
private $nombrePuesto;
/**
* #var \DateTime
*
* #ORM\Column(name="date", type="date")
*/
private $date;
/**
* #ORM\ManyToOne(targetEntity="UserBundle\Entity\UsuarioTrabajador", inversedBy="puestos")
* #var [type]
*/
private $usuarioPuesto;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set nombrePuesto
*
* #param string $nombrePuesto
*
* #return Puesto
*/
public function setNombrePuesto($nombrePuesto)
{
$this->nombrePuesto = $nombrePuesto;
return $this;
}
/**
* Get nombrePuesto
*
* #return string
*/
public function getNombrePuesto()
{
return $this->nombrePuesto;
}
/**
* Set date
*
* #param \DateTime $date
*
* #return Puesto
*/
public function setDate($date)
{
$this->date = $date;
return $this;
}
/**
* Get date
*
* #return \DateTime
*/
public function getDate()
{
return $this->date;
}
/**
* Set usuario
*
* #param \UserBundle\Entity\UsuarioTrabajdor $usuario
*
* #return Puesto
*/
public function setUsuario(\UserBundle\Entity\UsuarioTrabajdor $usuario = null)
{
$this->usuario = $usuario;
return $this;
}
/**
* Get usuario
*
* #return \UserBundle\Entity\UsuarioTrabajdor
*/
public function getUsuario()
{
return $this->usuario;
}
/**
* Set tipoPuesto
*
* #param string $tipoPuesto
*
* #return Puesto
*/
public function setTipoPuesto($tipoPuesto)
{
$this->tipoPuesto = $tipoPuesto;
return $this;
}
/**
* Get tipoPuesto
*
* #return string
*/
public function getTipoPuesto()
{
return $this->tipoPuesto;
}
/**
* Mostrar el noombre del puesto
* #return string
*/
public function __toString()
{
return $this->tipoPuesto.': '.$this->nombrePuesto.' '.$this->getUsuarioPuesto();
}
/**
* Set usuarioPuesto
*
* #param \UserBundle\Entity\UsuarioTrabajador $usuarioPuesto
*
* #return Puesto
*/
public function setUsuarioPuesto(\UserBundle\Entity\UsuarioTrabajador $usuarioPuesto = null)
{
$this->usuarioPuesto = $usuarioPuesto;
return $this;
}
/**
* Get usuarioPuesto
*
* #return \UserBundle\Entity\UsuarioTrabajador
*/
public function getUsuarioPuesto()
{
return $this->usuarioPuesto;
}
}
This is the Form
<?php
namespace UserBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
class RegistrationTrabajadorFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// agregar campos personalizados
$builder->add('nombre', null, [
'label' => 'Nombre/s',
'attr' => [
'placeholder' => 'Nombre/s',
'class' => 'form-control input-lg',
],
])
->add('apellidos', null, [
'label' => 'Apellidos/s',
'attr' => [
'placeholder' => 'Apellidos',
'class' => 'form-control input-lg',
],
])
->add('username', null, [
'label' => 'Usuario',
'translation_domain' => 'FOSUserBundle',
'attr' => [
'class' => 'form-control input-lg',
'placeholder' => 'Nombre de Usuario',
],
])
->add('email', 'email', [
'label' => 'Correo',
'translation_domain' => 'FOSUserBundle',
'required' => true,
'attr' => [
'class' => 'form-control input-lg',
'placeholder' => 'Correo electrónico',
],
])
->add('plainPassword', 'repeated', [
'label' => 'Contraseña',
'type' => 'password',
'options' => ['translation_domain' => 'FOSUserBundle'],
'first_options' => [
'label' => 'Contraseña',
'attr' => [
'class' => 'form-control input-lg',
'placeholder' => 'Contraseña',
],
],
'second_options' => [
'label' => 'Repetir Contraseña',
'attr' => [
'class' => 'form-control input-lg',
'placeholder' => 'Repetir Contraseña',
],
],
'invalid_message' => 'fos_user.password.mismatch',
])
->add('direccion',null,[
'label' => 'Dirección',
'attr' => [
'class' => 'form-control input-lg',
'placeholder' => 'Dirección',
],
'required' => true,
])
->add('dpi', null, [
'label' => 'DPI',
'attr' => [
'class' => 'form-control input-lg',
'placeholder' => 'Documento Personal de Identificación',
],
'required' => true,
])
->add('nit', null, [
'label' => 'NIT',
'attr' => [
'class' => 'form-control input-lg',
'placeholder' => 'Número de Identificación Tributaria',
],
'required' => true,
])
->add('telefono', null, [
'label' => 'Teléfono',
'translation_domain' => 'FOSUserBundle',
'attr' => [
'class' => 'form-control input-lg',
'placeholder' => 'Teléfono',
],
'required' => false,
])
->add('submit', 'submit', [
'label' => 'Guardar',
'attr' => [
'class' => 'btn btn-primary',
],
])
->add('puestos', 'entity', [
'label' => false,
'empty_value' => 'Aquí aparecerá su puesto',
'class' => 'UserBundle:Puesto',
'attr' => [
'class' => 'select2',
],
])
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'validation' => ['registration'],
]);
}
public function getParent()
{
return 'fos_user_registration';
}
public function getName()
{
return 'user_registration';
}
}
I have created Review model for the table. after that while creating views and controllers for the same table it shows PHP Compile error.
PHP Compile Error – yii\base\ErrorException
Declaration of app\models\Review::getRelation() must be compatible with yii\db\ActiveRecordInterface::getRelation($name, $throwException = true)
Here is the full error page
http://pastebin.com/kf8RFun8 .
I have created rest of the MVCs for my tables. I get error only for this.
My Model Class
app\models\Review
Search Model Class
app\models\ReviewSearch
Controller Class
app\controllers\ReviewController
Note: while creating this same in Yii2-Advanced It shows Error (#64) Internal Server Error
Review Model:
<?php
namespace app\models;
use Yii;
/**
* This is the model class for table "review".
*
* #property string $id
* #property string $title
* #property string $reviewer_id
* #property string $timestamp
* #property string $description
* #property string $organization_id
* #property integer $rating
* #property string $relation_id
* #property integer $send_msg
* #property string $org_contact_email
* #property string $org_contact_msg
*
* #property Answer[] $answers
* #property Reviewer $reviewer
* #property Organization $organization
* #property Relation $relation
*/
class Review extends \yii\db\ActiveRecord
{
/**
* #inheritdoc
*/
public static function tableName()
{
return 'review';
}
/**
* #inheritdoc
*/
public function rules()
{
return [
[['title', 'reviewer_id', 'organization_id', 'rating', 'relation_id'], 'required'],
[['reviewer_id', 'organization_id', 'rating', 'relation_id', 'send_msg'], 'integer'],
[['timestamp'], 'safe'],
[['title'], 'string', 'max' => 45],
[['description'], 'string', 'max' => 2000],
[['org_contact_email'], 'string', 'max' => 60],
[['org_contact_msg'], 'string', 'max' => 1000]
];
}
/**
* #inheritdoc
*/
public function attributeLabels()
{
return [
'id' => 'ID',
'title' => 'Title',
'reviewer_id' => 'Reviewer ID',
'timestamp' => 'Timestamp',
'description' => 'Description',
'organization_id' => 'Organization ID',
'rating' => 'Rating',
'relation_id' => 'Relation ID',
'send_msg' => 'Send Msg',
'org_contact_email' => 'Org Contact Email',
'org_contact_msg' => 'Org Contact Msg',
];
}
/**
* #return \yii\db\ActiveQuery
*/
public function getAnswers()
{
return $this->hasMany(Answer::className(), ['review_id' => 'id']);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getReviewer()
{
return $this->hasOne(Reviewer::className(), ['id' => 'reviewer_id']);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getOrganization()
{
return $this->hasOne(Organization::className(), ['id' => 'organization_id']);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getRelation()
{
return $this->hasOne(Relation::className(), ['id' => 'relation_id']);
}
}
You need to rename your getRelation() method as, you are overriding yii\db\ActiveRecordInterface::getRelation($name, $throwException = true). So this will cause an exception that getRelation method has an invalid declaration.