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;
}
}
Related
I’m creating an authentication / login system using Slim 3 PHP on the back-end and Angular on the front-end and I’m trying to understand the ‘domain object’ and ‘data mapper’ part of a model layer within an MVC structure. I’ve read a lot of useful answers on various questions such as this, from which I understand the model should be comprised of ‘domain objects’, ‘data mappers’ and ‘services’.
However I’m not exactly sure what how this should be structured in the context of a user being able to register and log in to a website.
From my understanding I could have a user 'domain object' that has properties such as username and password. It could also have methods such as register or log in to represent business logic.
Would I then have a service class that creates a new instance of a user object, in which I would pass the form data into the object? So now my user object instance would have set username and password values?
Now i'm not sure how this objects property data would be inserted into the database. Would I use the user objects register method to insert the data into the database by passing in the username and password as parameters?
Apparently the service should be where the domain object and the data mapper interact, but i'm not sure how this would work if the register method is in the user domain object.
I was hoping someone could show me some code examples of what should be in the service class and how the interaction between the domain object and data mapper might work in the context of a user registering and logging in.
Note I don't want to use any frameworks, I want to try and implement a proper MVC structure manually as I feel i'd learn more.
So far I have this structure for registering a user:
I have an AuthenticationController with the method registerUser to allow a user to create an account:
class AuthenticationController
{
protected $authenticationService;
public function __construct(AuthenticationService $authenticationService)
{
$this->authenticationService = $authenticationService;
}
public function registerUser($request, $response)
{
$this->authenticationService->registerUser($request, $response);
}
}
I then have the AuthenticationService class with the registerUser method:
class AuthenticationService
{
protected $database;
public function __construct(PDO $database)
{
$this->database = $database;
}
public function registerUser ($request, $response)
{
$strings = $request→getParsedBody(); // will be sanitised / validated later
$username = $strings['username'];
$password = $strings['password'];
$email = "temp random email";
$stmt = $this->database->prepare("INSERT INTO users (email, username, password) values (:email, :username, :password)");
$stmt->bindParam(':email', $email);
$stmt->bindParam(':username', $username);
$stmt->bindParam(':password', $password);
$stmt->execute();
}
}
Later on I intend to put the SQL into an AuthenticationRepository and the PDO logic into it’s own class. This AuthenticationService method will also make sure the user details are sanitised using PHP’s built in functions.
I’m not sure if the proposed PDO database class or AuthenticationRepository would count as a data mapper or not.
The registration would be performed by the service.
The service could "directly" use a data mapper, in order to "transfer" the entity to/from the database. Though, additionally, a repository can be implemented. The service would see it and communicate with it as with a collection of one or more entities.
Since a service is part of the model layer (domain model), it should know nothing about any request or response objects. The controller should extract the needed values from the request and pass them as arguments to the service methods. A response can be sent back by the controller, or the view, depending on which MVC variation you are trying to implement.
You say "I intend to put the [...] PDO logic into it's own class". You really don't need to implement a wrapper for the PDO extension.
Here a registration example. I didn't test it at all. For more details see the resources list at the end of this answer. Maybe begin with the last one, which - I just realized - is the answer to a question of yours.
Used file system structure:
a) Extended "MyApp/UI":
b) Extended "MyApp/Domain":
The controller:
<?php
namespace MyApp\UI\Web\Controller\Users;
use Psr\Http\Message\ServerRequestInterface;
use MyApp\Domain\Model\Users\Exception\InvalidData;
use MyApp\Domain\Service\Users\Exception\FailedRegistration;
use MyApp\Domain\Service\Users\Registration as RegistrationService;
class Registration {
private $registration;
public function __construct(RegistrationService $registration) {
$this->registration = $registration;
}
public function register(ServerRequestInterface $request) {
$username = $request->getParsedBody()['username'];
$password = $request->getParsedBody()['password'];
$email = $request->getParsedBody()['email'];
try {
$user = $this->registration->register($username, $password, $email);
} catch (InvalidData $exc) {
// Write the exception message to a flash messenger, for example,
// in order to be read and displayed by the specific view component.
var_dump($exc->getMessage());
} catch (FailedRegistration $exc) {
// Write the exception message to the flash messenger.
var_dump($exc->getMessage());
}
// In the view component, if no exception messages are found in the flash messenger, display a success message.
var_dump('Successfully registered.');
}
}
The service:
<?php
namespace MyApp\Domain\Service\Users;
use MyApp\Domain\Model\Users\User;
use MyApp\Domain\Model\Users\Email;
use MyApp\Domain\Model\Users\Password;
use MyApp\Domain\Service\Users\Exception\UserExists;
use MyApp\Domain\Model\Users\UserCollection as UserCollectionInterface;
class Registration {
/**
* User collection, e.g. user repository.
*
* #var UserCollectionInterface
*/
private $userCollection;
public function __construct(UserCollectionInterface $userCollection) {
$this->userCollection = $userCollection;
}
/**
* Register user.
*
* #param string $username Username.
* #param string $password Password.
* #param string $email Email.
* #return User User.
*/
public function register(string $username, string $password, string $email) {
$user = $this->createUser($username, $password, $email);
return $this->storeUser($user);
}
/**
* Create user.
*
* #param string $username Username.
* #param string $password Password.
* #param string $email Email.
* #return User User.
*/
private function createUser(string $username, string $password, string $email) {
// Create the object values (containing specific validation).
$email = new Email($email);
$password = new Password($password);
// Create the entity (e.g. the domain object).
$user = new User();
$user->setUsername($username);
$user->setEmail($email);
$user->setPassword($password);
return $user;
}
/**
* Store user.
*
* #param User $user User.
* #return User User.
*/
private function storeUser(User $user) {
// Check if user already exists.
if ($this->userCollection->exists($user)) {
throw new UserExists();
}
return $this->userCollection->store($user);
}
}
The exception thrown when trying to register an already existing user:
<?php
namespace MyApp\Domain\Service\Users\Exception;
use MyApp\Domain\Service\Users\Exception\FailedRegistration;
class UserExists extends FailedRegistration {
public function __construct(\Exception $previous = null) {
$message = 'User already exists.';
$code = 123;
parent::__construct($message, $code, $previous);
}
}
<?php
namespace MyApp\Domain\Service\Users\Exception;
abstract class FailedRegistration extends \Exception {
public function __construct(string $message, int $code = 0, \Exception $previous = null) {
$message = 'Registration failed: ' . $message;
parent::__construct($message, $code, $previous);
}
}
The domain object (entity):
<?php
namespace MyApp\Domain\Model\Users;
use MyApp\Domain\Model\Users\Email;
use MyApp\Domain\Model\Users\Password;
/**
* User entity (e.g. domain object).
*/
class User {
private $id;
private $username;
private $email;
private $password;
public function getId() {
return $this->id;
}
public function setId(int id) {
$this->id = $id;
return $this;
}
public function getUsername() {
return $this->username;
}
public function setUsername(string $username) {
$this->username = $username;
return $this;
}
public function getEmail() {
return $this->email;
}
public function setEmail(Email $email) {
$this->email = $email;
return $this;
}
public function getPassword() {
return $this->password;
}
public function setPassword(Password $password) {
$this->password = $password;
return $this;
}
}
The value objects used by the entity:
<?php
namespace MyApp\Domain\Model\Users;
use MyApp\Domain\Model\Users\Exception\InvalidEmail;
/**
* Email object value.
*/
class Email {
private $email;
public function __construct(string $email) {
if (!$this->isValid($email)) {
throw new InvalidEmail();
}
$this->email = $email;
}
private function isValid(string $email) {
return (isEmpty($email) || !isWellFormed($email)) ? false : true;
}
private function isEmpty(string $email) {
return empty($email) ? true : false;
}
private function isWellFormed(string $email) {
return !filter_var($email, FILTER_VALIDATE_EMAIL) ? false : true;
}
public function __toString() {
return $this->email;
}
}
<?php
namespace MyApp\Domain\Model\Users;
use MyApp\Domain\Model\Users\Exception\InvalidPassword;
/**
* Password object value.
*/
class Password {
private const MIN_LENGTH = 8;
private $password;
public function __construct(string $password) {
if (!$this->isValid($password)) {
throw new InvalidPassword();
}
$this->password = $password;
}
private function isValid(string $password) {
return (isEmpty($password) || isTooShort($password)) ? false : true;
}
private function isEmpty(string $password) {
return empty($password) ? true : false;
}
private function isTooShort(string $password) {
return strlen($password) < self::MIN_LENGTH ? true : false;
}
public function __toString() {
return $this->password;
}
}
The exceptions thrown by the value objects:
<?php
namespace MyApp\Domain\Model\Users\Exception;
use MyApp\Domain\Model\Users\Exception\InvalidData;
class InvalidEmail extends InvalidData {
public function __construct(\Exception $previous = null) {
$message = 'The email address is not valid.';
$code = 123402;
parent::__construct($message, $code, $previous);
}
}
<?php
namespace MyApp\Domain\Model\Users\Exception;
use MyApp\Domain\Model\Users\Exception\InvalidData;
class InvalidPassword extends InvalidData {
public function __construct(\Exception $previous = null) {
$message = 'The password is not valid.';
$code = 123401;
parent::__construct($message, $code, $previous);
}
}
<?php
namespace MyApp\Domain\Model\Users\Exception;
abstract class InvalidData extends \LogicException {
public function __construct(string $message, int $code = 0, \Exception $previous = null) {
$message = 'Invalid data: ' . $message;
parent::__construct($message, $code, $previous);
}
}
The repository interface:
<?php
namespace MyApp\Domain\Model\Users;
use MyApp\Domain\Model\Users\User;
/**
* User collection, e.g. user repository.
*/
interface UserCollection {
/**
* Find a user by id.
*
* #param int $id User id.
* #return User|null User.
*/
public function findById(int $id);
/**
* Find all users.
*
* #return User[] User list.
*/
public function findAll();
/**
* Check if the given user exists.
*
* #param User $user User
* #return bool True if user exists, false otherwise.
*/
public function exists(User $user);
/**
* Store a user.
*
* #param User $user User
* #return User User.
*/
public function store(User $user);
}
The repository:
<?php
namespace MyApp\Domain\Infrastructure\Repository\Users;
use MyApp\Domain\Model\Users\User;
use MyApp\Domain\Infrastructure\Mapper\Users\UserMapper;
use MyApp\Domain\Model\Users\UserCollection as UserCollectionInterface;
/**
* User collection, e.g. user repository.
*/
class UserCollection implements UserCollectionInterface {
private $userMapper;
public function __construct(UserMapper $userMapper) {
$this->userMapper = $userMapper;
}
/**
* Find a user by id.
*
* #param int $id User id.
* #return User|null User.
*/
public function findById(int $id) {
return $this->userMapper->fetchUserById($id);
}
/**
* Find all users.
*
* #return User[] User list.
*/
public function findAll() {
return $this->userMapper->fetchAllUsers();
}
/**
* Check if the given user exists.
*
* #param User $user User
* #return bool True if user exists, false otherwise.
*/
public function exists(User $user) {
return $this->userMapper->userExists($user);
}
/**
* Store a user.
*
* #param User $user User
* #return User User.
*/
public function store(User $user) {
return $this->userMapper->saveUser($user);
}
}
The data mapper interface:
<?php
namespace MyApp\Domain\Infrastructure\Mapper\Users;
use MyApp\Domain\Model\Users\User;
/**
* User mapper.
*/
interface UserMapper {
/**
* Fetch a user by id.
*
* #param int $id User id.
* #return User|null User.
*/
public function fetchUserById(int $id);
/**
* Fetch all users.
*
* #return User[] User list.
*/
public function fetchAllUsers();
/**
* Check if the given user exists.
*
* #param User $user User.
* #return bool True if the user exists, false otherwise.
*/
public function userExists(User $user);
/**
* Save a user.
*
* #param User $user User.
* #return User User.
*/
public function saveUser(User $user);
}
The data mapper:
<?php
namespace MyApp\Domain\Infrastructure\Mapper\Users;
use PDO;
use MyApp\Domain\Model\Users\User;
use MyApp\Domain\Model\Users\Email;
use MyApp\Domain\Model\Users\Password;
use MyApp\Domain\Infrastructure\Mapper\Users\UserMapper;
/**
* PDO user mapper.
*/
class PdoUserMapper implements UserMapper {
/**
* Database connection.
*
* #var PDO
*/
private $connection;
public function __construct(PDO $connection) {
$this->connection = $connection;
}
/**
* Fetch a user by id.
*
* Note: PDOStatement::fetch returns FALSE if no record is found.
*
* #param int $id User id.
* #return User|null User.
*/
public function fetchUserById(int $id) {
$sql = 'SELECT * FROM users WHERE id = :id LIMIT 1';
$statement = $this->connection->prepare($sql);
$statement->execute([
'id' => $id,
]);
$record = $statement->fetch(PDO::FETCH_ASSOC);
return ($record === false) ? null : $this->convertRecordToUser($record);
}
/**
* Fetch all users.
*
* #return User[] User list.
*/
public function fetchAllUsers() {
$sql = 'SELECT * FROM users';
$statement = $this->connection->prepare($sql);
$statement->execute();
$recordset = $statement->fetchAll(PDO::FETCH_ASSOC);
return $this->convertRecordsetToUserList($recordset);
}
/**
* Check if the given user exists.
*
* Note: PDOStatement::fetch returns FALSE if no record is found.
*
* #param User $user User.
* #return bool True if the user exists, false otherwise.
*/
public function userExists(User $user) {
$sql = 'SELECT COUNT(*) as cnt FROM users WHERE username = :username';
$statement = $this->connection->prepare($sql);
$statement->execute([
':username' => $user->getUsername(),
]);
$record = $statement->fetch(PDO::FETCH_ASSOC);
return ($record['cnt'] > 0) ? true : false;
}
/**
* Save a user.
*
* #param User $user User.
* #return User User.
*/
public function saveUser(User $user) {
$id = $user->getId();
if (!isset($id)) {
return $this->insertUser($user);
}
return $this->updateUser($user);
}
/**
* Insert a user.
*
* #param User $user User.
* #return User User.
*/
private function insertUser(User $user) {
$sql = 'INSERT INTO users (
username,
password,
email
) VALUES (
:username,
:password,
:email
)';
$statement = $this->connection->prepare($sql);
$statement->execute([
':username' => $user->getUsername(),
':password' => (string) $user->getPassword(),
':email' => (string) $user->getEmail(),
]);
$user->setId($this->connection->lastInsertId());
return $user;
}
/**
* Update a user.
*
* #param User $user User.
* #return User User.
*/
private function updateUser(User $user) {
$sql = 'UPDATE users
SET
username = :username,
password = :password,
email = :email
WHERE id = :id';
$statement = $this->connection->prepare($sql);
$statement->execute([
':id' => $user->getId(),
':username' => $user->getUsername(),
':password' => (string) $user->getPassword(),
':email' => (string) $user->getEmail(),
]);
return $user;
}
/**
* Convert a record to a user.
*
* #param array $record Record data.
* #return User User.
*/
private function convertRecordToUser(array $record) {
$user = $this->createUser(
$record['id'],
$record['username'],
$record['password'],
$record['email']
);
return $user;
}
/**
* Convert a recordset to a list of users.
*
* #param array $recordset Recordset data.
* #return User[] User list.
*/
private function convertRecordsetToUserList(array $recordset) {
$users = [];
foreach ($recordset as $record) {
$users[] = $this->convertRecordToUser($record);
}
return $users;
}
/**
* Create user.
*
* #param int $id User id.
* #param string $username Username.
* #param string $password Password.
* #param string $email Email.
* #return User User.
*/
private function createUser(int $id, string $username, string $password, string $email) {
$user = new User();
$user
->setId($id)
->setUsername($username)
->setPassword(new Password($password))
->setEmail(new Email($email))
;
return $user;
}
}
Resources:
Keynote: Architecture the Lost Years
Sandro Mancuso : An introduction to interaction-driven design
Unbreakable Domain Models
An older answer of mine, for some explanations.
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.
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!
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)
I have a User table where userID, username and password are stored and a Role table which contains user role . To link these two tables, I have a table (user_role) which contains userID and roleID. How can I use Zend Auth to authenticate users and use Zend Acl to control user access. This is the database design
You can create a Zend_Auth adapter that works with whatever structure your application has.
Here is an example of an Auth adapter that uses my entity models and mappers to provide the credentials and user data for authentication.
<?php
/**
* Description of Auth_Adapter
*
*/
class Auth_Adapter implements Zend_Auth_Adapter_Interface
{
/**
* The username
*
* #var string
*/
protected $identity = null;
/**
* The password
*
* #var string
*/
protected $credential = null;
/**
* Users database object
*
* #var Model_Mapper_Abstract
*/
protected $usersMapper = null;
/**
* #param string $username
* #param string $password
* #param Model_Mapper_Abstract $userMapper
*/
public function __construct($username, $password, Model_Mapper_Abstract $userMapper = null)
{
if (!is_null($userMapper)) {
$this->setMapper($userMapper);
} else {
$this->usersMapper = new Application_Model_Mapper_User();
}
$this->setIdentity($username);
$this->setCredential($password);
}
/**
* #return \Zend_Auth_Result
*/
public function authenticate()
{
// Fetch user information according to username
$user = $this->getUserObject();
if (is_null($user)) {
return new Zend_Auth_Result(
Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND,
$this->getIdentity(),
array('Invalid username')
);
}
// check whether or not the hash matches using my own password class
$check = Password::comparePassword($this->getCredential(), $user->password);
if (!$check) {
return new Zend_Auth_Result(
Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID,
$this->getIdentity(),
array('Incorrect password')
);
}
// Success!
return new Zend_Auth_Result(
Zend_Auth_Result::SUCCESS,
$this->getIdentity(),
array()
);
}
/**
* #param type $userName
* #return \Auth_Adapter
*/
public function setIdentity($userName)
{
$this->identity = $userName;
return $this;
}
/**
* #param type $password
* #return \Auth_Adapter
*/
public function setCredential($password)
{
$this->credential = $password;
return $this;
}
/**
* #param type $mapper
* #return \Auth_Adapter
*/
public function setMapper($mapper)
{
$this->usersMapper = $mapper;
return $this;
}
/**
* #return object
*/
private function getUserObject()
{
return $this->getMapper()->findOneByColumn('name', $this->getIdentity());
}
/**
* #return object
*/
public function getUser()
{
$object = $this->getUserObject();
$array = array(
'id' => $object->id,
'name' => $object->name,
'role' => $object->role
);
return (object) $array;
}
/**
* #return string
*/
public function getIdentity()
{
return $this->identity;
}
/**
* #return string
*/
public function getCredential()
{
return $this->credential;
}
/**
* #return object Model_Mapper_Abstract
*/
public function getMapper()
{
return $this->usersMapper;
}
}
You could also extend any of the current adapters to provide the functionality you need.
Good Luck!