Did we migrate incorrectly? Validation not happening in forms migrated from Symfony 2.7 to 4.0 - symfony

In code migrated from Symfony 2.7 to 4.0, validation no longer happens on my form, allowing bad data to pass through and cause a Doctrine constraint violation
I'm new to Symfony and was asked to migrate a 2.7 application to 4.0. I did this in steps (2.7->2.8->3.x->4.0) and addressed issues as they came up, but one thing that broke along the way is automatic form validation. In the original version, if I attempted to create a new user and left the fields blank, it would correctly flag those and pop up " must not be empty" messages in the UI. Now, it lets those past until it attempts to write to the database, at which point Doctrine barfs because the database not null constraints are violated.
I've tried to figure out what I'm doing wrong, but I don't have a firm grasp on how the form creation process and syntax has changed. All of the example documentation on validation in forms assumes the createFormBuilder() approach, and all my existing code uses createForm(). What am I missing?
Here's part of the user object associated with the form showing the #Assert statements that I expect to trigger validation warnings:
/**
* #ORM\Table(name="users")
* #ORM\Entity(repositoryClass="Domain\CoreBundle\Repository\UserRepository")
* #ORM\HasLifecycleCallbacks()
* #UniqueEntity(fields="email", message="This email address is already in usage")
* #UniqueEntity(fields="username", message="This username is already in usage")
*/
class User extends BaseUser implements JsonSerializable
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #Assert\NotBlank(message="Email should not be empty")
* #Assert\Email(strict=true)
* #Assert\Length(max=150, maxMessage="Email should be less than {{ limit }} characters")
*/
protected $email;
/**
* #Assert\NotBlank(message="Username should not be empty")
* #Assert\Regex(
* pattern = "/^\d*[a-zA-Z][ a-zA-Z0-9\!\#\#\$\%\^\&\-\_\=\+\~\?\.]*$/i",
* message = "Username should include at least one letter"
* )
*/
protected $username;
/**
* #var string
*
* #Assert\NotBlank(message="First name should not be empty")
* #ORM\Column(name="first_name", type="string", length=255)
*/
protected $firstName;
/**
* #var string
*
* #Assert\NotBlank(message="Last name should not be empty")
* #ORM\Column(name="last_name", type="string", length=255)
*/
protected $lastName;
(rest of code omitted for conciseness)
And here's the addNew action from the controller (AdministratorController extends UserController):
/**
* Add new administrator
*
* #param Request $request
*
* #return Response
*/
public function addNewAction(Request $request)
{
$company = $this->getCurrentCompany();
$form = $this->createForm(AddAdministratorType::class, null,
array('current_user'=> $this->user, 'restricted_admin'=>$this->getRestrictedAdmin(), 'company'=>$company));
if ($request->getMethod() == Request::METHOD_POST) {
$form->handleRequest($request);
// check if the user already exists
$userManager = $this->get('fos_user.user_manager');
$user = $form->getData();
$oldUser = $userManager->findUserByUsername($user['email']);
if ($oldUser)
{
$alreadyExists = false;
if ($user["isSuperAdmin"] &&$oldUser->isGrantedSuperAdmin())
$alreadyExists = true;
if ($user["isCompanyAdmin"] && $oldUser->isGranted(UserRepository::ROLE_COMPANY_ADMIN, $company))
$alreadyExists = true;
if (!$user["isCompanyAdmin"] && !$user["isSuperAdmin"] && $oldUser->isGranted(UserRepository::ROLE_ADMIN,$company))
$alreadyExists = true;
if ($alreadyExists)
$form->get('email')->addError(new FormError('This email address is already in use'));
}
if ($form->isValid()) {
$user = $form->getData();
if ($oldUser) // if the user already exists, we just need to add the role
{
if (!$this->getUser()->isGrantedSuperAdmin() &&
!in_array($company->getId(), array_map(function($x){return $x->getId();}, $oldUser->getCompaniesWithRole())))
{
// the user isn't currently in this company and the user adding the role
// isn't a super admin, so we have to create a shadow user entry to hide
// the real user info from other in the company until the user logs into
// the company
$oldShadow=$this->em->getRepository(ShadowUser::class)->findOneBy(array("user" => $oldUser, "company"=>$company));
if (!$oldShadow)
{
$shadow = new ShadowUser();
$shadow->setUser($oldUser);
$shadow->setFirstName($user["firstName"]);
$shadow->setLastName($user["lastName"]);
$shadow->setCompany($company);
$shadow->setIsVydioUsed($user["isVydioUsed"]);
$shadow->setVydioRoomLink($user["vydioRoomLink"]);
$shadow->setCreatedDate(new \DateTime());
$this->em->persist($shadow);
}
}
if ($user["isSuperAdmin"])
{
$oldUser->addMyRole(UserRepository::ROLE_SUPER_ADMIN, $company);
$this->get('pp_mailer')->onAddNewRole($oldUser,UserRepository::ROLE_SUPER_ADMIN, $company );
}
if ($user["isCompanyAdmin"])
{
$oldUser->addMyRole(UserRepository::ROLE_COMPANY_ADMIN, $company);
$this->get('pp_mailer')->onAddNewRole($oldUser,UserRepository::ROLE_COMPANY_ADMIN, $company );
}
if (!$user["isSuperAdmin"] && !$user["isCompanyAdmin"])
{
$oldUser->addMyRole(UserRepository::ROLE_ADMIN, $company);
$this->get('pp_mailer')->onAddNewRole($oldUser,UserRepository::ROLE_ADMIN, $company );
}
$programRepo = $this->em->getRepository(ProgramUser::class);
foreach($user["programs"] as $program)
{
$oldRelation = $programRepo->findOneBy(array("user"=> $oldUser, "program"=>$program));
if (!$oldRelation)
{
$relation = new ProgramUser();
$relation->setUser($oldUser);
$relation->setProgram($program);
$relation->setCompany($company);
$this->em->merge($relation);
}
}
$this->em->persist($oldUser);
$this->em->flush();
}
else
{
$newUser = new User();
$newUser->setPassword($this->get('domain_core_service')->generatePassword());
$newUser->setDefaultCompany($company);
$newUser->setFirstName($user["firstName"]);
$newUser->setLastName($user["lastName"]);
$newUser->setEmail($user["email"]);
$newUser->setUsername($user["email"]);
$newUser->setEnabled($user["enabled"]);
$newUser = $this->em->getRepository('DomainCoreBundle:User')->addUserInSystem($userManager, $newUser);
$token = $this->get('domain_core_service')->generateToken();
$newUser->setConfirmationToken($token);
if ($user["isSuperAdmin"])
{
$newUser->addMyRole(UserRepository::ROLE_SUPER_ADMIN, $company);
$this->get('pp_mailer')->onAddNewUser($newUser,UserRepository::ROLE_SUPER_ADMIN, $company );
}
if ($user["isCompanyAdmin"])
{
$newUser->addMyRole(UserRepository::ROLE_COMPANY_ADMIN, $company);
$this->get('pp_mailer')->onAddNewUser($newUser,UserRepository::ROLE_COMPANY_ADMIN, $company );
}
if (!$user["isSuperAdmin"] && !$user["isCompanyAdmin"])
{
$newUser->addMyRole(UserRepository::ROLE_ADMIN, $company);
$this->get('pp_mailer')->onAddNewUser($newUser,UserRepository::ROLE_ADMIN, $company );
}
foreach($user["programs"] as $program)
{
$relation = new ProgramUser();
$relation->setUser($newUser);
$relation->setProgram($program);
$relation->setCompany($company);
$this->em->merge($relation);
}
$this->em->persist($newUser);
$this->em->flush();
}
return $this->redirect($this->generateUrl('domain_admin_show_all_administrators_page'));
}
}
return $this->render(
'DomainAdminBundle:Administrators:add-new.html.twig',
array(
'form' => $form->createView(),
'page_title' => 'Add New Administrator',
'currentSidebar' => $this->currentSideBar,
'currentSidebarItem' => $this->currentSidebarItem,
)
);
}
And the twig file for the form:
{% extends 'DomainAdminBundle::base-admin-layout.html.twig' %}
{% import '::widgets/form_errors.html.twig' as form_custom_errors %}
{% import '::widgets/label.html.twig' as form_custom_labels %}
{% block title %} My Application| {{ page_title }} {% endblock %}
{% block javascripts %}
{{ parent() }}
<script type="text/javascript" src="{{ asset('assets/scripts/admin-add-new.js') }}"></script>
{% endblock %}
{% block stylesheets %}
{{ parent() }}
<link rel="stylesheet" type="text/css" href="{{ asset('assets/styles/admin-add-new.css') }}">
{% endblock %}
{% block admin_main_content %}
<div class="content-block administrator-controller" ng-controller="AdministratorController">
<div class="content-title-bar">
<div class="pull-left">
<h2>{{ page_title }}</h2>
</div>
</div>
<div class="content-block" ng-controller="AdminController">
{{ form_start(form, {"attr": { "action":"{{ path('domain_admin_add_new_administrator_page') }}", 'enctype': 'multipart/form-data', "method":"POST", "novalidate":"novalidate", "autocomplete":"off", "class":"form-horizontal add-user", "ng-submit":"disableAddButton()" }}) }}
<div class="base-box info-block">
<div class="control-group">
<div class="controls">
{{ form_widget(form.enabled) }}
{{ form_label(form.enabled, 'Active') }}
</div>
</div>
{% if app.user.isGrantedSuperAdmin() %}
<div class="control-group">
<div class="controls">
{% set companyAdminValue = form.isCompanyAdmin.vars.checked ? 'true' : 'false' %}
{{ form_widget(form.isCompanyAdmin, { 'attr':{ 'ng-model':'adminForm.isCompanyAdmin', 'ng-init': 'adminForm.isCompanyAdmin=' ~ companyAdminValue } }) }}
{{ form_label(form.isCompanyAdmin, 'Company Admin') }}
{% set superAdminValue = form.isSuperAdmin.vars.checked ? 'true' : 'false' %}
{{ form_widget(form.isSuperAdmin, { 'attr':{ 'ng-model':'adminForm.isSuperAdmin', 'ng-init': 'adminForm.isSuperAdmin=' ~ superAdminValue } }) }}
{{ form_label(form.isSuperAdmin, 'Super Admin') }}
</div>
</div>
{% endif %}
<div class="control-group" ng-init="initMultiSelect(true)">
{{ form_custom_labels.widget(form.programs) }}
<div class="controls">
{{ form_widget(form.programs) }}
{{ form_custom_errors.widget(form.programs) }}
</div>
</div>
<div class="control-group">
{{ form_custom_labels.widget(form.firstName) }}
<div class="controls">
{{ form_widget(form.firstName) }}
{{ form_custom_errors.widget(form.firstName) }}
</div>
</div>
<div class="control-group">
{{ form_custom_labels.widget(form.lastName) }}
<div class="controls">
{{ form_widget(form.lastName) }}
{{ form_custom_errors.widget(form.lastName) }}
</div>
</div>
<div class="control-group">
{{ form_custom_labels.widget(form.email) }}
<div class="controls">
{{ form_widget(form.email) }}
{{ form_custom_errors.widget(form.email) }}
</div>
</div>
<div class="control-group">
{{ form_custom_labels.widget(form.timezone) }}
<div class="controls">
{{ form_widget(form.timezone) }}
{{ form_custom_errors.widget(form.timezone) }}
</div>
</div>
</div>
<div class="text-right">
<button id="add-admin-submit" type="submit" class="btn btn-do" ng-disabled="isDisabled">Add new administrator</button>
Cancel
</div>
{{ form_rest(form) }}
{{ form_end(form) }}
</div>
</div>
{% endblock %}
If I leave all fields blank and click "Add New Administrator" it doesn't flag them as blank, instead passing them onto Doctrine. The expected behavior is that it flags them at the UI and doesn't attempt to write them to the database.
I'm sure I've created multiple crimes against Symfony as I've ramped up the learning curve, so go easy. Right now I'm just trying to address this narrow issue; refactoring to more elegantly fit Symfony 4 will have to wait for another day.
Thanks!

Looks like you want to validate the User class against the data from your request.
Have you set data_class option in your form type class?
It's required if you want to use validation rules from another class (as you marked your properties with some #Assert* annotations).
https://symfony.com/doc/current/forms.html#creating-form-classes
Another way to do validation is to choose validation rules right in your FormType.

Related

What is the better solution to get the Entity I want in a biderectional OneToMany relation?

I have one entity Article and an other entity Image with a bidrectional relation OneToMany and ManyToOne :
class Article
{
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Image", mappedBy="article")
*/
private $images;
}
class Image
{
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Article", inversedBy="images")
* #ORM\JoinColumn(nullable=true)
*/
private $article;
}
In my controller I use #paramconverter to get the article I want :
/**
* #Route("/blog/{slug}", name="article")
* #ParamConverter("article", class="AppBundle:Article")
*/
public function articleAction(Article $article)
{
return $this->render('default/article.html.twig', array(
'article' => $article,
));
}
Now my problem is that I want to identify the ONLY image with the attributes "main = true" in all the "article.images" I have.
What is the best solution?
In my wiew I can do somehting like this but it's not the best I think :
{% for image in article.images %}
{% if image.main %}
<img src="{{ asset( image.src ) }}" alt="{{ image.alt }}" title="{{ image.title }}">
{% endif %}
{% endfor %}
I'd like to use something like :
{{ article.mainImg }}
How can I achieve this please? And is this the best solution?
Doctrine provides a collection filter mechanism you could use to get the "main image":
public function articleAction(Article $article)
{
$criteria = Criteria::create()
->where(Criteria::expr()->eq("main", true))
->setMaxResults(1);
$mainImg = $article->getImages()->matching($criteria)->first();
return $this->render('default/article.html.twig', array(
'article' => $article,
'mainImg' => $mainImg
));
}
More information on filtering doctrine collections: Filtering collections
I did not test the code myself, but it should convey the idea of how it can be done.

Details list in symfony2, from two bundles

I have little in the development of Symfony and I have a question: To show a list with details of two Bundles.
My project have a Owner, but can have 1 o more consultants. The Entity has been related but when the results are listed only can view the user id. How can see the user name?
I will attach a image with my entities.
Thanks,
View
Entities
I need show the name of the differents consultants in a project. These are users too. I have already related the owner of the project, but this consultant is a field in my entity.
Attachment images to reference, and the php and twig files content. See the image: Database relation, and view.
UserProjectController
public function showAction(UserProject $userProject, User $user) {
$id = $userProject->getId();
$em = $this->getDoctrine()->getManager();
$project = $em->getRepository('ProjectBundle:Project')->find($id);
$userProjects = $em->getRepository('ProjectBundle:UserProject')->findby(array('idproject' => $id, 'status' => '1'));
if (empty($userProjects)) {
return $this->redirectToRoute('userproject_new');
}
$users = $em->getRepository('UserBundle:User')->findby(array('id' => 1));
return $this->render('ProjectBundle:Userproject:show.html.twig', array(
'userProjects' => $userProjects, 'project' => $project, 'User' => $user,
));
}
Entity User
/**
* #ORM\OneToMany(targetEntity="ProjectBundle\Entity\UserProject", mappedBy="user")
*/
protected $uproject;
Entity UserProject
/**
* #ORM\ManyToOne(targetEntity="ProjectBundle\Entity\Project", inversedBy="userproject")
* #ORM\JoinColumn(name="project_id", referencedColumnName="id")
* #Assert\NotBlank()
*/
protected $idproject;
/**
* #ORM\ManyToOne(targetEntity="UserBundle\Entity\User", inversedBy="uproject")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
* #Assert\NotBlank()
*/
protected $user;
Entity Project
/**
* #ORM\OneToMany(targetEntity="ProjectBundle\Entity\UserProject", mappedBy="idproject")
*/
protected $userproject;
View show.html.twig
{% for userProject in userProjects %}
<br>
<dl>
<dt><span class="text-primary">{{'User_id'|trans}}</span></dt>
<dd>
{{ userProject.user }}
</dd>
<br>
<dt><span class="text-primary">{{'Consultor_id'|trans}}</span></dt>
<dd>
{{ userProject.consultorId }}
</dd>
<br>
<dt><span class="text-primary">{{'Status'|trans}}</span></dt>
<dd>
{% if userProject.status == 1 %}
<span class="text-success">{% trans %}Enabled{% endtrans %}</span>
{% elseif userProject.status == 0 %}
<span class="text-danger">{% trans %}Disabled{% endtrans %}</span>
{% endif %}
</dd>
<br>
</dl>
{% endfor %}

Add action in Symfony2

In the show page i whant add order quantity in my session.
This is my action
/**
* #Route("/add/{id}", name="add_action")
* #param Request $request
* #param $id
* #return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function addAction(Request $request, $id)
{
$session = $request->getSession();
$basket = $session->get('basket');
$key = array_search($id, $basket);
if(!$key)
{
$basket[$key] = $request->query->get('item');
$session->set('basket', $basket[$key]);
}
return $this->redirectToRoute('basket_action');
}
In this action i have error Warning: array_search() expects parameter 2 to be array, null given. What can be done to make it work?
P.S.
part of my template
<form method="get" action="{{ path('add_action', {'id' : product.id}) }}">
<select class="form-control" name="item">
{% for i in 1..10 %}
<option value="{{ i }}">{{ i }}</option>
{% endfor %}
</select>
<button class="btn btn-success" >Add to basket</button>
</form>
change
$basket = $session->get('basket');
to
$basket = $session->get('basket', array());
Looks like you are not storing basket as an array on session correctly, I would need to see your code where stores it. You can add a check if is an array before using array_search to prevent this error

Passing multiple arguments through twig path

I have this twig code:
<div style="padding-left: 5em" class="comment">
<p>{{ comment.author.name }} - {{ comment.created|date('j. n. Y H:i') }}</p>
<p>{{ comment.text }}</p>
<p>Odpovědět na komentář</p>
{% for child in comment.children %}
{% include 'BlogApplicationBundle:Post:_comment.html.twig' with {'comment' : child}%}
{% endfor %}
</div>
and this is function that processes the output from link in twig code:
/**
* #Route("/post/{id}/newcommentresponse", name="comment_response_new")
* #Template("BlogApplicationBundle:Post:form.html.twig")
*/
public function commentResponceAction($id,$idc)
{
$comment = new Comment();
$form = $this->createForm(new CommentType(), $comment);
return array(
'form' => $form->createView()
);
}
when i try to run code i get this error :
Controller "Cvut\Fit\BiWt1\Blog\ApplicationBundle\Controller\CommentController::commentResponceAction()"
requires that you provide a value for the "$idc" argument (because
there is no default value or because there is a non optional argument
after this one).
So it seems that second argument passsed through link is ignored and i have no idea what am i doing wrong.
You are missing the $idc definition in your #Route annotation. It should look something like this:
#Route("/post/{id}/newcommentresponse/{idc}", name="comment_response_new")
or this:
#Route("/post/{id}/{idc}/newcommentresponse", name="comment_response_new")
You can also leave it out of the route and function declaration and grab it directly from the Controller:
/**
* #Route("/post/{id}/newcommentresponse", name="comment_response_new")
* #Template("BlogApplicationBundle:Post:form.html.twig")
*/
public function commentResponceAction($id)
{
$idc = $request->query->get('idc');

echoing variable gets "Resource id"

Using symfony2 I load some entities and then try to iterate over them in a twig template.
However, instead of the variable content I am getting the following:
Resource id #23
My twig template looks like this:
<ol>
{% for post in posts %}
<li>
<div>
{{ post.content }}
</div>
</li>
{% endfor %}
</ol>
My controller code is:
$repository = $this->getDoctrine()
->getRepository('AppPostBundle:Post');
$reviews = $repository->findBy(
array('title' => 'my title'))
;
Maybe is too late for this answer (definitely it is LOL) but I recently had the same issue and the problem is that blob datatypes are treated as a buffer, so you have to read the info using buffer functions.
In your entity Post code you may add this method:
public function readContent(){
$content = '';
while(!feof($this->getContent())){
$content.= fread($this->getContent(), 1024);
}
rewind($this->getContent());
return $content;
}
Then in your views you may call the readcontent method instead of content:
<ol>
{% for post in posts %}
<li>
<div>
{{ post.readcontent }}
</div>
</li>
{% endfor %}
</ol>
Is better late than never :)
My entity name is : 'Test'
Blob type field is : 'Details'
in '/src/Vendorname/Bundlename/Entity/entityname.php'
**
* Get details
*
* #return string
*/
public function getDetails()
{
return $this->details;
}
/**
* Reading the blob type of content.
* #return data
*/
public function readDetails(){
$details = '';
while(!feof($this->getDetails())){
$details.= fread($this->getDetails(), 1024);
}
rewind($this->getDetails());
return $details;
}
and in '*.html.twig' file
{% for match in matches %}
{{match.readdetails}}
{%endfor%}

Resources