Add a custom field to FOSUserBundle command is not working - php

I have been looking and trying to add a custom command to FOSUserBundle but have been impossible :(
I do triple check the structure of my files, CreateUserCommand is in Command folder and UserManipulator is in Util folder.
I also tried to relaunch the server and to change the command but is not working:
The command line
php app/console fos:user:create root test#example.com admin gabriel --super-admin
The error:
[RuntimeException]
Too many arguments.
My Bundle main file:
<?php
namespace INCES\ComedorBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class INCESComedorBundle extends Bundle
{
public function getParent()
{
return 'FOSUserBundle';
}
}
My CreateUserCommand File:
<?php
namespace INCES\ComedorBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use FOS\UserBundle\Model\User;
/**
* #author Matthieu Bontemps <matthieu#knplabs.com>
* #author Thibault Duplessis <thibault.duplessis#gmail.com>
* #author Luis Cordova <cordoval#gmail.com>
*/
class CreateUserCommand extends ContainerAwareCommand
{
/**
* #see Command
*/
protected function configure()
{
$this
->setName('fos:user:create')
->setDescription('Create a user.')
->setDefinition(array(
new InputArgument('username', InputArgument::REQUIRED, 'The username'),
new InputArgument('email', InputArgument::REQUIRED, 'The email'),
new InputArgument('password', InputArgument::REQUIRED, 'The password'),
new InputArgument('nombre', InputArgument::REQUIRED, 'The nombre'),
new InputOption('super-admin', null, InputOption::VALUE_NONE, 'Set the user as super admin'),
new InputOption('inactive', null, InputOption::VALUE_NONE, 'Set the user as inactive'),
))
->setHelp(<<<EOT
//..
);
}
/**
* #see Command
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$username = $input->getArgument('username');
$email = $input->getArgument('email');
$password = $input->getArgument('password');
$nombre = $input->getArgument('nombre');
$inactive = $input->getOption('inactive');
$superadmin = $input->getOption('super-admin');
$manipulator = $this->getContainer()->get('inces_comedor.util.user_manipulator');
$manipulator->create($username, $password, $email, $nombre, !$inactive, $superadmin);
$output->writeln(sprintf('Created user <comment>%s</comment>', $username));
}
/**
* #see Command
*/
protected function interact(InputInterface $input, OutputInterface $output)
{
// ...
if (!$input->getArgument('nombre')) {
$nombre = $this->getHelper('dialog')->askAndValidate(
$output,
'Please choose a nombre:',
function($nombre) {
if (empty($nombre)) {
throw new \Exception('Nombre can not be empty');
}
return $nombre;
}
);
$input->setArgument('nombre', $nombre);
}
}
}
My UserManipulator file:
<?php
namespace INCES\ComedorBundle\Util;
use FOS\UserBundle\Model\UserInterface;
use FOS\UserBundle\Model\UserManagerInterface;
/**
* Executes some manipulations on the users
*
* #author Christophe Coevoet <stof#notk.org>
* #author Luis Cordova <cordoval#gmail.com>
*/
class UserManipulator
{
/**
* User manager
*
* #var UserManagerInterface
*/
private $userManager;
public function __construct(UserManagerInterface $userManager)
{
$this->userManager = $userManager;
}
/**
* Creates a user and returns it.
*
* #param string $username
* #param string $password
* #param string $email
* #param string $nombre
* #param Boolean $active
* #param Boolean $superadmin
*
* #return \FOS\UserBundle\Model\UserInterface
*/
public function create($username, $password, $email, $nombre, $active, $superadmin)
{
$user = $this->userManager->createUser();
$user->setUsername($username);
$user->setEmail($email);
$user->setNombre($nombre);
$user->setPlainPassword($password);
$user->setEnabled((Boolean) $active);
$user->setSuperAdmin((Boolean) $superadmin);
$this->userManager->updateUser($user);
return $user;
}
}

Here's a technique that may help:
In CreateUserCommand
protected function execute(InputInterface $input, OutputInterface $output)
{
$username = $input->getArgument('username');
$email = $input->getArgument('email');
$password = $input->getArgument('password');
$nombre = $input->getArgument('nombre');
$inactive = $input->getOption('inactive');
$superadmin = $input->getOption('super-admin');
$manipulator = $this->getContainer()->get('inces_comedor.util.user_manipulator');
$manipulator->setNombre($nombre);
$manipulator->create($username, $password, $email, $nombre, !$inactive, $superadmin);
$output->writeln(sprintf('Created user <comment>%s</comment>', $username));
}
and in UserManipulator add:
public function setNombre($nombre)
{
$this->nombre= $nombre;
}
and change to:
public function create($username, $password, $email, $active, $superadmin)

Related

Symfony 4.2 unit testing wont works

I am doing unit testing / functional testing, but my code gives me an error. Kindly have a look on my code and suggest a solution.
Codes On Controller
<?php
namespace App\Controller;
use App\Entity\Organization;
use App\Entity\User;
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
use Psr\Log\LoggerInterface;
use Swift_Mailer;
use Swift_Message;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
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\Validator\Validator\ValidatorInterface;
class RegisterController extends AbstractController
{
protected $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
/**
* Validate the data
* Encodes the password
* Creates new organization if evaplyId does not exists
* Create user and send verification email.
* return status
*
* #param Request $request
* #param UserPasswordEncoderInterface $userPasswordEncoder
* #param ValidatorInterface $validator
* #param Swift_Mailer $mailer
* #return Response
*/
public function register(Request $request, UserPasswordEncoderInterface $userPasswordEncoder, ValidatorInterface $validator, Swift_Mailer $mailer)
{
try {
$form = $request->getContent();
$form = json_decode($form);
$entityManager = $this->getDoctrine()->getManager(); //doctrine manager for entity managing
$user = new User();
$user->setEmail($form->email);
if (!$this->checkEmail($form->email)) {
return new JsonResponse(['status' => false, 'message' => 'Email already exists']);
}
$user->setFirstName($form->first_name);
$user->setLastName($form->last_name);
$user->setPassword($form->plain_password);
if (!$form->evaply_id) {
if(!$form->organization_name)
return new JsonResponse(['status' => false, 'message' => 'Missing field Organization']);
$organization = new Organization();
$organization->setName($form->organization_name);
$startGuideStatus['status'] = false;
$startGuideStatus['add_company'] = false;
$startGuideStatus['first_connection'] = false;
$startGuideStatus['first_scorecard'] = false;
$startGuideStatus['first_order'] = false;
$organization->setStartGuideStatus(($startGuideStatus));
$organization->setCustOrSupplier($form->cust_or_supplier);
$evaply_id = $this->generateEvaplyId($form->organization_name);
$organization->setEvaplyId($evaply_id);
$entityManager->persist($organization);
$entityManager->flush();
} else {
$organization = $this->getDoctrine()->getRepository(Organization::class)->findOneBy(array('evaplyId' => $form->evaply_id));
if (!$organization) {
return new JsonResponse(['status' => false, 'message' => 'Invalid Evaply ID']);
}
}
$user->setOrganization($organization);
$user->setUuid($this->generateUUID());
$user->setRoles(['Admin']);
$error = $validator->validate($user);
if (count($error) > 0) {
return new JsonResponse(['status' => false, 'message' => 'Validation error']);
}
if ($form->plain_password != $form->confirm_password) {
return new JsonResponse(['status' => false, 'message' => 'Password miss match']);
}
$user->setPassword(
$userPasswordEncoder->encodePassword(
$user,
$form->plain_password
)
);
$token = $this->generateVerificationToken($user);
$this->logger->info($token);
$verificationUrl = $this->getParameter('app_url') . "account/verify/" . $token;
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($user);
$entityManager->flush();
$this->sendUserRegisteredMail($user, $verificationUrl, $mailer);
return new JsonResponse(['status' => true, 'message' => "Successfully registered"]);
} catch (\Exception $e) {
$this->logger->error($e->getMessage());
return new Response($e->getMessage());
}
}
/**
* Check email duplication in database
*
* #param $email
* #return bool
*/
public function checkEmail($email)
{
$user = $this->getDoctrine()->getRepository(User::class)->findOneBy(array('email' => $email));
if ($user)
return false;
else
return true;
}
/**
* Generate a unique id using organization name and timestamp
*
* #param $orgName
* #return string
*/
public function generateEvaplyId($orgName)
{
$org = strtoupper(substr($orgName, 0, 3));
$dateTime = time();
$evaply_id = $org . $dateTime;
return $evaply_id;
}
/**
* Generate unique uuid for user
* Recursive function
*
* #return int
*/
public function generateUUID()
{
$uuid = rand(1000000000, 9999999999);
$user = $this->getDoctrine()->getRepository(User::class)->findOneBy(array('uuid' => $uuid));
if ($user) {
return $this->generateUUID();
} else {
return $uuid;
}
}
/**
* Send email to user after a successfull registartion.
*
* #param User $user
* #param Swift_Mailer $mailer
* #return int
*/
public function sendUserRegisteredMail(User $user, $verificationUrl, Swift_Mailer $mailer)
{
$message = (new Swift_Message("Successfully Registered"))
->setFrom('admin#evaply.com')
->setTo($user->getEmail())
->setBody(
$this->renderView(
'emails/registration.html.twig',
array('firstName' => $user->getFirstName(), 'lastName' => $user->getLastName(), 'verificationUrl' => $verificationUrl)
),
'text/html'
);
return $mailer->send($message);
}
/**
* Generates a verification token using aes-128-gcm encryption
* encrypts email and uuid
* encode ciphertext with base64 encoder
*
* #param User $user
* #return string
*/
public function generateVerificationToken(User $user)
{
$data = array();
$data['email'] = $user->getEmail();
$data['uuid'] = $user->getUuid();
$plaintext = json_encode($data);
$cipher = $this->getParameter('cipher');
if (in_array($cipher, openssl_get_cipher_methods())) {
$this->logger->info("inside cipher");
//$ivlen = openssl_cipher_iv_length($cipher);
$iv = $this->getParameter('secretIV');
$key = $this->getParameter('cipher_key');
//$tag = $this->getParameter('tagg');
$ciphertext = openssl_encrypt($plaintext, $cipher, $key, $options = 0, $iv);
$original_plaintext = openssl_decrypt($ciphertext, $cipher, $key, $options = 0, $iv);
return $token = base64_encode($ciphertext);
}
}
}
Codes On Tests/Controller Folder
<?php
/**
* Created by PhpStorm.
* User: xavier-phases
* Date: 22/1/19
* Time: 5:09 PM
*/
namespace App\Tests\Controller;
use App\Controller\RegisterController;
use PHPUnit\Framework\TestCase;
class EvaplyTest extends TestCase
{
public function testGenerate_evaply_clid(LoggerInterface $logger)
{
$id= new RegisterController();
$org_name=$id->generateEvaplyId('Tset');
$org = strtoupper(substr($org_name, 0, 3));
$dateTime = time();
$evaply_id = $org . $dateTime;
return $evaply_id;
}
}`
I am calling the generateEvaplyId() method from the register controller. I have installed all test packages. It gives me the following error:
"ArgumentCountError: Too few arguments to function App\Tests\Controller\EvaplyTest::testGenerate_evaply_clid(), 0 passed and exactly 1 expected". KIndly have a look and suggest me a solution.
<?php
namespace App\Tests\Controller;
use App\Controller\RegisterController;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
class EvaplyTest extends TestCase
{
/** #var LoggerInterface|MockObject */
protected $logger;
/** #var RegisterController **/
protected $controller;
/**
* {#inheritdoc}
*/
protected function setUp()
{
$this->logger = $this->createMock(LoggerInterface::class);
$this->controller = new RegisterController(
$this->logger,
);
parent::setUp();
}
public function testGenerate_evaply_clid()
{
$result = $this->controller->generateEvaplyId('Test');
$this->assertEquals( 'TEST1548303737',$result);
}
}
Here is how it should be. Initiate controller instance and all it's dependencies in setUp method, then, in all the tests you can reference them. By default, PHPUnit does not expect any dependencies for tests suite, what you tried to do is called data provider (check for data providers docs), it does require additional annotation and used for completely different purpose.
And for the last, I do NOT recommend you to store any part of logic in controllers. Move everything to service layer. Controllers are only to catch request, pass it to manager and return response.

TearDown database after a phpUnitTest on a WebTestCase using DataFixtures

I have the following phpUnit functional Test:
namespace Tests\AppBundle\Controller;
/**
* #testtype Functional
*/
class DefaultControllerTest extends BasicHttpController
{
/**
* {#inheritdoc}
*/
public function setUp()
{
$client = static::createClient();
$container = $client->getContainer();
$doctrine = $container->get('doctrine');
$entityManager = $doctrine->getManager();
$fixture = new YourFixture();
$fixture->load($entityManager);
}
/**
* {#inheritdoc}
*/
public function tearDown()
{
//Database is being destroyed here....
}
public function testIndex()
{
$client = static::createClient();
$crawler = $client->request('GET', '/');
$response=$client->getResponse();
$this->assertEquals(302, $response->getStatusCode());
$this->assertEquals('/login',$response->headers->get('Location'));
//#todo Create Dummy Users
$this->checkPanelAfterSucessfullLogin($crawler); //How I can create some user?
}
}
As you can see I load the following Fixture:
namespace AppBundle\DataFixtures\Test;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Doctrine\Common\Persistence\ObjectManager;
class DummyUserFixtures extends AbstractFixture implements OrderedFixtureInterface, ContainerAwareInterface
{
/**
* #var ContainerInterface
*/
private $container=null;
/**
* Generic function that creates a user with provided information.
* #param $name {String} The user's name
* #param $surname {String} The user's surname
* #param $username {String} The user's username
* #param $password {String} The user's password
* #param $email {String} The user's recovery email
* #param $role {String} The user's system role
* #param $phone {String | null} The user's phone number
* #param $organization {String|null} The user's organization
* #param $occupation {String|null} The user's occupation
*
* #return AppBundle\Entity\User
*/
private function createUser($name,$surname,$username,$password,$email,$role,$phone=null,$organization=null,$occupation=null)
{
$fosUserManager=$this->container->get('fos_user.user_manager');
/**
* #var AppBundle\Entity\User
*/
$user=$fosUserManager->createUser();
$user->setUsername($username);
$user->setEmail($email);
$user->setPlainPassword($password);
$user->setEnabled(true);
$user->setRoles(array($role));
$user->setName($name);
$user->setSurname($surname);
if($phone){
$user->setPhone($phone);
}
if($organization){
$user->setOrganization($organization);
}
if($occupation){
$user->serOccupation($occupation);
}
$fosUserManager->updateUser($user, true);
return $user;
}
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
/**
* {#inheritDoc}
*/
public function load(ObjectManager $manager)
{
$this->createUser('John','Doe','jdoe','simplepasswd','jdoe#example.com','ROLE_USER','+3021456742324','Acme Products','Soft Engineer');
$this->createUser('Jackie','Chan','jchan','thesimplepasswd','jackiechan#example.com','ROLE_ADMIN','+302141232324','Holywood','Actor');
$this->createUser('Chuck','Norris','chuck_norris','unhackablepasswd','chucknorris#example.com','ROLE_SUPERADMIN',null,'Universe','Master');
}
}
But I want to be able after my database changes happen to be able to tear down completely the database and recreate it for the next test. Do you have ant idea how to colpletely tear down the database?
As you are using Doctrine DataFixtures, you can implement this logic in your test:
<?php
namespace Tests\AppBundle\Controller;
Use Doctrine\Common\DataFixtures\Purger\ORMPurger;
/**
* #testtype Functional
*/
class DefaultControllerTest extends BasicHttpController
{
private $em;
/**
* {#inheritdoc}
*/
public function setUp()
{
$client = static::createClient();
$container = $client->getContainer();
$doctrine = $container->get('doctrine');
$this->em = $doctrine->getManager();
$fixture = new YourFixture();
$fixture->load($entityManager);
}
private function truncateEntities()
{
$purger = new ORMPurger($this->em);
$purger->purge();
}
public function tearDown()
{
$this->truncateEntities();
}
// your tests
}

Persisting Updated Objects in Symfony

I have a symfony application where I am attempting to update an entity in the database using a setter. However when I update the entity and call $this->em->flush() the entity is not persisted to the database.
Here is my service:
<?php
namespace AppBundle\Service;
use AppBundle\Exceptions\UserNotFoundException;
use Doctrine\ORM\EntityManager;
use AppBundle\Entity\User;
/**
* Class MyService
* #package AppBundle\Service
*/
class MyService extends BaseService {
/**
* #var EntityManager
*/
protected $em;
/**
* #var User
*/
protected $user;
/**
* MyService constructor.
* #param EntityManager $em
*/
public function __construct(EntityManager $em){
$this->em = $em;
}
/**
* See if a user email exists
* #param string $email
* #return bool
*/
public function checkEmailExists($email){
$this->user = $this->em
->getRepository('AppBundle:User')
->findOneBy(['email' => $email]);
return !(is_null($this->user));
}
/**
* add credit to a users account
* #param User $user
* #param integer $credit
*/
public function addCredit(User $user, $credit){
$user->addCredit($credit);
$this->em->flush();
}
/**
* add a credit to a users account
* #param $email
* #param $credit
*/
public function addCreditByEmail($email, $credit){
if(!($this->checkEmailExists($email))){
throw new UserNotFoundException(sprintf('User with email %s not found.', $email));
}
$this->addCredit($this->user, $credit);
}
}
Here is my test:
<?php
namespace AppBundle\Tests\Service;
use AppBundle\DataFixtures\ORM\PurchaseFixture;
use AppBundle\Entity\Vendor;
use AppBundle\Repository\OfferRepository;
use AppBundle\Tests\TestCase;
use AppBundle\Entity\Offer;
use AppBundle\DataFixtures\ORM\OfferFixture;
use AppBundle\DataFixtures\ORM\PaymentSystemFixture;
/**
* Class UserOfferServiceTest
* #package AppBundle\Tests\Service
*/
class MyServiceTest extends TestCase implements ServiceTestInterface
{
function __construct($name = null, array $data = [], $dataName = '')
{
$this->setFixtures([
'AppBundle\DataFixtures\ORM\CityFixture',
'AppBundle\DataFixtures\ORM\CountryFixture',
'AppBundle\DataFixtures\ORM\PaymentSystemFixture',
'AppBundle\DAtaFixtures\ORM\UserFixture',
]);
parent::__construct($name, $data, $dataName);
}
/**
* test the checkEmailExists() of app.vendor
*/
public function testCheckEmailExists(){
$myService = $this->getService();
$this->assertTrue($myService->checkEmailExists('user1#user1.com'));
$this->assertFalse($myService->checkEmailExists($this->fake()->safeEmail));
}
/**
* test the addCredit functionality
*/
public function testAddCredit(){
$myService = $this->getService();
$user = $this->getUser();
$this->assertEquals(0, $user->getCredit());
$toAdd = $this->fake()->numberBetween(1, 500);
$myService->addCredit($user, $toAdd);
$this->assertEquals($toAdd, $user->getCredit());
}
/**
* test the addCreditByEmail functionality
*/
public function testAddCreditByEmail(){
$myService = $this->getService();
$user = $this->getUser();
$email = $this->getUser()->getEmail();
$this->assertEquals(0, $user->getCredit());
$toAdd = $this->fake()->numberBetween(1, 500);
$myService->addCreditByEmail($email, $toAdd);
$updatedUser = $this->getEntityManager()
->getRepository('AppBundle:User')
->findOneBy(['email' => $email]);
$this->assertEquals($toAdd, $updatedUser->getCredit());
}
/**
* #return \AppBundle\Service\VendorService|object
*/
public function getService(){
$this->seedDatabase();
$this->client = $this->createClient();
return $this->client->getContainer()->get('app.admin_kiosk');
}
}
The testAddCredit() test passes (because I'm checking the same object), but the testAddCreditByEmail fails with the following error: 1) AppBundle\Tests\Service\MyServiceTest::testAddCreditByEmail
Failed asserting that null matches expected 149.
I've tried persisting the entity, flushing the entity (like: $this->em->flush($user)) all to no avail. Please let me know how I can fix this.
I found the issue.
In the test case I just had to refresh the entity by doing $this->getEntityManager()->refresh($user) (on the original $user entity). Thanks!

Overriding FOSUserBundle

I want to override FOSUserBundle so that I can add extra fields(name,avatar,...) to user entity .
I also want to create a command like fos:user:create for creating user,so I create createUserCommand.php and override UserManipulator.php but when runnig command it comes with this error Column 'name' cannot be null
I think I must override UserInteface,UserManager and ...
But in this way I have to override almost whole FOSUserBundle !!
Is there any good tutorial that explain how to do this job ?
Symp/UserBundle/Util/UsertManipulator
<?php
namespace Symp\UserBundle\Util;
use FOS\UserBundle\Model\UserManagerInterface;
class UserManipulator
{
/**
* User manager
*
* #var UserManagerInterface
*/
private $userManager;
public function __construct(UserManagerInterface $userManager)
{
$this->userManager = $userManager;
}
/**
* Creates a user and returns it.
*
* #param string $username
* #param string $password
* #param string $email
* #param Boolean $active
* #param Boolean $superadmin
*
* #return \FOS\UserBundle\Model\UserInterface
*/
public function create($username, $password, $email, $active, $superadmin,$name)
{
$user = $this->userManager->createUser();
$user->setName($name);
$user->setUsername($username);
$user->setEmail($email);
$user->setPlainPassword($password);
$user->setEnabled((Boolean) $active);
$user->setSuperAdmin((Boolean) $superadmin);
$this->userManager->updateUser($user);
return $user;
}
/**
* Activates the given user.
*
* #param string $username
*/
public function activate($username)
{
$user = $this->userManager->findUserByUsername($username);
if (!$user) {
throw new \InvalidArgumentException(sprintf('User identified by "%s" username does not exist.', $username));
}
$user->setEnabled(true);
$this->userManager->updateUser($user);
}
/**
* Deactivates the given user.
*
* #param string $username
*/
public function deactivate($username)
{
$user = $this->userManager->findUserByUsername($username);
if (!$user) {
throw new \InvalidArgumentException(sprintf('User identified by "%s" username does not exist.', $username));
}
$user->setEnabled(false);
$this->userManager->updateUser($user);
}
/**
* Changes the password for the given user.
*
* #param string $username
* #param string $password
*/
public function changePassword($username, $password)
{
$user = $this->userManager->findUserByUsername($username);
if (!$user) {
throw new \InvalidArgumentException(sprintf('User identified by "%s" username does not exist.', $username));
}
$user->setPlainPassword($password);
$this->userManager->updateUser($user);
}
/**
* Promotes the given user.
*
* #param string $username
*/
public function promote($username)
{
$user = $this->userManager->findUserByUsername($username);
if (!$user) {
throw new \InvalidArgumentException(sprintf('User identified by "%s" username does not exist.', $username));
}
$user->setSuperAdmin(true);
$this->userManager->updateUser($user);
}
/**
* Demotes the given user.
*
* #param string $username
*/
public function demote($username)
{
$user = $this->userManager->findUserByUsername($username);
if (!$user) {
throw new \InvalidArgumentException(sprintf('User identified by "%s" username does not exist.', $username));
}
$user->setSuperAdmin(false);
$this->userManager->updateUser($user);
}
/**
* Adds role to the given user.
*
* #param string $username
* #param string $role
*
* #return Boolean true if role was added, false if user already had the role
*/
public function addRole($username, $role)
{
$user = $this->userManager->findUserByUsername($username);
if (!$user) {
throw new \InvalidArgumentException(sprintf('User identified by "%s" username does not exist.', $username));
}
if ($user->hasRole($role)) {
return false;
}
$user->addRole($role);
$this->userManager->updateUser($user);
return true;
}
/**
* Removes role from the given user.
*
* #param string $username
* #param string $role
*
* #return Boolean true if role was removed, false if user didn't have the role
*/
public function removeRole($username, $role)
{
$user = $this->userManager->findUserByUsername($username);
if (!$user) {
throw new \InvalidArgumentException(sprintf('User identified by "%s" username does not exist.', $username));
}
if (!$user->hasRole($role)) {
return false;
}
$user->removeRole($role);
$this->userManager->updateUser($user);
return true;
}
}
Symp/UserBundle/Command/CreateUserCommand
<?php
namespace Symp\UserBundle\Command;
use FOS\UserBundle\Command\CreateUserCommand as BaseCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class CreateUserCommand extends BaseCommand
{
/**
* #see Command
*/
protected function configure()
{
$this
->setName('symp:user:create')
->setDescription('Create a user.')
->setDefinition(array(
new InputArgument('name', InputArgument::REQUIRED, 'The name of user'),
new InputArgument('username', InputArgument::REQUIRED, 'The username'),
new InputArgument('email', InputArgument::REQUIRED, 'The email'),
new InputArgument('password', InputArgument::REQUIRED, 'The password'),
new InputOption('super-admin', null, InputOption::VALUE_NONE, 'Set the user as super admin'),
new InputOption('inactive', null, InputOption::VALUE_NONE, 'Set the user as inactive'),
))
->setHelp(<<<EOT
The <info>fos:user:create</info> command creates a user:
<info>php app/console fos:user:create matthieu</info>
This interactive shell will ask you for an email and then a password.
You can alternatively specify the email and password as the second and third arguments:
<info>php app/console fos:user:create matthieu matthieu#example.com mypassword</info>
You can create a super admin via the super-admin flag:
<info>php app/console fos:user:create admin --super-admin</info>
You can create an inactive user (will not be able to log in):
<info>php app/console fos:user:create thibault --inactive</info>
EOT
);
}
/**
* #see Command
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$name = $input->getArgument('name');
$username = $input->getArgument('username');
$email = $input->getArgument('email');
$password = $input->getArgument('password');
$inactive = $input->getOption('inactive');
$superadmin = $input->getOption('super-admin');
$manipulator = $this->getContainer()->get('symp_user.util.user_manipulator');
$manipulator->create($username, $password, $email, !$inactive, $superadmin,$name);
$output->writeln(sprintf('Created user <comment>%s</comment>', $username));
}
/**
* #see Command
*/
protected function interact(InputInterface $input, OutputInterface $output)
{
if (!$input->getArgument('name')){
$name = $this->getHelper('dialog')->askAndValidate(
$output,
'Please choose a name: ',
function($name){
if(empty($name)){
throw new \Exception('Name can not be empty');
}
}
);
$input->setArgument('name',$name);
}
parent::interact($input,$output);
}
}
The error occurs because $name is never set. In the following $name is passed to the manipulator with a setter; $name does not appear in the argument list.
Instead try this:
services.yml
app.user_manipulator:
class: AppBundle\Tools\UserManipulator
arguments: [#fos_user.user_manager]
CreateUserCommand modification
namespace AppBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
/**
* #author Matthieu Bontemps <matthieu#knplabs.com>
* #author Thibault Duplessis <thibault.duplessis#gmail.com>
* #author Luis Cordova <cordoval#gmail.com>
*/
class CreateUserCommand extends ContainerAwareCommand
{
/**
* #see Command
*/
protected function configure()
{
$this
->setName('app:user:create')
->setDescription('Create a user.')
->setDefinition(array(
new InputArgument('username', InputArgument::REQUIRED, 'A username'),
new InputArgument('name', InputArgument::REQUIRED, 'A name'),
new InputArgument('email', InputArgument::REQUIRED, 'An email'),
new InputArgument('password', InputArgument::REQUIRED, 'A password'),
new InputOption('inactive', null, InputOption::VALUE_NONE, 'Set the user as inactive'),
new InputOption('superadmin', null, InputOption::VALUE_NONE, 'Set the user as superadmin'),
))
->setHelp(<<<EOT
The <info>app:user:create</info> command creates a user:
<info>php app/console app:user:create bborko</info>
This interactive shell will ask you for ...
EOT
);
}
/**
* #see Command
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$username = $input->getArgument('username');
$name = $input->getArgument('name');
$email = $input->getArgument('email');
$password = $input->getArgument('password');
$inactive = $input->getOption('inactive');
$superadmin = $input->getOption('superadmin');
$manipulator = $this->getContainer()->get('app.user_manipulator');
$manipulator->setName($name);
$manipulator->create($username, $password, $email, !$inactive, $superadmin);
$output->writeln(sprintf('Created user <comment>%s</comment>', $username));
}
/**
* #see Command
*/
protected function interact(InputInterface $input, OutputInterface $output)
{
$helper = $this->getHelper('question');
if (!$input->getArgument('username')) {
$question = new Question('Please enter a username: ');
$question->setValidator(function ($answer) {
if (empty($answer)) {
throw new \RuntimeException(
'A username is required'
);
}
return $answer;
});
$question->setMaxAttempts(2);
$input->setArgument('username', $helper->ask($input, $output, $question));
}
if (!$input->getArgument('name')) {
$question = new Question('Please enter a name: ');
$question->setValidator(function ($answer) {
if (empty($answer)) {
throw new \RuntimeException(
'A name is required'
);
}
return $answer;
});
$question->setMaxAttempts(2);
$input->setArgument('name', $helper->ask($input, $output, $question));
}
if (!$input->getArgument('email')) {
$question = new Question('Please enter an email: ');
$question->setValidator(function ($answer) {
if (empty($answer)) {
throw new \RuntimeException(
'An e-mail address is required'
);
}
return $answer;
});
$question->setMaxAttempts(2);
$input->setArgument('email', $helper->ask($input, $output, $question));
}
if (!$input->getArgument('password')) {
$question = new Question('Please enter a password: ');
$question->setValidator(function ($answer) {
if (empty($answer)) {
throw new \RuntimeException(
'A password is required'
);
}
return $answer;
});
$question->setMaxAttempts(5);
$input->setArgument('password', $helper->ask($input, $output, $question));
}
}
}
UserManipulator
namespace AppBundle\Tools;
use FOS\UserBundle\Model\UserManagerInterface;
use FOS\UserBundle\Util\UserManipulator as Manipulator;
/**
* Executes some manipulations on the users
*
* #author Christophe Coevoet <stof#notk.org>
* #author Luis Cordova <cordoval#gmail.com>
*/
class UserManipulator extends Manipulator
{
/**
* User manager
*
* #var UserManagerInterface
*/
private $userManager;
public function __construct(UserManagerInterface $userManager)
{
$this->userManager = $userManager;
}
/**
* Creates a user and returns it.
*
* #param string $username
* #param string $password
* #param string $email
* #param Boolean $active
* #param Boolean $superadmin
*
* #return \FOS\UserBundle\Model\UserInterface
*/
public function create($username, $password, $email, $active, $superadmin)
{
$user = $this->userManager->createUser();
$user->setUsername($username);
$user->setName($this->name);
$user->setEmail($email);
$user->setPlainPassword($password);
$user->setEnabled((Boolean) $active);
$this->userManager->updateUser($user, true);
return $user;
}
public function setName($name)
{
$this->name = $name;
}
}

Symfony 2 - keeping the code DRY - FOSRestBundle

I'm currently using Symfony2 to create (and learn how to) a REST API. I'm using FOSRestBundle and i've created an "ApiControllerBase.php" with the following :
<?php
namespace Utopya\UtopyaBundle\Controller;
use FOS\RestBundle\Controller\FOSRestController;
use FOS\RestBundle\View\View;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormTypeInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Class ApiControllerBase
*
* #package Utopya\UtopyaBundle\Controller
*/
abstract class ApiControllerBase extends FOSRestController
{
/**
* #param string $entityName
* #param string $entityClass
*
* #return array
* #throws NotFoundHttpException
*/
protected function getObjects($entityName, $entityClass)
{
$dataRepository = $this->container->get("doctrine")->getRepository($entityClass);
$entityName = $entityName."s";
$data = $dataRepository->findAll();
foreach ($data as $object) {
if (!$object instanceof $entityClass) {
throw new NotFoundHttpException("$entityName not found");
}
}
return array($entityName => $data);
}
/**
* #param string $entityName
* #param string $entityClass
* #param integer $id
*
* #return array
* #throws NotFoundHttpException
*/
protected function getObject($entityName, $entityClass, $id)
{
$dataRepository = $this->container->get("doctrine")->getRepository($entityClass);
$data = $dataRepository->find($id);
if (!$data instanceof $entityClass) {
throw new NotFoundHttpException("$entityName not found");
}
return array($entityClass => $data);
}
/**
* #param FormTypeInterface $objectForm
* #param mixed $object
* #param string $route
*
* #return Response
*/
protected function processForm(FormTypeInterface $objectForm, $object, $route)
{
$statusCode = $object->getId() ? 204 : 201;
$em = $this->getDoctrine()->getManager();
$form = $this->createForm($objectForm, $object);
$form->submit($this->container->get('request_stack')->getCurrentRequest());
if ($form->isValid()) {
$em->persist($object);
$em->flush();
$response = new Response();
$response->setStatusCode($statusCode);
// set the `Location` header only when creating new resources
if (201 === $statusCode) {
$response->headers->set('Location',
$this->generateUrl(
$route, array('id' => $object->getId(), '_format' => 'json'),
true // absolute
)
);
}
return $response;
}
return View::create($form, 400);
}
}
This handles getting one object with a given id, all objects and process a form. But to use this i have to create as many controller as needed. By example : GameController.
<?php
namespace Utopya\UtopyaBundle\Controller;
use FOS\RestBundle\Controller\FOSRestController;
use FOS\RestBundle\Controller\Annotations as Rest;
use FOS\RestBundle\View\View;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Utopya\UtopyaBundle\Entity\Game;
use Utopya\UtopyaBundle\Form\GameType;
/**
* Class GameController
*
* #package Utopya\UtopyaBundle\Controller
*/
class GameController extends ApiControllerBase
{
private $entityName = "Game";
private $entityClass = 'Utopya\UtopyaBundle\Entity\Game';
/**
* #Rest\View()
*/
public function getGamesAction()
{
return $this->getObjects($this->entityName, $this->entityClass);
}
/**
* #param int $id
*
* #return array
* #throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
* #Rest\View()
*/
public function getGameAction($id)
{
return $this->getObject($this->entityName, $this->entityClass, $id);
}
/**
* #return mixed
*/
public function postGameAction()
{
return $this->processForm(new GameType(), new Game(), "get_game");
}
}
This sound not so bad to me but there's a main problem : if i want to create another controller (by example Server or User or Character), i'll have to do the same process and i don't want to since it'll be the same logic.
Another "maybe" problem could be my $entityName and $entityClass.
Any idea or could i make this better ?
Thank-you !
===== Edit 1 =====
I think i made up my mind. For those basics controllers. I would like to be able to "configure" instead of "repeat".
By example i could make a new node in config.yml with the following :
#config.yml
mynode:
game:
entity: 'Utopya\UtopyaBundle\Entity\Game'
This is a very basic example but is it possible to make this and transform it into my GameController with 3 methods routes (getGame, getGames, postGame) ?
I just want some leads if i can really achieve with this way or not, if yes with what components ? (Config, Router, etc.)
If no, what could i do? :)
Thanks !
I'd like to show my approach here.
I've created a base controller for the API, and I stick with the routing that's generated with FOSRest's rest routing type. Hence, the controller looks like this:
<?php
use FOS\RestBundle\Controller\Annotations\View;
use \Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Psr\Log\LoggerInterface;
use Symfony\Component\Form\FormFactoryInterface,
Symfony\Bridge\Doctrine\RegistryInterface,
Symfony\Component\Security\Core\SecurityContextInterface,
Doctrine\Common\Persistence\ObjectRepository;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
abstract class AbstractRestController
{
/**
* #var \Symfony\Component\Form\FormFactoryInterface
*/
protected $formFactory;
/**
* #var string
*/
protected $formType;
/**
* #var string
*/
protected $entityClass;
/**
* #var SecurityContextInterface
*/
protected $securityContext;
/**
* #var RegistryInterface
*/
protected $doctrine;
/**
* #var EventDispatcherInterface
*/
protected $dispatcher;
/**
* #param FormFactoryInterface $formFactory
* #param RegistryInterface $doctrine
* #param SecurityContextInterface $securityContext
* #param LoggerInterface $logger
*/
public function __construct(
FormFactoryInterface $formFactory,
RegistryInterface $doctrine,
SecurityContextInterface $securityContext,
EventDispatcherInterface $dispatcher
)
{
$this->formFactory = $formFactory;
$this->doctrine = $doctrine;
$this->securityContext = $securityContext;
$this->dispatcher = $dispatcher;
}
/**
* #param string $formType
*/
public function setFormType($formType)
{
$this->formType = $formType;
}
/**
* #param string $entityClass
*/
public function setEntityClass($entityClass)
{
$this->entityClass = $entityClass;
}
/**
* #param null $data
* #param array $options
*
* #return \Symfony\Component\Form\FormInterface
*/
public function createForm($data = null, $options = array())
{
return $this->formFactory->create(new $this->formType(), $data, $options);
}
/**
* #return RegistryInterface
*/
public function getDoctrine()
{
return $this->doctrine;
}
/**
* #return \Doctrine\ORM\EntityRepository
*/
public function getRepository()
{
return $this->doctrine->getRepository($this->entityClass);
}
/**
* #param Request $request
*
* #View(serializerGroups={"list"}, serializerEnableMaxDepthChecks=true)
*/
public function cgetAction(Request $request)
{
$this->logger->log('DEBUG', 'CGET ' . $this->entityClass);
$offset = null;
$limit = null;
if ($range = $request->headers->get('Range')) {
list($offset, $limit) = explode(',', $range);
}
return $this->getRepository()->findBy(
[],
null,
$limit,
$offset
);
}
/**
* #param int $id
*
* #return object
*
* #View(serializerGroups={"show"}, serializerEnableMaxDepthChecks=true)
*/
public function getAction($id)
{
$this->logger->log('DEBUG', 'GET ' . $this->entityClass);
$object = $this->getRepository()->find($id);
if (!$object) {
throw new NotFoundHttpException(sprintf('%s#%s not found', $this->entityClass, $id));
}
return $object;
}
/**
* #param Request $request
*
* #return \Symfony\Component\Form\Form|\Symfony\Component\Form\FormInterface
*
* #View()
*/
public function postAction(Request $request)
{
$object = new $this->entityClass();
$form = $this->createForm($object);
$form->submit($request);
if ($form->isValid()) {
$this->doctrine->getManager()->persist($object);
$this->doctrine->getManager()->flush($object);
return $object;
}
return $form;
}
/**
* #param Request $request
* #param int $id
*
* #return \Symfony\Component\Form\FormInterface
*
* #View()
*/
public function putAction(Request $request, $id)
{
$object = $this->getRepository()->find($id);
if (!$object) {
throw new NotFoundHttpException(sprintf('%s#%s not found', $this->entityClass, $id));
}
$form = $this->createForm($object);
$form->submit($request);
if ($form->isValid()) {
$this->doctrine->getManager()->persist($object);
$this->doctrine->getManager()->flush($object);
return $object;
}
return $form;
}
/**
* #param Request $request
* #param $id
*
* #View()
*
* #return object|\Symfony\Component\Form\FormInterface
*/
public function patchAction(Request $request, $id)
{
$this->logger->log('DEBUG', 'PATCH ' . $this->entityClass);
$object = $this->getRepository()->find($id);
if (!$object) {
throw new NotFoundHttpException(sprintf('%s#%s not found', $this->entityClass, $id));
}
$form = $this->createForm($object);
$form->submit($request, false);
if ($form->isValid()) {
$this->doctrine->getManager()->persist($object);
$this->doctrine->getManager()->flush($object);
return $object;
}
return $form;
}
/**
* #param int $id
*
* #return array
*
* #View()
*/
public function deleteAction($id)
{
$this->logger->log('DEBUG', 'DELETE ' . $this->entityClass);
$object = $this->getRepository()->find($id);
if (!$object) {
throw new NotFoundHttpException(sprintf('%s#%s not found', $this->entityClass, $id));
}
$this->doctrine->getManager()->remove($object);
$this->doctrine->getManager()->flush($object);
return ['success' => true];
}
}
It has methods for most of RESTful actions. Next, when I want to implement a CRUD for an entity, that's what I do:
The abstract controller is registered in the DI as an abstract service:
my_api.abstract_controller:
class: AbstractRestController
abstract: true
arguments:
- #form.factory
- #doctrine
- #security.context
- #logger
- #event_dispatcher
Next, when I'm implementing a CRUD for a new entity, I create an empty class and a service definition for it:
class SettingController extends AbstractRestController implements ClassResourceInterface {}
Note that the class implements ClassResourceInterface. This is necessary for the rest routing to work.
Here's the service declaration for this controller:
api.settings.controller.class: MyBundle\Controller\SettingController
api.settings.form.class: MyBundle\Form\SettingType
my_api.settings.controller:
class: %api.settings.controller.class%
parent: my_api.abstract_controller
calls:
- [ setEntityClass, [ MyBundle\Entity\Setting ] ]
- [ setFormType, [ %my_api.settings.form.class% ] ]
Then, I'm just including the controller in routing.yml as the FOSRestBundle doc states, and it's done.

Categories