I have an entity Calendar with dateFrom and dateTo properties.
Now in my form I have one hidden input with date formatted like this: 2010-01-01,2011-01-01.
How can I write a data transformer in Symfony2 which will allow me to transform this date to TWO properties?
I think that the transformer himself has nothing to do with the "properties", it just handle transformation from a data structure to another data structure. You just have to handle the new data structure on your code base.
The transformer himself might look like thisĀ :
class DateRangeArrayToDateRangeStringTransformer implements DataTransformerInterface
{
/**
* Transforms an array of \DateTime instances to a string of dates.
*
* #param array|null $dates
* #return string
*/
public function transform($dates)
{
if (null === $dates) {
return '';
}
$datesStr = $dates['from']->format('Y-m-d').','.$dates['to']->format('Y-m-d');
return $datesStr;
}
/**
* Transforms a string of dates to an array of \DateTime instances.
*
* #param string $datesStr
* #return array
*/
public function reverseTransform($datesStr)
{
$dates = array();
$datesStrParts = explode(',', $datesStr);
return array(
'from' => new \DateTime($datesStrParts[1]),
'to' => new \DateTime($datesStrParts[2])
);
}
}
You can use the explode function like that :
$dates = explode(",", "2010-01-01,2011-01-01");
echo $dates[0]; // 2010-01-01
echo $dates[1]; // 2011-01-01
Then create two new DateTime.
If it's possible, use 2 hidden fields. Then use a DateTime to String datatransformer on each field. Then your form is logically mapped to your entity.
I solved a similar problem by adding a custom getter/setter to my entity (for example, getDateIntervalString and setDateIntervalString). The getter converts dateTo and dateFrom into the interval string and returns it, and the setter accepts a similarly formatted string and uses it to set dateTo and dateFrom. Then, add the field to the form like this:
$builder->add('dates', 'text', ['property_path' => 'date_interval_string'])
By overriding the property path, your custom getter and setter will be used.
Related
My Setup is a Symfony 3.4 App with the typical 'ManyToMany'-Relation with additional fields, something like this:
Entity Article
Entity Specialty
Entity ArticleSpecialtyRelation
In a Form for an Article i wanted it to look like as if it were a ManyToMany-Relation rendered as an EntityType with multiple=true and expanded=true, so all entries of Specialty are rendered as checkboxes.
To achieve that i created a non orm-mapped property specialties that is an ArrayCollection, gets initialized in the Constructor and has a Getter, Adder and Remover.
/**
*
* #var ArrayCollection;
*
*/
protected $specialties;
public function __construct()
{
$this->specialties = new ArrayCollection();
}
/**
* #return Collection|Specialty[]
*/
public function getSpecialties()
{
return $this->specialties;
}
/**
* #param Specialty $specialties
*/
public function addSpecialties(Specialty $specialties)
{
$this->specialties->add($specialties);
}
/**
* #param Specialty $specialties
*/
public function removeSpecialties(Specialty $specialties)
{
$this->specialties->removeElement($specialties);
}
This property is used to render the Specialty Entity as checkboxes:
add('specialties', EntityType::class,array(
'class' => Specialty::class,
'expanded'=>true,
'multiple'=>true,
'label'=>'Specialties',
'required' => false,
'mapped'=>true,
));
To populate it with the data from SpecialtyRelation i added a PreSetData Formevent:
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$form = $event->getForm();
$article = $event->getData();
if ($article instanceof Article) {
$form->get('specialties')->setData($article->getUsedSpecialties());
}
});
The used Getter of $artikel just iterates over $article->getArtikelSpecialties and returns a collection of Specialty.
It all works until the submit. Because the formfield is mapped=true, somewhere in handleRequest($form) where the entity is hydrated with the form data, it explodes when the Adder for $specialty is called:
Call to a member function add() on null
Because as i just learned, the Constructor is never called by Doctrine and obviously initializes all ORM-ArrayCollections but not the ArrayCollection for the non-mapped property specialties -
Of course I can check if the ArrayCollection is initialized in the Adder and Remover and initialize it there if it is null, but that just feels a bit hacky in a already at least hacky-felt setup and i am wondering if my setup is completely stupid, especially since i didn't find anybody trying to do that (or getting problems with that) on here or elsewhere.
Is there a better solution to this or should i just check the ArrayCollection in Adder and Remover and live happily ever after?
Also, just curious, is there any other way to initialize the ArrayCollection?
P.S. If there are typos in the names it's because i translated the names into english.
Partial Stacktrace
Symfony\Component\Debug\Exception\FatalThrowableError: Call to a
member function add() on null
at src/Test/Bundle/TestBundle/Entity/Article.php:312 at
Test\Bundle\TestBundle\Entity\Article->addSpecialties(object(Specialty))
(vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php:674)
at
Symfony\Component\PropertyAccess\PropertyAccessor->writeCollection(array(object(Article),
object(Article)), 'specialties', object(ArrayCollection),
'addSpecialties', 'removeSpecialties')
(vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php:622)
at
Symfony\Component\PropertyAccess\PropertyAccessor->writeProperty(array(object(Article),
object(Article)), 'specialties', object(ArrayCollection))
(vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php:216)
at
Symfony\Component\PropertyAccess\PropertyAccessor->setValue(object(Article),
object(PropertyPath), object(ArrayCollection))
(vendor/symfony/symfony/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php:86)
at
Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper->mapFormsToData(object(RecursiveIteratorIterator),
object(Article))
(vendor/symfony/symfony/src/Symfony/Component/Form/Form.php:636) at Symfony\Component\Form\Form->submit(array(), true)
(vendor/symfony/symfony/src/Symfony/Component/Form/Form.php:580)
With Symfony 2.7, you could customize a form's name in your EntityType class with the method getName()
This is now deprecated. Is there another way to do that with Symfony 3.0 ?
I have custom prototype entry_rows for collections that I would need to use in different forms.
Since the name of the rows is based on the form's name, I would need to change the later in order to use them with a different form.
You should implements the getBlockPrefix method instead of getName as described in the migration guide here.
As example:
/**
* Returns the prefix of the template block name for this type.
*
* The block prefix defaults to the underscored short class name with
* the "Type" suffix removed (e.g. "UserProfileType" => "user_profile").
*
* #return string The prefix of the template block name
*/
public function getBlockPrefix()
{
return "form_name";
}
Hope this help
Depending on how your form is built, there is different ways to set the name of your form.
If you are creating the form through $this->createForm(CustomType::class):
$formFactory = $this->get('form.factory');
$form = $formFactory->createNamed('custom_form_name', CustomType::class);
If you are building the form from the controller directly through $this->createFormBuilder():
$formFactory = $this->get('form.factory');
$form = $formFactory->createNamedBuilder('custom_form_name', CustomType::class);
Look at the FormFactory and FormBuilder APIs for more information.
You can try it, remove prefix on field name
public function getBlockPrefix()
{
return null;
}
I have huge user update form. Sometimes update contains huge amount of fields, sometimes just one or two. This is my code:
public function updateUser(Request $request){
$user = User::where('id',$request->id)->firstOrFail();
if($request->first_name){
$user->first_name= $request->first_name;
}
if($request->last_name){
$user->last_name = $request->last_name;
}
if($request->job_name){
$user->job_name= $request->job_name;
}
//etc.. 20 more fields
$user->save();
It is possible to set model attributes dependent on fields in $request? Sometimes $request contains 1 field, sometimes 20. Please notice I want to touch database only once, using save() method at the end.
$user->update($request->all());
Make sure all necessary variables are specified in your $fillable array for User model
If you want to update model attributes without saving use fill method
If $request field name and Model field name are same(as it seems in your current code) try this:
$input = $request->all();
$user = User::firstOrFail('id',$input->id);
$updateNow = $user->update($input);
Another option is:
DB::table('users')
->where('id', $request->id)
->update($request); //or can use Input::all()
Have a look at it as well for more explanation: Query Builder
for User model
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password', 'facebook_id', 'job_name', '20 more fields...'
];
for Controller
public function store(Request $request){
$allRequest = $request->all();
// It is not in table
unset($allRequest['_token']);
User::create($allRequest);
}
I have Eloquent Event model, which is related towards multiple dates like this:
$event->dates // shows Collection of 8 Eloquent date models
After that i need to pick the only date, what is closest to current time. I know how to do this using query of raw SQL, or DB class. But isnt there any better solution? I dont want to jump into database for data, I already have.
Date format in eloquent models is surprisingly string.
You can use what we call in laravel mutators like this ->
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Event extends Model
{
public function dates()
{
return $this->hasMany('Date');
}
/**
* Get Dates for the event.
*
* #param string $value
* #return array
*/
public function getDates()
{
$dates = $this->dates()->getQuery()->orderBy('created_at', 'asc')->get();
return $dates;
}
}
Hope this helps.
UPDATE
I think now you can also directly do this in the model definition like this -
return $this->hasMany('Date')->orderBy('created_at', 'asc')
I have a form with Sonata Admin Bundle with a date, to set the birthday of the user we want to add. Here goes MemberAdmin.php :
/**
* #param \Sonata\AdminBundle\Form\FormMapper $formMapper
*
* #return void
*/
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('General')
->add('username')
->add('name')
->add('surname')
->add('birthdate', 'birthday', array('format' => 'yyyy-MM-dd'))
// ...
}
And my problem is when I send the form, I obtain Error: Call to a member function format() on a non-object ... But if I do print_r($birthdate) in the Entity class it shows me the DateTime object ...
Here are the interesting Entity parts:
/**
* #var date
*
* #ORM\Column(name="birthdate", type="date", nullable=true, options={"default" = "1990-01-01 00:00:00"})
* #Assert\DateTime()
*/
private $birthdate;
/**
* Set birthdate
*
* #param \DateTime $birthdate
* #return Membre
*/
public function setBirthdate($birthdate)
{
$this->birthdate = $birthdate;
return $this;
}
/**
* Get birthdate
*
* #return \DateTime
*/
public function getBirthdate()
{
return $this->birthdate;
}
My problem, currently, is that I don't know what I should do, I just want the date, no time, no anything else, i don't know if the column should be date (I work with PostgreSQL). What should I use for the types of my variables, I feel lost here, no simple Date possible ??
I tried to figure out from where it could come, but when I change too much I end up with: This form should not contain extra fields directly in the form, or even Incorrect value, but the field is a valid date ...
Thanks for your help !!
Change your field type to sonata_type_date_picker and test if the error message persist.
->add('birthdate', 'sonata_type_date_picker', array(
'format' => 'dd/MM/yyyy',
'widget' => 'single_text',
'label' => 'Birthdate',
))
From manual (sonata-project.org) :
If no type is set, the Admin class will use the one set in the
doctrine mapping definition.
So, you can try this:
->add('birthdate', null, array('format' => 'yyyy-MM-dd'));
#wr0ng.name you should never overwrite vendor code. NEVER.
There is something wrong with your mapping somewhere. You can use doctrine's commands to check your entity.
Edit
As #rande said, modifying vendors files is not the way to go, it provided an easy temp workaround for a local private app. As it is not dedicated to stay like that, I took care of the issue once I had more time. Sorry for the delay to come back to you guys.
I played around, tried with multiple setups, it took me time to figure it out, but I finally came to the conclusion that the issue... was caused by another date, that I was generating wrong in the constructor one line above.
Also, thanks to all of you, that guided me on the right path!