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);
}
Related
I have created a small Symfony (Sf3.2 + php7) web with a Task Entity. I have my controller where I have a new action to create a new task. Everything is fine. Now, some of the form fields are not necessary to be filled (created date, for example). So I have removed from my taskType:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add->('name')->add('description');
}
Now my form looks exactly as I want. The surprise is that when I submit it my controller pass it to doctrine,and every missing field is write as a NULL. NO!, I don't want that, what I wanted is my default mysql value.
Ok, I read the doc. There seems to be two ways of handling data:
$form->handleRequest($request);
$form->submit($request->request->get($form->getName()));
I've found in the API, (not in the doc) that submit have a second parameter, a boolean:
API says:
bool $clearMissing Whether to set fields to NULL when they are missing in the submitted data.
Great! this is exactly what I need. Lets see it:
public function newAction(Request $request) {
$task = new Task();
$form = $this->createForm('myBundle\Form\TaskType', $task);
$form->submit($request->request->get($form->getName()), false);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($task);
$em->flush();
$response = new Response();
$response->setContent(json_encode(array(
'data' => 'success',
)));
$response->headers->set('Content-Type', 'application/json');
return $response;
return $this->redirectToRoute('task_success');
}
return $this->render('task/new.html.twig', array(
'task' => $task,
'form' => $form->createView(),
));
}
Well, after tried everything I always get NULL in all missing fields. What am I doing wrong.
Thank you, Caterina.
I'm afraid there must be something more. And I was thinking that perhaps is not Doctrine the solution. The Symfony log shows me the insert and it's setting a Null in every empty field, on empty fields and missing fields. In fields like status i could set a default value, at doctrine level, but if I want to set created_at field, I suppose that must be Mysql the responsible is setting current timeStamp.
Anyway this is my ORM property status from Task Entity.
/**
* #var \scrumBundle\Entity\Taskstatus
*
* #ORM\ManyToOne(targetEntity="scrumBundle\Entity\Taskstatus")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="status", referencedColumnName="id")
* })
* #ORM\Column(name="status", type="integer", nullable=false,options={"default":1})
*/
private $status;
And this is the property id from TaskStatus:
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false, options={"default":1})
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
As you see I tried to follow your advise of setting the option default value. I've tried to create to set the form with handleRequest and submit with same result
$task = new Task();
$form = $this->createForm('scrumBundle\Form\TaskType', $task);
//$form->handleRequest($request);
$form->submit($request->request->get($form->getName()), false);
if ($form->isSubmitted() && $form->isValid()) {
I even tried to debug step by step submit function, a real Hell, because submit is a recursive function to much complex for me.
Ok , again thank for you time.
Regards, Cate
PS. Sorry about my poor english.;^)
Short answer - when you persist a PHP object through Doctrine, you must have absolutely every value set that you want. If your PHP object has null fields, Doctrine will manually set them as null in your entity. Doctrine doesn't assume that just because a value is null in your PHP object, that you don't want it included on your INSERT statement. This is because null is a perfectly valid SQL value.
So, whatever your PHP object is at the time of insert is exactly what is going to be inserted into your database, null values and all.
When you are editing an entry, Doctrine will only update the fields that are different, so this isn't a concern. Your concern is when persisting entities.
The easiest solution is to copy your MySQL default value into your PHP entity. Like one of these many ways:
// set default value on the variable itself
public $someVar = 100;
// set default values in the constructor (so when creating a new entry)
public function __construct()
{
$this->createdAt = new \DateTime();
$this->isActive = true;
}
You can also use Lifecycle Callbacks to set when inserting a new entry:
/**
* #ORM\Entity()
* #ORM\HasLifecycleCallbacks()
*/
class Task
{
// ...
/**
* #ORM\PrePersist
*/
public function setCreatedAtValue()
{
$this->createdAt = new \DateTime();
}
}
I would like to build a preview page for a create form. I set "deleted" property of the record to "1" when in previewAction because in the BE the list module is used to approve the inserted records - so if the record was never finally saved its deleted anyway.
Problem: I can create the record (deleted=1) - I can jump back to the form (no history back for I have to keep the created object). But if I submit again the property mapping tells me
Object of type MyModel with identity "3" not found.
Of course that's because its deleted. The settings in the Repository to ignore deleted are not taking action here.
Yes I could bypass the Extbase magic by filling up everything manually, but this is not what I want.
Here is the action to get an idea what I'm trying
/**
* action preview
*
* #param MyModel
* #return void
*/
public function previewAction(MyModel $newModel)
{
//check if model was already saved
$uid = $this->request->hasArgument('uid') ? this->request->getArgument('uid') : 0;
if($uid){
$newModel = $this->myRepository->findDeletedByUid($uid);
$this->myRepository->update($newModel);
}
else{
$newModel->setDeleted(true);
$this->myRepository->add($newModel);
}
$this->view->assign('ad', $newModel);
$this->persistenceManager->persistAll();
$uid = $this->persistenceManager->getIdentifierByObject($newModel);
$this->view->assign('uid', $uid);
}
Any ideas?
The Extbase default query settings suppress deleted objects.
Since you've already stated the custom query findDeletedByUid() in your repository, you just need to set it to include deleted records. It is important, however, that if you want to call your controller action using the object, you'll have to retrieve it before calling the action. Use an initialization action for that. The initializaton will be called automatically before the action.
If you want to set wether the object is deleted, you'll also going to need to define a property, getter and setter in your Domain Model and a proper definition in your tca to enable the data mapper to access the column.
In the repository:
public function findDeletedByUid($uid) {
$query = $this->createQuery();
$query->getQuerySettings()->setIncludeDeleted(true);
$query->matching(
$query->equals('uid',$uid)
);
return $query->execute();
}
In your Controller class:
/**
* initialize action previewAction
* Overrides the default initializeAction with one that can retrieve deleted objects
*/
public function initializePreviewAction(){
if( $this->request->hasArgument('mymodel') ){
$uid = $this->request->getArgument('mymodel');
if( $mymodel = $this->mymodelRepository->findDeletedByUid($uid) ){
$this->request->setArgument($mymodel);
} else {
// handle non retrievable object here
}
} else {
// handle missing argument here
}
}
In your Domain Model:
...
/**
* #var bool
*/
protected $deleted;
/**
* #return bool
*/
public function getDeleted() {
return $this->deleted;
}
/**
* #param bool $deleted
*/
public function setDeleted($deleted) {
$this->deleted = $deleted;
}
In your tca.php
...
'deleted' => array(
'exclude' => 1,
'label' => 'LLL:EXT:lang/locallang_general.xlf:LGL.deleted',
'config' => array(
'type' => 'check',
),
),
Instead of doing any magic with deleted, you should use the hidden field to allow editors to preview documents.
You can tell your query to include hidden records inside the repository.
Your findDeletedByUid($uid) function caught my eye. If it's not a custom function, should it use something like findByDeleted(TRUE) or findByDeleted(1) in combination with ->getFirst() or ->findByUid()? You can find discussions in the Extbase manual reference and the Repository __call() function API sections.
Thanks for all hints.
I think depending to the answers its not possible without bypass extbase property-mapping magic. So I think in general its not a good idea to do it like that.
So I put now my own flag "stored" to the model.
In BE List-Module the not "stored" objects are still visible, but using an own BE Module or deleting the not "stored" object by a cron-job should do the job.
If anyone has a bedder idea feel free to share it :-)
In my model I have a Recipe entity and Ingredient entity. In Recipe entity, the relation is defined like this:
/**
* #ORM\OneToMany(targetEntity="Ingredient", mappedBy="recipe", cascade={"remove", "persist"}, orphanRemoval=true)
* #ORM\OrderBy({"priority" = "ASC"})
*/
private $ingredients;
In Ingredient entity:
/**
* #ORM\ManyToOne(targetEntity="Recipe", inversedBy="ingredients")
* #ORM\JoinColumn(name="recipe_id", referencedColumnName="id")
*/
private $recipe;
I am working on CRUD controller for the recipe and I want the user to be able to add ingredients dynamically. I also want the user to drag-and-drop ingredients to set their priority (order) in the recipe. I am using CollectionType form field for this.
and this page as tutorial:
http://symfony.com/doc/current/cookbook/form/form_collections.html
Adding and showing of the recipe are working perfectly so far, however there is a problem with Edit/Update action, which I will try to describe below:
In the controller, I load the entity and create the form like this:
public function updateAction($id, Request $request)
{
$em = $this->getDoctrine()->getManager();
$recipe = $em->getRepository('AppBundle:Recipe')->find($id);
$form = $this->createEditForm($recipe);
$form->handleRequest($request);
...
}
Since the priority is saved in the DB, and I have #ORM\OrderBy({"priority" = "ASC"}), the initial loading and display of ingredients works fine. However if the user drags and drops ingredients around, the priority values change. In case there are form validation errors and the form needs to be displayed repeatedly, ingredients inside the form get displayed in the old order, even though priority values are updated.
For example, I have the following initial Ingredient => priority values in DB:
A => 1
B => 2
C => 3
The form rows are displayed in order: A,B,C;
After user changes the order, I have:
B => 1
A => 2
C => 3
but the form rows are still displayed as A,B,C;
I understand that the form has been initialized with order A,B,C, and updating priority doesn't change the element order of ArrayCollection. But I have (almost) no idea how to change it.
What I have tried so far:
$form->getData();
// sort in memory
$form->setData();
This doesn't work, as apparently it isn't allowed to use setData() on form which already has input.
I have also tried to set a DataTransformer to order the rows, but the form ignores new order.
I have also tried to use PRE/POST submit handlers in the FormType class to order the rows, however the form still ignores the new order.
The last thing that (kind of) works is this:
In Recipe entity, define sortIngredients() method which sorts ArrayCollection in memory,
public function sortIngredients()
{
$sort = \Doctrine\Common\Collections\Criteria::create();
$sort->orderBy(Array(
'priority' => \Doctrine\Common\Collections\Criteria::ASC
));
$this->ingredients = $this->ingredients->matching($sort);
return $this;
}
Then, in the controller:
$form = $this->createEditForm($recipe);
$form->handleRequest($request);
$recipe->sortIngredients();
// repeatedly create and process form with already sorted ingredients
$form = $this->createEditForm($recipe);
$form->handleRequest($request);
// ... do the rest of the controller stuff, flush(), etc
This works, but the form is created and processed twice, and honestly it looks like a hack...
I am looking for a better way to solve the problem.
You need to use finishView method of your form type.
Here is the example of code:
public function finishView(FormView $view, FormInterface $form, array $options)
{
usort($view['photos']->children, function (FormView $a, FormView $b) {
/** #var Photo $objectA */
$objectA = $a->vars['data'];
/** #var Photo $objectB */
$objectB = $b->vars['data'];
$posA = $objectA->getSortOrder();
$posB = $objectB->getSortOrder();
if ($posA == $posB) {
return 0;
}
return ($posA < $posB) ? -1 : 1;
});
}
It is possible to combine arrow function https://www.php.net/manual/en/functions.arrow.php with spaceship https://www.php.net/manual/en/migration70.new-features.php with PHP 7.
With the previous example :
public function finishView(FormView $view, FormInterface $form, array $options)
{
usort($view['photos']->children, fn (FormView $a, FormView $b) => $a->vars['data']->getSortOrder() <=> $b->vars['data']->getSortOrder());
}
A more advanced example, ordered any collection from everywhere by using any ordered field :
public function finishView(FormView $view, FormInterface $form, array $options)
{
$view['blocks']->children = array_merge($view['blockTexts']->children, $view['blockImages']->children);
usort($view['blocks']->children, fn (FormView $a, FormView $b) => $a->vars['data']->orderNumber <=> $b->vars['data']->orderNumber);
}
Using:
Symfony 2.5
SonataAdminBundle
I am trying to change one of the entity fields (title) when data is submitted / saved to database by using two fields from associated entites ex.
DocumentRevision <- Document -> CustomEntity [title] = Document[title]+DocumentRevision[number]
But title of CustomEntity has to be unique - this was the problem I was trying to solve and managed with Database constraints and UniqueEntity validation (not quite - more on this later).
Now the issue is that I change the title data on Doctrine preUpdate/Persist effectivly skipping validation for that field since it's empty at validation time. When user puts wrong data Database layer throws an error about duplicate for unique constraint.
/**
* #ORM\PrePersist
* #ORM\PreUpdate
*/
public function setTitleFromDocumentName() {
$this->setTitle($this->getDocument()->getName() . " rev. " . $this->getDocumentRevision()->getRevisionNumber());
}
The entity itself is using UniqueEntity constraint on field title, so custom constraints or validation groups are pointles from my perspective as it would only duplicate the already used constraint.
/**
* #UniqueEntity(
* fields={"title"}
* )
**/
The simplest solution as it seems would be to get somewhere between post Submit before validation, but it would have to be done from Entity.
My question is how can (can it?) be done without overriding SonataCRUD Controller or it's other parts, is it even possible?
It can be done, but there are issues:
I was able to change the title using Form Events like this:
protected function configureFormFields(FormMapper $formMapper) {
...
$builder = $formMapper->getFormBuilder();
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$data = $event->getData();
$form = $event->getForm();
if (!$data) {
return;
}
$data['title'] = $data['document'] . ' rev. ' . $data['documentRevision'];
$event->setData($data);
}
...
formMapper
->add('title',null,array(
...
);
The current problem is that I am getting the IDs of 'document' and 'documentRevision' and I need their names or __toString() representation at least.
Another issue is that although I can set the title using the event it shows error from DB when it should show Form error since validation should be done on FormEvents::SUBMIT - this one I don't understand.
Last thing to note is that if I try to use callback function:
$builder->addEventListener(FormEvents::PRE_SUBMIT, array($this,'onPreSubmit'))
public function onPreSubmit() {
$entity = $this->getSubject();
$entity->setTitleFromDocumentName();
}
I will get null title and errors if Entity tries to get fields from related entites - Calling function on non object.
Regarding entity data maybe this will help you to get the subject:
https://gist.github.com/webdevilopers/fef9e296e77bb879d138
Then you could use getters to get the desired data for instance:
protected function configureFormFields(FormMapper $formMapper)
{
$subject = $this->getSubject();
$formMapper->getFormBuilder()->addEventListener(FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($subject) {
$document = $subject->getDocument();
// ...
});
}
I also posted this on your issue:
https://github.com/sonata-project/SonataAdminBundle/issues/2273
To solved this when I changed the unique entity validation constraints as ones used by me where not completely valid from conceptual perspective.
Also it's important to note that functions that are marked as #PrePersist, #PreUpdate etc. must be public if they are to be used like that, marking them private will make Doctrine fail.
Note that the methods set as lifecycle callbacks need to be public and, when using these annotations, you have to apply the #HasLifecycleCallbacks marker annotation on the entity class.
See: http://doctrine-orm.readthedocs.org/en/latest/reference/events.html#lifecycle-callbacks (first paragraph after the code sample).
For some reason, when I do an mysql db insert from Zend, my row is dulpicated. I've tried a direct insert via phpmyadmin and it works perfect, so its not a mysql server problem.
This is the code I use:
<?php
class Model_Team extends Zend_Db_Table_Abstract {
protected $_name = 'team';
public function createUser($data) {
$user = $this->createRow();
$user->name = $data['name'];
$user->title = $data['title'];
$id = $user->save();
return $id;
}
}
?>
Thanks in advance.
EDIT:
I've found that this duplication only occurs when i call the form via AJAX (modal box), although the form post is normal, not an ajax request)
I don't know why your code is double pumping the database on save but it should'nt matter as you're using the Row object and save(). (save() inserts or updates)
You may want to restructure your createUser() function so that it can't create a new row if the row already exists.
<?php
class Model_Team extends Zend_Db_Table_Abstract {
protected $_name = 'team';
public function createUser(array $data) {
$user = $this->createRow();
//test if user has id in the array
if (array_key_exists('id', $data)){
$user->id = $data['id'];
}
$user->name = $data['name'];
$user->title = $data['title'];
$user->save();
//no need to create a new variable to return the user row
return $user;
}
}
This method will create and update a user row.
To help you further I'll need to see the controller code most of my double pumps have happened there.
Instead of using createRow() have you tried using insert()?
/**
* Insert array of data as new row into database
* #param array $data associative array of column => value pairs.
* #return int Primary Key of inserted row
*/
public function createUser($data)
{
return $this->insert($data);
}
Also - could we see the ajax code? It may be that the form is being posted as well?