I'm trying to test the signup method, however im getting this on the logs.
[2018-08-30 00:13:50] request.INFO: Matched route "signup".
{"route":"signup","route_parameters":{"_route":"signup","_controller":"App\Controller\UserController::signup"},"request_uri":"http://localhost/signup","method":"POST"}
[] [2018-08-30 00:13:50] security.INFO: Populated the TokenStorage
with an anonymous Token. [] [] [2018-08-30 00:13:50] doctrine.DEBUG:
"START TRANSACTION" [] [] [2018-08-30 00:13:50] doctrine.DEBUG:
"ROLLBACK" [] [] [2018-08-30 00:13:50] request.CRITICAL: Uncaught PHP
Exception Doctrine\DBAL\Exception\TableNotFoundException: "An
exception occurred while executing 'INSERT INTO user (email, username,
is_active, password, roles) VALUES (?, ?, ?, ?, ?)': SQLSTATE[HY000]:
General error: 1 no such table: user" at
/Applications/MAMP/htdocs/my_project/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractSQLiteDriver.php
line 63 {"exception":"[object]
(Doctrine\DBAL\Exception\TableNotFoundException(code: 0): An
exception occurred while executing 'INSERT INTO user (email, username,
is_active, password, roles) VALUES (?, ?, ?, ?,
?)':\n\nSQLSTATE[HY000]: General error: 1 no such table: user at
/Applications/MAMP/htdocs/my_project/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractSQLiteDriver.php:63,
Doctrine\DBAL\Driver\PDOException(code: HY000): SQLSTATE[HY000]:
General error: 1 no such table: user at
/Applications/MAMP/htdocs/my_project/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php:82,
PDOException(code: HY000): SQLSTATE[HY000]: General error: 1 no such
table: user at
/Applications/MAMP/htdocs/my_project/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php:80)"}
[]
and this as an error
Failed asserting that 500 is identical to 200.
SignUpControllerTest.php
<?php
namespace App\Tests\Controller;
use App\Entity\Product;
use App\Entity\Category;
use App\Entity\User;
use App\Repository\UserRepository;
use App\Repository\CategoryRepository;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\DBAL\Schema\SchemaException;
use Doctrine\ORM\Tools\SchemaTool;
class SignUpControllerTest extends WebTestCase
{
/**
* #var \Doctrine\ORM\EntityManager
*/
private $em;
/**
* {#inheritDoc}
*/
protected function setUp()
{
self::bootKernel();
$this->em = static::$kernel->getContainer()
->get('doctrine')
->getManager();
parent::setUp();
}
public function testUserCreate()
{
$client = static::createClient();
$client->request('POST', '/signup', [], [], [], json_encode([
'user' => [
'username' => 'chuck_norris',
'password' => 'foobar',
'email' => 'chuck#norris.com',
'roles' => 'ROLE_USER'
],
]));
$response = $client->getResponse();
$this->assertSame(Response::HTTP_OK, $response->getStatusCode());
$data = json_decode($response->getContent(), true);
$this->assertArrayHasKey('user', $data);
$this->assertSame('chuck#norris.com', $data['user']['email']);
$this->assertSame('chuck_norris', $data['user']['username']);
}
UserController.php
/**
* #Route("/signup", name="signup")
*/
public function signup(Request $request, UserPasswordEncoderInterface $passwordEncoder )
{
$user = new User();
$entityManager = $this->getDoctrine()->getManager();
$user->setEmail($request->get('email'));
$user->setPlainPassword($request->get('password'));
$user->setUsername($request->get('username'));
$password = $passwordEncoder->encodePassword($user, $user->getPlainPassword());
$user->setPassword($password);
$entityManager->persist($user);
$entityManager->flush();
return $this->redirectToRoute('login');
}
User.php (entity)
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* #ORM\Entity
* #UniqueEntity(fields="email", message="Email already taken")
* #UniqueEntity(fields="username", message="Username already taken")
*/
class User implements UserInterface, \Serializable
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=190, unique=true)
* #Assert\NotBlank()
* #Assert\Email()
*/
private $email;
/**
* #ORM\Column(type="string", length=190, unique=true)
* #Assert\NotBlank()
*/
private $username;
/**
* #ORM\Column(name="is_active", type="boolean")
*/
private $isActive;
/**
* #Assert\NotBlank()
* #Assert\Length(max=190)
*/
private $plainPassword;
/**
* The below length depends on the "algorithm" you use for encoding
* the password, but this works well with bcrypt.
*
* #ORM\Column(type="string", length=64)
*/
private $password;
/**
* #ORM\Column(type="array")
*/
private $roles;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Product", mappedBy="user")
*/
private $products;
public function __construct()
{
$this->roles = array('ROLE_USER');
$this->isActive = true;
$this->products = new ArrayCollection();
}
// other properties and methods
public function getEmail()
{
return $this->email;
}
public function isEnabled()
{
return $this->isActive;
}
public function setEmail($email)
{
$this->email = $email;
}
public function getUsername()
{
return $this->username;
}
public function setUsername($username)
{
$this->username = $username;
}
public function getPlainPassword()
{
return $this->plainPassword;
}
public function setPlainPassword($password)
{
$this->plainPassword = $password;
}
public function getPassword()
{
return $this->password;
}
public function setPassword($password)
{
$this->password = $password;
}
public function getSalt()
{
// The bcrypt and argon2i algorithms don't require a separate salt.
// You *may* need a real salt if you choose a different encoder.
return null;
}
public function getRoles()
{
return $this->roles;
}
public function eraseCredentials()
{
}
/** #see \Serializable::serialize() */
public function serialize()
{
return serialize(array(
$this->id,
$this->username,
$this->password,
// see section on salt below
// $this->salt,
));
}
/** #see \Serializable::unserialize() */
public function unserialize($serialized)
{
list (
$this->id,
$this->username,
$this->password,
// see section on salt below
// $this->salt
) = unserialize($serialized, array('allowed_classes' => false));
}
/**
* #return Collection|Product[]
*/
public function getProducts(): Collection
{
return $this->products;
}
public function addProduct(Product $product): self
{
if (!$this->products->contains($product)) {
$this->products[] = $product;
$product->setUser($this);
}
return $this;
}
public function removeProduct(Product $product): self
{
if ($this->products->contains($product)) {
$this->products->removeElement($product);
// set the owning side to null (unless already changed)
if ($product->getUser() === $this) {
$product->setUser(null);
}
}
return $this;
}
}
Related
I am trying to make my own User entity which extends the SuluUser entity (so I can add some properties). After following the instructions from the Sulu documentation: Extend Entities I then created my own symfony authentication for the (front) website however, when I try to authenticate I get the following error.
Edit: added the imports and annotations for the user entity
An exception occurred while executing 'SELECT t1.username AS username_2, t1.password AS password_3, t1.locale AS locale_4, t1.salt AS salt_5, t1.locked AS locked_6, t1.enabled AS enabled_7, t1.lastLogin AS lastLogin_8, t1.confirmationKey AS confirmationKey_9, t1.passwordResetToken AS passwordResetToken_10, t1.passwordResetTokenExpiresAt AS passwordResetTokenExpiresAt_11, t1.passwordResetTokenEmailsSent AS passwordResetTokenEmailsSent_12, t1.privateKey AS privateKey_13, t1.apiKey AS apiKey_14, t1.email AS email_15, t1.id AS id_16, t1.firstname AS firstname_17, t1.lastname AS lastname_18, t1.phonenumber AS phonenumber_19, t1.gender AS gender_20, t1.password_changed_date AS password_changed_date_21, t1.confirmation_token AS confirmation_token_22, t1.idContacts AS idContacts_23 FROM user t1 WHERE t0.email = ? LIMIT 1' with params ["test#test.com"]:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 't0.email' in 'where clause'
I'm not sure why it's using t0.email when the rest of the query uses t1 as an alias, but this breaks the login from the (front) website. Admin can login just fine for the sulu backend. I believe it has something to do with the inheritance with the SuluUser that my User entity extends. Any help would be very much appreciated. I have read about doctrine inheritance here but I don't think that applies to me as I cannot (should not) change the class in Sulu\Bundle\SecurityBundle\Entity\User. I have configured the following
App\Entity\User
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Sulu\Bundle\SecurityBundle\Entity\User as SuluUser;
use Symfony\Component\Security\Core\Validator\Constraints\UserPassword;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
* #UniqueEntity(fields={"email"})
*/
class User extends SuluUser
{
/**
* #Groups({"get", "post", "put", "get-comment-with-author", "get-blog-post-with-author"})
* #ORM\Column(type="string", length=25)
* #Assert\NotBlank(groups={"post"})
* #Assert\Length(min=4, max="100")
*/
private $Firstname;
/**
* #Groups({"get", "post", "put"})
* #ORM\Column(type="string", length=25)
* #Assert\NotBlank(groups={"post"})
* #Assert\Length(min=4, max="100")
*/
private $Lastname;
/**
* #ORM\Column(type="string", length=10, nullable=true)
*
* #Groups({"get", "post", "put"})
*/
private $phonenumber;
/**
* #ORM\Column(type="string", nullable=true)
* #Groups({"get", "post", "put"})
* #Assert\Collection()
*/
private $gender;
/**
* #Groups({"post"})
* #Assert\NotBlank(groups={"post"})
* #Assert\Expression(
* "this.getPassword() === this.getRetypedPassword()",
* message="Passwords do not match"
* )
*/
private $retypedPassword;
/**
* #Assert\Length(min=10, max="100")
* #Assert\NotBlank(groups={"put-reset-password"})
* #Groups({"put-reset-password"})
* #Assert\Regex(
* pattern="/(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9]).{7,}/",
* message="Your password needs to be at least 10 characters long and contain the folloiwing"
* )
*/
private $newPassword;
/**
* #Groups({"put-reset-password"})
* #Assert\NotBlank(groups={"put-reset-password"})
* #Assert\Expression(
* "this.getNewPassword() === this.getNewRetypedPassword()",
* message="Passwords does not match",
* groups={"put-reset-password"}
* )
*/
private $newRetypedPassword;
/**
* #Groups({"put-reset-password"})
* #Assert\NotBlank(groups={"put-reset-password"})
* #UserPassword(groups={"put-reset-password"})
*/
private $oldPassword;
/**
* #ORM\Column(type="integer", nullable=true)
*/
private $passwordChangedDate;
/**
* #ORM\Column(type="string", length=40, nullable=true)
*/
private $confirmationToken;
public function __construct()
{
$this->confirmationToken = null;
}
public function getFirstname(): ?string
{
return $this->Firstname;
}
public function setFirstname( $Firstname): self
{
$this->Firstname = $Firstname;
return $this;
}
public function getLastname(): ?string
{
return $this->Lastname;
}
public function setLastname( $Lastname): self
{
$this->Lastname = $Lastname;
return $this;
}
public function getPhonenumber(): ?string
{
return $this->phonenumber;
}
public function setPhonenumber(?string $phonenumber): self
{
$this->phonenumber = $phonenumber;
return $this;
}
/**
* #return mixed
*/
public function getGender()
{
return $this->gender;
}
/**
* #param mixed $gender
*/
public function setGender($gender): void
{
$this->gender = $gender;
}
/**
* #return mixed
*/
public function getRetypedPassword()
{
return $this->retypedPassword;
}
/**
* #param mixed $retypedPassword
*/
public function setRetypedPassword($retypedPassword): void
{
$this->retypedPassword = $retypedPassword;
}
public function getNewPassword(): ?string
{
return $this->newPassword;
}
public function setNewPassword($newPassword): void
{
$this->newPassword = $newPassword;
}
public function getNewRetypedPassword(): ?string
{
return $this->newRetypedPassword;
}
public function setNewRetypedPassword($newRetypedPassword): void
{
$this->newRetypedPassword = $newRetypedPassword;
}
public function getOldPassword(): ?string
{
return $this->oldPassword;
}
public function setOldPassword($oldPassword): void
{
$this->oldPassword = $oldPassword;
}
public function getPasswordChangedDate()
{
return $this->passwordChangedDate;
}
public function setPasswordChangedDate($passwordChangedDate): void
{
$this->passwordChangedDate = $passwordChangedDate;
}
public function getConfirmationToken()
{
return $this->confirmationToken;
}
public function setConfirmationToken($confirmationToken): void
{
$this->confirmationToken = $confirmationToken;
}
public function __toString(): string
{
return $this->Firstname . ' ' . $this->Lastname;
}
}
App\config\packges\security_website.yaml
security:
encoders:
App\Entity\User:
algorithm: auto
providers:
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: false
lazy: true
provider: app_user_provider
guard:
authenticators:
- App\Security\AppAuthenticator
logout:
path: app_logout
# where to redirect after logout
target: home
App\Security\AppAuthenticator
class AppAuthenticator extends AbstractFormLoginAuthenticator implements PasswordAuthenticatedInterface
{
use TargetPathTrait;
public const LOGIN_ROUTE = 'app.login';
private $entityManager;
private $urlGenerator;
private $csrfTokenManager;
private $passwordEncoder;
public function __construct(EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder)
{
$this->entityManager = $entityManager;
$this->urlGenerator = $urlGenerator;
$this->csrfTokenManager = $csrfTokenManager;
$this->passwordEncoder = $passwordEncoder;
}
public function supports(Request $request)
{
return self::LOGIN_ROUTE === $request->attributes->get('_route')
&& $request->isMethod('POST');
}
public function getCredentials(Request $request)
{
$credentials = [
'email' => $request->request->get('email'),
'password' => $request->request->get('password'),
'csrf_token' => $request->request->get('_csrf_token'),
];
$request->getSession()->set(
Security::LAST_USERNAME,
$credentials['email']
);
return $credentials;
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
$token = new CsrfToken('authenticate', $credentials['csrf_token']);
if (!$this->csrfTokenManager->isTokenValid($token)) {
throw new InvalidCsrfTokenException();
}
$user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $credentials['email']]);
if (!$user) {
// fail authentication with a custom error
throw new CustomUserMessageAuthenticationException('Email could not be found.');
}
return $user;
}
public function checkCredentials($credentials, UserInterface $user)
{
return $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
}
/**
* Used to upgrade (rehash) the user's password automatically over time.
*/
public function getPassword($credentials): ?string
{
return $credentials['password'];
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
return new RedirectResponse($targetPath);
}
$role = $token->getUser()->getRoles();
// For example : return new RedirectResponse($this->urlGenerator->generate('some_route'));
// throw new \Exception('TODO: provide a valid redirect inside '.__FILE__);
// return new RedirectResponse('admin');
return new RedirectResponse('/');
}
protected function getLoginUrl()
{
return $this->urlGenerator->generate(self::LOGIN_ROUTE);
}
}
Its very important when you override the Sulu entities that you set it on the same table name else the override of the entity does not work correctly so as in the documentation set the #ORM\Table with "se_users" and #ORM\Entity to it.
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Sulu\Bundle\SecurityBundle\Entity\User as SuluUser;
/**
* Following annotations are required and should not be changed:
*
* #ORM\Table(name="se_users")
* #ORM\Entity
*/
class User extends SuluUser
{
}
I have an issue with an Entity.
The goal is to display a Form which the user can use to change his personal information like the Email or password. I created a form for that, but when I created the /Edit Route I get the following error:
"App\Entity\Users object not found by the #ParamConverter annotation."
Here's my Controller:
<?php
namespace App\Controller;
use App\Entity\Users;
use App\Form\EditProfileType;
use App\Form\UsersType;
use App\Repository\UsersRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* #Route("/users")
*/
class UsersController extends AbstractController
{
/**
* #Route("/", name="users_index", methods={"GET"})
*/
public function index(): Response
{
return $this->render('users/index.html.twig');
}
/**
* #Route("/{id}", name="users_show", methods={"GET"})
*/
public function show(Users $user): Response
{
return $this->render('users/show.html.twig', [
'user' => $user,
]);
}
/**
* #Route("/edit", name="users_edit")
*/
public function editProfile(Request $request): Response
{
$user = $this->getUser();
$form = $this->createForm(EditProfileType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($user);
$em->flush();
$this->addflash('message', 'Profile mis à jour');
return $this->redirectToRoute('users');
}
return $this->render('users/editprofile.html.twig', [
'form' => $form->createView(),
]);
}
Here's the form:
<?php
namespace App\Form;
use App\Entity\Users;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class EditProfileType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email')
->add('pseudo', TextType::class)
->add('Valider', SubmitType::class)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Users::class,
]);
}
}
Here's the Entity Users:
<?php
namespace App\Entity;
use App\Repository\UsersRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* #ORM\Entity(repositoryClass=UsersRepository::class)
* #UniqueEntity(fields={"email"}, message="There is already an account with this email")
*/
class Users implements UserInterface
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=180, unique=true)
*/
private $email;
/**
* #ORM\Column(type="json")
*/
private $roles = [];
/**
* #var string The hashed password
* #ORM\Column(type="string")
*/
private $password;
/**
* #ORM\OneToMany(targetEntity=Commentaires::class, mappedBy="auteur", orphanRemoval=true)
*/
private $commentaires;
/**
* #ORM\OneToMany(targetEntity=Notes::class, mappedBy="user", orphanRemoval=true)
*/
private $note;
/**
* #ORM\Column(type="string", length=100)
*/
private $pseudo;
public function __construct()
{
$this->commentaires = new ArrayCollection();
$this->note = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
/**
* A visual identifier that represents this user.
*
* #see UserInterface
*/
public function getUsername(): string
{
return (string) $this->email;
}
/**
* #see UserInterface
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
}
/**
* #see UserInterface
*/
public function getPassword(): string
{
return (string) $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
/**
* #see UserInterface
*/
public function getSalt()
{
// not needed when using the "bcrypt" algorithm in security.yaml
}
/**
* #see UserInterface
*/
public function eraseCredentials()
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
/**
* #return Collection|Commentaires[]
*/
public function getCommentaires(): Collection
{
return $this->commentaires;
}
public function addCommentaire(Commentaires $commentaire): self
{
if (!$this->commentaires->contains($commentaire)) {
$this->commentaires[] = $commentaire;
$commentaire->setAuteur($this);
}
return $this;
}
public function removeCommentaire(Commentaires $commentaire): self
{
if ($this->commentaires->contains($commentaire)) {
$this->commentaires->removeElement($commentaire);
// set the owning side to null (unless already changed)
if ($commentaire->getAuteur() === $this) {
$commentaire->setAuteur(null);
}
}
return $this;
}
/**
* #return Collection|Notes[]
*/
public function getNote(): Collection
{
return $this->note;
}
public function addNote(Notes $note): self
{
if (!$this->note->contains($note)) {
$this->note[] = $note;
$note->setUser($this);
}
return $this;
}
public function removeNote(Notes $note): self
{
if ($this->note->contains($note)) {
$this->note->removeElement($note);
// set the owning side to null (unless already changed)
if ($note->getUser() === $this) {
$note->setUser(null);
}
}
return $this;
}
public function getPseudo(): ?string
{
return $this->pseudo;
}
public function setPseudo(string $pseudo): self
{
$this->pseudo = $pseudo;
return $this;
}
}
Here's the View Editprofile.html.twig:
{% extends 'basefront.html.twig' %}
{% block title %} Profile de {{ app.user.pseudo }} {% endblock %}
{% block body %}
<h1 class="h1 text-center">Modification du profile de {{ app.user.pseudo }}</h1>
{{ form(form) }}
{% endblock %}
order matters. especially when routing without requirements. you have the following routes (update: added prefix, thanks #Cerad!):
/users/ users_index
/users/{id} users_show
/users/edit users_edit
now, when you request the uri /users/edit the UrlMatcher will iterate over your routes to see if and which matches, stopping on the first match.
/users/ obviously doesn't match
/users/{id} does match, since {id} is allowed to be any alpha-numeric string (+ few extra chars), and edit matches that, leading to the users_show route, on which the User object cannot be determined by the id "edit`. And thus, the error message appears. One way to fix this would be to add requirements to the route matching - assuming the id is numerical only:
/**
* #Route("/{id}", ..., requirements={"id":"\d+"})
*/
I also agree with #Cerad that Users is a very bad name for an entity. They're usually in singular form.
So I'm trying to create a method that inserts Recipies to MySQL database.
The schema of my project that a recipe (Recette) has many Ingredients.
The Recette has a Title(titre), Subtitle(Soustitre) and Ingredients that must be inserted in "POST" Request I'll show you my code, my "POST" request in Postman and the Result I get.
Also, My same code has an update function that doesn't work as well and it's always the problem of the ingredients.
This is my Controller:
namespace App\Controller;
use App\Entity\Ingredients;
use App\Entity\Recettes;
use Doctrine\ORM\EntityManagerInterface;
use http\Env\Response;
use Psr\Container\ContainerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use App\Repository\RecettesRepository;
use Symfony\Component\Serializer\Exception\NotEncodableValueException;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class ApiRecetteController extends AbstractController
{
/**
* #Route("/api/recette", name="api_recette_index", methods={"GET"})
*/
public function index(RecettesRepository $recettesRepository)
{
return $this->json($recettesRepository->findAll(), 200, [], ['groups' => 'recette:read']);
}
/**
* #Route("/api/recette", name="api_recette_addRecettes", methods={"POST"})
*/
public function addRecettes(Request $request, SerializerInterface $serializer, EntityManagerInterface
$entityManager)
{
$jsonRecu = $request->getContent();
try {
$recette = $serializer->deserialize($jsonRecu, Recettes::class,"json");
$entityManager->persist($recette);
$entityManager->flush();
return $this->json($recette,201);
}
catch (NotEncodableValueException $e){
return $this->json([
'status'=>400,
'message' =>$e->getMessage()
],400);
}
}
/**
* #Route("/api/recette/Update/{id}", name="api_recette_updateRecettes", methods={"PUT"})
*/
public function UpdateRecettes($id, RecettesRepository $recettesRepository, Request $request,
EntityManagerInterface $entityManager)
{
$entityManger = $this->getDoctrine()->getManager();
$recettes = $entityManger->getRepository(Recettes::class)->find($id);
$data = json_decode($request->getContent(), true);
if (!$recettes) {
throw $this->createNotFoundException(
'Aucune recette trouvé pour id' . $id
);
}
empty($data['Titre']) ? true : $recettes->setTitre($data['Titre']);
empty($data['Soustitre']) ? true : $recettes->setSoustitre($data['Soustitre']);
$entityManager->persist($recettes);
$entityManger->flush();
return $this->json($recettes,204, [], ['groups' => 'recette:read']);
}
/**
* #Route("/api/recette/{id}", name="api_recette_DeleteRecettes", methods={"DELETE"})
*/
public function DeleteRecettes($id, EntityManagerInterface $entityManager, Request $request)
{
$recettes = $entityManager->getRepository(Recettes::class)->find($id);
$entityManager->remove($recettes);
$entityManager->flush();
return $this->json($recettes,202, [], ['groups' => 'recette:read']);
}
}
and My Two Entities
<?PHP
namespace App\Entity;
use App\Repository\IngredientsRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
/**
* #ORM\Entity(repositoryClass="App\Repository\IngredientsRepository",
repositoryClass=IngredientsRepository::class)
*/
class Ingredients
{
/**
* #ORM\Id()
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
* #Groups("recette:read")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
* #Groups("recette:read")
*/
private $name;
/**
* #ORM\ManyToOne(targetEntity=Recettes::class, inversedBy="ingredients")
* #ORM\JoinColumn(nullable=false)
*/
private $recettes;
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getRecettes(): ?Recettes
{
return $this->recettes;
}
public function setRecettes(?Recettes $recettes): self
{
$this->recettes = $recettes;
return $this;
}
public function __toString(): ?string
{
return $this->getName();
}
}
And Recettes
<?PHP
namespace App\Entity;
use App\Repository\RecettesRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
/**
* #ORM\Entity(repositoryClass=RecettesRepository::class)
*/
class Recettes
{
/**
* #ORM\Id()
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
* #Groups("recette:read")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
* #Groups("recette:read")
*/
private $Titre;
/**
* #ORM\Column(type="string", length=255, nullable=true)
* #Groups("recette:read")
*/
private $Soustitre;
/**
* #ORM\OneToMany(targetEntity=Ingredients::class, mappedBy="recettes")
*/
private $ingredients;
public function __construct()
{
$this->ingredients = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getTitre(): ?string
{
return $this->Titre;
}
public function setTitre(string $Titre): self
{
$this->Titre = $Titre;
return $this;
}
public function getSoustitre(): ?string
{
return $this->Soustitre;
}
public function setSoustitre(?string $Soustitre): self
{
$this->Soustitre = $Soustitre;
return $this;
}
/**
* #return Collection|Ingredients[]
*/
public function getingredients(): Collection
{
return $this->ingredients;
}
public function addingredients(Ingredients $ingredients): self
{
if (!$this->ingredients->contains($ingredients)) {
$this->ingredients[] = $ingredients;
$ingredients->setRecettes($this);
}
return $this;
}
public function removeingredients(Ingredients $ingredients): self
{
if ($this->ingredients->contains($ingredients)) {
$this->ingredients->removeElement($ingredients);
// set the owning side to null (unless already changed)
if ($ingredients->getRecettes() === $this) {
$ingredients->setRecettes(null);
}
}
return $this;
}
}
My Request json
JSON Row Request
My Response with 201 ok status but empty Ingredients inserted
Response From Server
To insert related entities you need to set cascade={"persist"} to your relation, ie:
/**
* #ORM\OneToMany(targetEntity=Ingredients::class, mappedBy="recettes", cascade={"persist"})
*/
private $ingredients;
Else you will need to persist the Recettes first, get the id and set it on your Ingredientss before persisting them.
I am trying to migrate a some parts of a project made with Typescript + Express + Firebase to Symfony 5 and MySQL. But I don't get why the ValidatorInterface isn't working.
When I submit the form with unexpected characters (i.e.: a password with 3 charactesrs) it creates the user despites the validation constraints assigned in the User entity. The only constraint validation that actually works despites it does not show any form errors, is the UniqueEntity.
This is how my controller looks like:
/**
* GET: Display form
* POST: Creates a user
*
* #Route("/users/crear", name="create_user", methods={"GET", "POST"})
*/
public function create_user(Request $request, EntityManagerInterface $entityManager, ValidatorInterface $validator)
{
$user = new User();
// Form
if ($request->isMethod('GET')) {
$form = $this->createForm(CrearUserType::class, $user);
return $this->render('user/create.html.twig', [
'form' => $form->createView(),
'errors' => []
]);
}
// Form submit
$data = $request->request->get('create_user');
$errors = [];
$user->setName($data['name']);
$user->setEmail($data['email']);
$user->setPassword($data['password']);
$user->setCreatedAt(new \DateTime());
$user->setUpdatedAt(new \DateTime());
$form = $this->createForm(CrearUserType::class, $user);
// Entity validation
$errors = $validator->validate($user);
if (count($errors) > 0) { // This is always 0
$user->setPassword('');
return $this->render('user/create.html.twig', [
'form' => $form->createView(),
'user' => $user,
'errors' => $errors
]);
}
try {
$user->setPassword(password_hash($user->getPassword(), PASSWORD_BCRYPT));
$entityManager->persist($user);
$entityManager->flush();
$this->addFlash(
'success',
'User '.$user->getName().' has been saved.'
);
return $this->redirectToRoute('users');
} catch(\Exception $exception) {
$user->setPassword('');
$this->addFlash(
'error',
'Server error: ' . $exception->getMessage()
);
return $this->render('user/create.html.twig', [
'form' => $form->createView(),
'user' => $user,
'errors' => $errors
]);
}
}
And this is my entity:
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
* #UniqueEntity("email")
*/
class User
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
* #Assert\NotBlank(message="Name is mandatory")
*/
private $name;
/**
* #ORM\Column(type="string", length=255, unique=true)
* #Assert\NotBlank(message="Email is mandatory")
* #Assert\Email(
* message="Invalid email address"
* )
*/
private $email;
/**
* #ORM\Column(type="string", length=60)
* #Assert\NotBlank(message="Password is mandatory")
* #Assert\GreaterThanOrEqual(
* value=6,
* message="The password has to be at least 6 chars long"
* )
*/
private $password;
/**
* #ORM\Column(type="date")
*/
private $createdAt;
/**
* #ORM\Column(type="date")
*/
private $updatedAt;
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
public function getPassword(): ?string
{
return $this->password;
}
public function setPassword(?string $password): self
{
$this->password = $password;
return $this;
}
public function getCreatedAt(): ?\DateTimeInterface
{
return $this->createdAt;
}
public function setCreatedAt(\DateTimeInterface $createdAt): self
{
$this->createdAt = $createdAt;
return $this;
}
public function getUpdatedAt(): ?\DateTimeInterface
{
return $this->updatedAt;
}
public function setUpdatedAt(\DateTimeInterface $updatedAt): self
{
$this->updatedAt = $updatedAt;
return $this;
}
}
So, any clues on what is wrong here?
I'm not sure of my response, because I never handle request with a ValidatorInterface. I use the handlerequest method provided by controller.
You created the form
$form = $this->createForm(CrearUserType::class, $user);
But you forgot to handle request
$form->handleRequest($request);
So your form does not validate data forwarded by request. Try to add this line just after the $form creation. Then, you can test that the user entity is valid with
if ($form->isValid()) {
//no errors
}
//error exists
I've added an avatar image to my User class. When I wanted to render my edit form, I got this error
Serialization of 'Symfony\Component\HttpFoundation\File\File' is not allowed
I tried to solve the problem by implementing \Serializable in my User class according to Symfony Official Documentation. but didint work.
Here is my UserEntity, Artsite.php:
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use
Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;
use Serializable;
/**
*
#ORM\Entity(repositoryClass="App\Repository\ArtisteRepository")
* #UniqueEntity(fields={"username"}, message="There is already
an account with this username")
*/
class Artiste implements UserInterface, Serializable
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $NomPrenom;
/**
* #ORM\Column(type="string", length=255)
*/
private $adresse;
/**
* #ORM\Column(type="integer")
*/
private $numero1;
/**
* #ORM\Column(type="integer", nullable=true)
*/
private $numero2;
/**
* #ORM\Column(type="string", length=255)
*/
private $username;
/**
* #ORM\Column(type="string", length=255)
*/
private $password;
/**
* #ORM\Column(type="string", nullable=true)
* #Assert\File(mimeTypes={ "image/*" },mimeTypesMessage="C'est n'est pas une image")
*/
private $image;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Produit", mappedBy="artiste_id")
*/
private $produits;
public function __construct()
{
$this->produits = new ArrayCollection();
}
public function getImage()
{
return $this->image;
}
public function setImage($image)
{
$this->image = $image;
return $this;
}
public function getId(): ?int
{
return $this->id;
}
public function getNomPrenom(): ?string
{
return $this->NomPrenom;
}
public function setNomPrenom(string $NomPrenom): self
{
$this->NomPrenom = $NomPrenom;
return $this;
}
public function getAdresse(): ?string
{
return $this->adresse;
}
public function setAdresse(string $adresse): self
{
$this->adresse = $adresse;
return $this;
}
public function getNumero1(): ?int
{
return $this->numero1;
}
public function setNumero1(int $numero1): self
{
$this->numero1 = $numero1;
return $this;
}
public function getNumero2(): ?int
{
return $this->numero2;
}
public function setNumero2(?int $numero2): self
{
$this->numero2 = $numero2;
return $this;
}
public function getUsername(): ?string
{
return $this->username;
}
public function setUsername(string $username): self
{
$this->username = $username;
return $this;
}
public function getPassword(): ?string
{
return $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
/**
* #return Collection|Produit[]
*/
public function getProduits(): Collection
{
return $this->produits;
}
public function addProduit(Produit $produit): self
{
if (!$this->produits->contains($produit)) {
$this->produits[] = $produit;
$produit->setArtisteId($this);
}
return $this;
}
public function removeProduit(Produit $produit): self
{
if ($this->produits->contains($produit)) {
$this->produits->removeElement($produit);
// set the owning side to null (unless already changed)
if ($produit->getArtisteId() === $this) {
$produit->setArtisteId(null);
}
}
return $this;
}
public function eraseCredentials()
{
}
public function getSalt()
{
}
public function getRoles(): array
{
$roles[] = 'ROLE_Etudiant';
return array_unique($roles);
}
/** #see \Serializable::serialize() */
public function serialize()
{
return serialize(array(
$this->id,
$this->image,
$this->username,
$this->password,
));
}
/** #see \Serializable::unserialize() */
public function unserialize($serialized)
{
list (
$this->id,
$this->image,
$this->username,
$this->password,
) = unserialize($serialized, array('allowed_classes' => false));
}
}
Here is my UserController.php
<?php
namespace App\Controller;
use App\Entity\Artiste;
use App\Form\ArtisteType;
use App\Repository\ArtisteRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use
Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use
Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\HttpFoundation\File\File;
class ArtisteController extends AbstractController
{
/**
* #Route("/", name="artiste_index", methods={"GET"})
*/
public function index(ArtisteRepository $artisteRepository,Security $security)
{
$user = $security->getUser();
return $this->render('artiste/index.html.twig', [
'client' =>$user
]);
}
/**
* #Route("/login", name="artiste_login", methods={"GET","POST"})
*/
public function login(AuthenticationUtils $authenticationUtils)
{
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('artiste/login.html.twig', [
'last_username' => $lastUsername,
'error' => $error,
]);
}
/**
* #Route("/logout", name="artiste_logout")
*/
public function logout()
{
}
/**
* #Route("/new", name="artiste_new", methods={"GET","POST"})
*/
public function new(Request $request,UserPasswordEncoderInterface $encoder): Response
{
$artiste = new Artiste();
$form = $this->createForm(ArtisteType::class, $artiste);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$hash=$encoder->encodePassword($artiste,$artiste->getPassword());
$artiste->setPassword($hash);
$file = $artiste->getImage();
if($file=="")
$artiste->setImage("default.jpg");
else{
$fileName = $this->generateUniqueFileName().'.'.$file->guessExtension();
// Move the file to the directory where brochures are stored
try {
$file->move(
$this->getParameter('images_directory'),
$fileName
);
} catch (FileException $e) {
// ... handle exception if something happens during file upload
}
$artiste->setImage($fileName);
}
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($artiste);
$entityManager->flush();
return $this->redirectToRoute('artiste_login');
}
return $this->render('artiste/new.html.twig', [
'artiste' => $artiste,
'form' => $form->createView(),
]);
}
/**
* #return string
*/
private function generateUniqueFileName()
{
// md5() reduces the similarity of the file names generated by
// uniqid(), which is based on timestamps
return md5(uniqid());
}
/**
* #Route("/profile", name="artiste_profile", methods={"GET"})
*/
public function show(Security $security)
{
$user = $security->getUser();
return $this->render('artiste/profile.html.twig', [
'client' => $user,
]);
}
/**
* #Route("/edit", name="artiste_edit", methods={"GET","POST"})
*/
public function edit(Request $request,Security $security,UserPasswordEncoderInterface $encoder)
{
$artiste=$security->getUser();
$artiste->setImage( new File($this->getParameter('images_directory').'/'.$artiste->getImage()));
$form = $this->createForm(ArtisteType::class, $artiste);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$hash=$encoder->encodePassword($artiste,$artiste->getPassword());
$artiste->setPassword($hash);
$file = $artiste->getImage();
$fileName = $this->generateUniqueFileName().'.'.$file->guessExtension();
// Move the file to the directory where brochures are stored
try {
$file->move(
$this->getParameter('images_directory'),
$fileName
);
} catch (FileException $e) {
// ... handle exception if something happens during file upload
}
$artiste->setImage($fileName);
$this->getDoctrine()->getManager()->flush();
return $this->redirectToRoute('artiste_profile', [
'client' => $artiste,
]);
}
return $this->render('artiste/edit.html.twig', [
'client' => $artiste,
'form' => $form->createView(),
]);
}
Here is my UserForm, UserType.php
<?php
namespace App\Form;
use App\Entity\Artiste;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
class ArtisteType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array
$options)
{
$builder
->add('NomPrenom')
->add('adresse')
->add('image', FileType::class, array('required' => false))
->add('numero1')
->add('numero2')
->add('username')
->add('password',PasswordType::class)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Artiste::class,
]);
}
}