├── web ├── css │ └── main.css ├── index.php └── index_dev.php ├── src └── Slx │ ├── Infrastructure │ ├── Resources │ │ └── Translations │ │ │ └── en.yml │ ├── Persistence │ │ └── Doctrine │ │ │ ├── Mapping │ │ │ ├── ValueObject │ │ │ │ ├── Email.Email.orm.yml │ │ │ │ ├── Task.TaskId.orm.yml │ │ │ │ ├── User.UserId.orm.yml │ │ │ │ └── Password.Password.orm.yml │ │ │ └── Entity │ │ │ │ ├── User.User.orm.yml │ │ │ │ └── Task.Task.orm.yml │ │ │ └── Repository │ │ │ ├── AbstractEntityRepository.php │ │ │ ├── User │ │ │ └── UserDoctrineRepository.php │ │ │ └── Task │ │ │ └── TaskDoctrineRepository.php │ ├── Service │ │ ├── Mail │ │ │ ├── EmailNoSendedException.php │ │ │ ├── ConfigFileDoesNotExistException.php │ │ │ └── Mailer.php │ │ ├── User │ │ │ ├── PasswordHashingService.php │ │ │ └── AuthenticateUserService.php │ │ └── service.php │ ├── middleware.php │ └── app.php │ ├── UserInterface │ ├── Twig │ │ └── Views │ │ │ └── templates │ │ │ ├── emails │ │ │ └── user │ │ │ │ ├── newtask.html.twig │ │ │ │ └── register.html.twig │ │ │ ├── errors │ │ │ ├── 404.html.twig │ │ │ ├── 500.html.twig │ │ │ ├── default.html.twig │ │ │ ├── 4xx.html.twig │ │ │ └── 5xx.html.twig │ │ │ ├── index.html.twig │ │ │ ├── views │ │ │ ├── home │ │ │ │ └── home.html.twig │ │ │ ├── user │ │ │ │ ├── login.html.twig │ │ │ │ └── signup.html.twig │ │ │ └── task │ │ │ │ ├── list.html.twig │ │ │ │ └── create.html.twig │ │ │ └── layout.html.twig │ ├── Controllers │ │ ├── Task │ │ │ ├── RemoveTaskController.php │ │ │ ├── ListTaskController.php │ │ │ └── CreateTaskController.php │ │ ├── User │ │ │ ├── SignOutController.php │ │ │ ├── SignUpController.php │ │ │ └── SignInController.php │ │ ├── Home │ │ │ └── HomeController.php │ │ └── controllers.php │ ├── Form │ │ └── form.php │ └── Console │ │ └── console.php │ ├── Domain │ ├── Entity │ │ ├── User │ │ │ ├── UserSessionNotFoundException.php │ │ │ ├── Exception │ │ │ │ ├── UserAlreadyExistsException.php │ │ │ │ ├── UserDoesNotExistsException.php │ │ │ │ └── UserPasswordDoesNotMatchException.php │ │ │ ├── UserRepositoryInterface.php │ │ │ └── User.php │ │ ├── Task │ │ │ ├── Exception │ │ │ │ ├── TaskNotFoundException.php │ │ │ │ └── TaskStatusDoesNotExistsException.php │ │ │ ├── TaskRepositoryInterface.php │ │ │ └── Task.php │ │ ├── EntityRepository.php │ │ └── Email │ │ │ └── EmailTemplateInterface.php │ ├── ValueObject │ │ ├── Email │ │ │ ├── EmailNotWellFormedException.php │ │ │ └── Email.php │ │ ├── Password │ │ │ ├── PasswordIsNotValidException.php │ │ │ └── Password.php │ │ ├── User │ │ │ └── UserId.php │ │ └── Task │ │ │ └── TaskId.php │ ├── EventListener │ │ ├── Listener.php │ │ ├── LogNewUserOnUserRegistered.php │ │ ├── SendWelcomeEmailOnUserRegistered.php │ │ └── SendNoticeEmailOnTaskCreated.php │ ├── Event │ │ ├── DomainEvent.php │ │ ├── DomainEventDispatcher.php │ │ ├── User │ │ │ └── UserRegistered.php │ │ └── Task │ │ │ └── TaskWasCreated.php │ └── Service │ │ └── User │ │ ├── UserAuthentifierService.php │ │ └── PasswordHashingService.php │ └── Application │ ├── CommandHandler │ ├── CommandHandlerNotFoundException.php │ ├── CommandHandlerInterface.php │ ├── CommandHandler.php │ ├── Task │ │ ├── RemoveTaskCommandHandler.php │ │ └── CreateTaskCommandHandler.php │ └── User │ │ ├── SignInUserCommandHandler.php │ │ └── SignUpUserCommandHandler.php │ ├── Command │ ├── CommandInterface.php │ ├── Task │ │ ├── RemoveTaskCommand.php │ │ └── CreateTaskCommand.php │ └── User │ │ ├── SignInUserCommand.php │ │ └── SignUpUserCommand.php │ ├── UseCase │ └── User │ │ └── SignOutUserUseCase.php │ └── EmailTemplate │ └── User │ ├── TaskCreatedUserEmail.php │ └── RegisterUserEmail.php ├── config ├── mailconfig.yml.dist ├── config.ini.dist ├── prod.php └── dev.php ├── .gitignore ├── foreground.sh ├── Dockerfile ├── bin └── console ├── docker-compose.yml ├── tests └── Application │ ├── Command │ └── User │ │ ├── SignUpUserCommandTest.php │ │ └── SignInUserCommandTest.php │ └── CommandHandler │ ├── CommandHandlerTest.php │ └── User │ ├── SignUpUserCommandHandlerTest.php │ └── SignInUserCommandHandlerTest.php ├── phpunit.xml.dist ├── LICENSE.md ├── composer.json └── README.md /web/css/main.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Slx/Infrastructure/Resources/Translations/en.yml: -------------------------------------------------------------------------------- 1 | Email: Email 2 | Password: Password 3 | Username: Username 4 | -------------------------------------------------------------------------------- /config/mailconfig.yml.dist: -------------------------------------------------------------------------------- 1 | mail: 2 | username: user@gmail.com 3 | password: yourpassword 4 | host: smtpserver 5 | -------------------------------------------------------------------------------- /src/Slx/UserInterface/Twig/Views/templates/emails/user/newtask.html.twig: -------------------------------------------------------------------------------- 1 |

New task was assignet to you {{ taskTitle }}

-------------------------------------------------------------------------------- /src/Slx/UserInterface/Twig/Views/templates/emails/user/register.html.twig: -------------------------------------------------------------------------------- 1 |

Welcome to silex skeleton app! {{ username }}

2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | !.gitignore 3 | .idea/ 4 | var/ 5 | environment.env 6 | config/config.ini 7 | mailconfig.yml 8 | phpunit.xml 9 | .mysql-data -------------------------------------------------------------------------------- /config/config.ini.dist: -------------------------------------------------------------------------------- 1 | [database] 2 | driver = pdo_mysql 3 | charset = utf8 4 | host = db 5 | dbname = dbname 6 | user = user 7 | password = user 8 | -------------------------------------------------------------------------------- /src/Slx/UserInterface/Twig/Views/templates/errors/404.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "layout.html.twig" %} 2 | 3 | {% block content %} 4 | Page not found. 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /src/Slx/UserInterface/Twig/Views/templates/errors/500.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "layout.html.twig" %} 2 | 3 | {% block content %} 4 | Internal server error. 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /src/Slx/UserInterface/Twig/Views/templates/errors/default.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "layout.html.twig" %} 2 | 3 | {% block content %} 4 | An error occurred. 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /src/Slx/Infrastructure/Persistence/Doctrine/Mapping/ValueObject/Email.Email.orm.yml: -------------------------------------------------------------------------------- 1 | Slx\Domain\ValueObject\Email\Email: 2 | type: embeddable 3 | fields: 4 | email: 5 | type: string -------------------------------------------------------------------------------- /src/Slx/Infrastructure/Persistence/Doctrine/Mapping/ValueObject/Task.TaskId.orm.yml: -------------------------------------------------------------------------------- 1 | Slx\Domain\ValueObject\Task\TaskId: 2 | type: embeddable 3 | id: 4 | id: 5 | type: string 6 | -------------------------------------------------------------------------------- /src/Slx/Infrastructure/Persistence/Doctrine/Mapping/ValueObject/User.UserId.orm.yml: -------------------------------------------------------------------------------- 1 | Slx\Domain\ValueObject\User\UserId: 2 | type: embeddable 3 | id: 4 | id: 5 | type: string 6 | -------------------------------------------------------------------------------- /src/Slx/UserInterface/Twig/Views/templates/errors/4xx.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "layout.html.twig" %} 2 | 3 | {% block content %} 4 | An error occurred on the client. 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /src/Slx/UserInterface/Twig/Views/templates/errors/5xx.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "layout.html.twig" %} 2 | 3 | {% block content %} 4 | An error occurred on the server. 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /src/Slx/Infrastructure/Persistence/Doctrine/Mapping/ValueObject/Password.Password.orm.yml: -------------------------------------------------------------------------------- 1 | Slx\Domain\ValueObject\Password\Password: 2 | type: embeddable 3 | fields: 4 | password: 5 | type: string 6 | -------------------------------------------------------------------------------- /config/prod.php: -------------------------------------------------------------------------------- 1 | __DIR__.'/../var/cache/twig'); 7 | -------------------------------------------------------------------------------- /foreground.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | read pid cmd state ppid pgrp session tty_nr tpgid rest < /proc/self/stat 4 | trap "kill -TERM -$pgrp; exit" EXIT TERM KILL SIGKILL SIGTERM SIGQUIT 5 | 6 | source /etc/apache2/envvars 7 | tail -F /var/log/apache2/* & 8 | exec apache2 -D FOREGROUND -------------------------------------------------------------------------------- /src/Slx/Domain/Entity/User/UserSessionNotFoundException.php: -------------------------------------------------------------------------------- 1 | 5 |
6 |

Welcome to your new Silex Application!

7 | Log in 8 |
9 | 10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /src/Slx/Application/Command/CommandInterface.php: -------------------------------------------------------------------------------- 1 | 5 |
6 |

Home

7 |

Welcome back {{ user.username }}

8 | Log out 9 |
10 | 11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /src/Slx/Domain/EventListener/Listener.php: -------------------------------------------------------------------------------- 1 | register(new MonologServiceProvider(), array( 13 | 'monolog.logfile' => __DIR__.'/../var/logs/silex_dev.log', 14 | )); 15 | 16 | $app->register(new WebProfilerServiceProvider(), array( 17 | 'profiler.cache_dir' => __DIR__.'/../var/cache/profiler', 18 | )); 19 | -------------------------------------------------------------------------------- /src/Slx/Domain/Entity/User/UserRepositoryInterface.php: -------------------------------------------------------------------------------- 1 | getParameterOption(array('--env', '-e'), getenv('SYMFONY_ENV') ?: 'dev'); 12 | 13 | $app = require __DIR__ . '/../src/Slx/Infrastructure/app.php'; 14 | require __DIR__ . '/../config/' . $env . '.php'; 15 | require __DIR__ . '/../src/Slx/Infrastructure/middleware.php'; 16 | $console = require __DIR__ . '/../src/Slx/UserInterface/Console/console.php'; 17 | $console->run(); 18 | -------------------------------------------------------------------------------- /web/index.php: -------------------------------------------------------------------------------- 1 | run(); 17 | -------------------------------------------------------------------------------- /src/Slx/Domain/Entity/Task/TaskRepositoryInterface.php: -------------------------------------------------------------------------------- 1 | assertEquals($email, $signInUserCommand->email()); 27 | $this->assertEquals($password, $signInUserCommand->password()); 28 | } 29 | } -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 16 | 17 | 18 | src/ 19 | tests/ 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Slx/Application/UseCase/User/SignOutUserUseCase.php: -------------------------------------------------------------------------------- 1 | application = $application; 30 | } 31 | 32 | /** 33 | * Remove session use case 34 | */ 35 | public function execute() 36 | { 37 | $this->application['session']->clear(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/Application/Command/User/SignInUserCommandTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($username, $signInUserCommand->username()); 28 | $this->assertEquals($email, $signInUserCommand->email()); 29 | $this->assertEquals($password, $signInUserCommand->password()); 30 | } 31 | } -------------------------------------------------------------------------------- /src/Slx/Application/Command/Task/RemoveTaskCommand.php: -------------------------------------------------------------------------------- 1 | taskId = $taskId; 29 | } 30 | 31 | /** 32 | * @return string 33 | */ 34 | public function taskId() 35 | { 36 | return $this->taskId; 37 | } 38 | 39 | /** 40 | * @return string 41 | */ 42 | public function commandHandler(): string 43 | { 44 | return 'removetask.service'; 45 | } 46 | } -------------------------------------------------------------------------------- /src/Slx/Domain/EventListener/LogNewUserOnUserRegistered.php: -------------------------------------------------------------------------------- 1 | monolog = $monolog; 28 | } 29 | 30 | /** 31 | * Deal with domain event 32 | * 33 | * @param DomainEvent $domainEvent 34 | * 35 | * @return mixed 36 | */ 37 | public function handle(DomainEvent $domainEvent) 38 | { 39 | $this->monolog->info('New user was registered: ' . $domainEvent->userEmail()->email()); 40 | } 41 | } -------------------------------------------------------------------------------- /src/Slx/UserInterface/Controllers/Task/RemoveTaskController.php: -------------------------------------------------------------------------------- 1 | application = $application; 24 | } 25 | 26 | public function indexAction() 27 | { 28 | $request = $this->application['request_stack']->getCurrentRequest(); 29 | $this->application['removetask.service']->execute(new RemoveTaskCommand( 30 | $request->get('task') 31 | )); 32 | 33 | return $this->application->redirect($this->application['url_generator']->generate('listtask')); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Slx/Infrastructure/Persistence/Doctrine/Mapping/Entity/User.User.orm.yml: -------------------------------------------------------------------------------- 1 | Slx\Domain\Entity\User\User: 2 | type: entity 3 | repositoryClass: Slx\Infrastructure\Persistence\Doctrine\Repository\User\UserDoctrineRepository 4 | table: user 5 | options: 6 | charset: utf8 7 | collate: utf8_general_ci 8 | embedded: 9 | password: 10 | class: Slx\Domain\ValueObject\Password\Password 11 | columnPrefix: false 12 | email: 13 | class: Slx\Domain\ValueObject\Email\Email 14 | columnPrefix: false 15 | uid: 16 | class: Slx\Domain\ValueObject\User\UserId 17 | columnPrefix: false 18 | oneToMany: 19 | tasks: 20 | targetEntity: Slx\Domain\Entity\Task\Task 21 | mappedBy: userAssigned 22 | fields: 23 | username: 24 | column: userName 25 | type: string 26 | nullable: false 27 | createdOn: 28 | type: datetime 29 | nullable: false 30 | updatedOn: 31 | type: datetime 32 | nullable: false 33 | -------------------------------------------------------------------------------- /src/Slx/Domain/ValueObject/User/UserId.php: -------------------------------------------------------------------------------- 1 | id = Uuid::uuid4()->toString(); 26 | } 27 | 28 | /** 29 | * Generate user id 30 | * 31 | * @return UserId 32 | */ 33 | public static function generateUserId(): UserId 34 | { 35 | return new self(); 36 | } 37 | 38 | /** 39 | * @return string 40 | */ 41 | public function id() 42 | { 43 | return $this->id; 44 | } 45 | 46 | /** 47 | * @return string 48 | */ 49 | public function __toString(): string 50 | { 51 | return $this->id; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Slx/Infrastructure/Service/User/PasswordHashingService.php: -------------------------------------------------------------------------------- 1 | password()); 25 | } 26 | 27 | /** 28 | * @param Password $userPassword 29 | * @param string $passwordToVerify 30 | * 31 | * @return bool 32 | */ 33 | public function verifyPassword(Password $userPassword, string $passwordToVerify) 34 | { 35 | return (sha1($passwordToVerify) == $userPassword->password()); 36 | } 37 | } -------------------------------------------------------------------------------- /src/Slx/Domain/ValueObject/Task/TaskId.php: -------------------------------------------------------------------------------- 1 | id = Uuid::uuid4()->toString(); 26 | } 27 | 28 | /** 29 | * Generate user id 30 | * 31 | * @return TaskId 32 | */ 33 | public static function generate(): TaskId 34 | { 35 | return new self(); 36 | } 37 | 38 | /** 39 | * @param TaskId $taskId 40 | * @return bool 41 | */ 42 | public function equals(TaskId $taskId) 43 | { 44 | return ($this->id === $taskId->id); 45 | } 46 | 47 | /** 48 | * @return string 49 | */ 50 | public function __toString() 51 | { 52 | return $this->id; 53 | } 54 | } -------------------------------------------------------------------------------- /src/Slx/UserInterface/Controllers/Task/ListTaskController.php: -------------------------------------------------------------------------------- 1 | application = $application; 26 | } 27 | 28 | public function indexAction() 29 | { 30 | $userFromSession = $this->application['session']->get('user'); 31 | $user = $this->application['user_repository']->findOneBy(['username' => $userFromSession['username']]); 32 | 33 | return $this->application['twig']->render('views/task/list.html.twig', 34 | [ 35 | 'tasks' => $this->application['task_repository']->fetchAvailable($user->id()), 36 | ] 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Slx/UserInterface/Controllers/User/SignOutController.php: -------------------------------------------------------------------------------- 1 | application = $application; 29 | } 30 | 31 | /** 32 | * @return \Symfony\Component\HttpFoundation\RedirectResponse 33 | */ 34 | public function indexAction() 35 | { 36 | (new AuthenticateUserService($this->application['session']))->removeSession(); 37 | 38 | return $this->application->redirect($this->application['url_generator']->generate('signin')); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Slx/Infrastructure/Persistence/Doctrine/Repository/AbstractEntityRepository.php: -------------------------------------------------------------------------------- 1 | getEntityManager()->persist($entity); 34 | $this->getEntityManager()->flush($entity); 35 | } 36 | 37 | /** 38 | * @param array $options 39 | * 40 | * @return mixed 41 | */ 42 | public function fetchBy(array $options) 43 | { 44 | return parent::findBy($options); 45 | } 46 | } -------------------------------------------------------------------------------- /src/Slx/UserInterface/Controllers/Home/HomeController.php: -------------------------------------------------------------------------------- 1 | application = $application; 29 | } 30 | 31 | /** 32 | * @return mixed 33 | */ 34 | public function indexAction() 35 | { 36 | if (null === $user = $this->application['session']->get('user')) { 37 | return $this->application->redirect($this->application['url_generator']->generate('signin')); 38 | } 39 | 40 | return $this->application['twig']->render('views/home/home.html.twig', [ 41 | 'user' => $user 42 | ]); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Slx/Infrastructure/Persistence/Doctrine/Mapping/Entity/Task.Task.orm.yml: -------------------------------------------------------------------------------- 1 | Slx\Domain\Entity\Task\Task: 2 | type: entity 3 | repositoryClass: Slx\Infrastructure\Persistence\Doctrine\Repository\Task\TaskDoctrineRepository 4 | table: task 5 | options: 6 | charset: utf8 7 | collate: utf8_general_ci 8 | embedded: 9 | id: 10 | class: Slx\Domain\ValueObject\Task\TaskId 11 | columnPrefix: false 12 | manyToOne: 13 | userAssigned: 14 | targetEntity: Slx\Domain\Entity\User\User 15 | inversedBy: tasks 16 | joinColumn: 17 | name: user_id 18 | referencedColumnName: id 19 | fields: 20 | title: 21 | type: string 22 | length: 50 23 | nullable: false 24 | status: 25 | type: string 26 | length: 25 27 | nullable: false 28 | description: 29 | type: string 30 | length: 250 31 | nullable: false 32 | createdOn: 33 | column: created_on 34 | type: datetime 35 | nullable: false 36 | updatedOn: 37 | column: updated_on 38 | type: datetime 39 | nullable: false 40 | -------------------------------------------------------------------------------- /src/Slx/Infrastructure/Persistence/Doctrine/Repository/User/UserDoctrineRepository.php: -------------------------------------------------------------------------------- 1 | getEntityManager()->persist($user); 25 | $this->getEntityManager()->flush(); 26 | } 27 | 28 | /** 29 | * @param $email 30 | * 31 | * @return array 32 | */ 33 | public function fetchByEmail($email) 34 | { 35 | return $this->getEntityManager()->getRepository(User::class)->findOneBy(['email.email' => $email]); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2014 Fabien Potencier 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/Slx/Application/CommandHandler/CommandHandler.php: -------------------------------------------------------------------------------- 1 | application = $application; 28 | } 29 | 30 | /** 31 | * 32 | * @param CommandInterface $command 33 | * @return mixed 34 | * @throws CommandHandlerNotFoundException 35 | */ 36 | public function execute(CommandInterface $command) 37 | { 38 | if (!isset($this->application[$command->commandHandler()])) { 39 | throw new CommandHandlerNotFoundException(); 40 | } 41 | return $this->application[$command->commandHandler()]->execute($command); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Slx/Domain/EventListener/SendWelcomeEmailOnUserRegistered.php: -------------------------------------------------------------------------------- 1 | mailer = $mailer; 31 | } 32 | 33 | /** 34 | * Deal with domain event 35 | * 36 | * @param DomainEvent $domainEvent 37 | * 38 | * @return mixed 39 | */ 40 | public function handle(DomainEvent $domainEvent) 41 | { 42 | $this->mailer->send( 43 | new RegisterUserEmail( 44 | $domainEvent->userEmail()->email(), 45 | $domainEvent->username() 46 | ) 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Slx/Infrastructure/middleware.php: -------------------------------------------------------------------------------- 1 | get('user'))) { 15 | throw new UserSessionNotFoundException(); 16 | } 17 | }; 18 | $app->before(function (Request $request, Application $app) { 19 | DomainEventDispatcher::instance()->addListener(UserRegistered::EVENT_NAME, new SendWelcomeEmailOnUserRegistered($app['mailer.service'])); 20 | DomainEventDispatcher::instance()->addListener(UserRegistered::EVENT_NAME, new LogNewUserOnUserRegistered($app['monolog'])); 21 | DomainEventDispatcher::instance()->addListener(TaskWasCreated::EVENT_NAME, new \Slx\Domain\EventListener\SendNoticeEmailOnTaskCreated($app['mailer.service'])); 22 | }); 23 | 24 | -------------------------------------------------------------------------------- /web/index_dev.php: -------------------------------------------------------------------------------- 1 | run(); 28 | -------------------------------------------------------------------------------- /src/Slx/Domain/EventListener/SendNoticeEmailOnTaskCreated.php: -------------------------------------------------------------------------------- 1 | mailer = $mailer; 31 | } 32 | 33 | /** 34 | * Deal with domain event 35 | * 36 | * @param DomainEvent $domainEvent 37 | * 38 | * @return mixed 39 | */ 40 | public function handle(DomainEvent $domainEvent) 41 | { 42 | $this->mailer->send( 43 | new TaskCreatedUserEmail( 44 | $domainEvent->taskTitle(), 45 | $domainEvent->taskDescription(), 46 | $domainEvent->userAssigned() 47 | ) 48 | ); 49 | } 50 | } -------------------------------------------------------------------------------- /src/Slx/Application/Command/User/SignInUserCommand.php: -------------------------------------------------------------------------------- 1 | email = $email; 36 | $this->password = $password; 37 | } 38 | 39 | /** 40 | * @return mixed 41 | */ 42 | public function email() 43 | { 44 | return $this->email; 45 | } 46 | 47 | /** 48 | * @return mixed 49 | */ 50 | public function password() 51 | { 52 | return $this->password; 53 | } 54 | 55 | /** 56 | * @return string 57 | */ 58 | public function commandHandler(): string 59 | { 60 | return 'signin.service'; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Slx/Infrastructure/Service/User/AuthenticateUserService.php: -------------------------------------------------------------------------------- 1 | session = $session; 30 | } 31 | 32 | /** 33 | * Authenticate an user and store session. 34 | * 35 | * @param User $user 36 | * 37 | * @return mixed 38 | */ 39 | public function authenticate(User $user) 40 | { 41 | $this->session->start(); 42 | $this->session->set('user', ['username' => $user->username()]); 43 | } 44 | 45 | /** 46 | * Remove session 47 | * 48 | * @return mixed 49 | */ 50 | public function removeSession() 51 | { 52 | $this->session->clear(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Slx/UserInterface/Twig/Views/templates/views/user/login.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "layout.html.twig" %} 2 | 3 | {% block content %} 4 |
5 |

Sign in

6 | {{ form_start(form, {'method': 'POST'}) }} 7 | {{ form_errors(form) }} 8 | 9 |
10 | {{ form_label(form.email, null, { 'label_attr' : { 'class' : 'col-2 col-form-label'}}) }} 11 |
12 | {{ form_widget(form.email) }} 13 | {{ form_errors(form.email) }} 14 |
15 |
16 | 17 |
18 | {{ form_label(form.password, null, { 'label_attr' : { 'class' : 'col-2 col-form-label'}}) }} 19 |
20 | {{ form_widget(form.password) }} 21 | {{ form_errors(form.password) }} 22 |
23 |
24 | 25 |
26 |
27 | 28 |
29 |
30 | 31 | Register!! 32 | 33 | {{ form_end(form) }} 34 |
35 | {% endblock %} 36 | -------------------------------------------------------------------------------- /src/Slx/Application/CommandHandler/Task/RemoveTaskCommandHandler.php: -------------------------------------------------------------------------------- 1 | taskRepository = $taskRepository; 28 | } 29 | 30 | /** 31 | * Remove task 32 | * 33 | * @param CommandInterface|RemoveTaskCommand $command 34 | * @return mixed 35 | * @throws TaskNotFoundException 36 | */ 37 | public function execute(CommandInterface $command) 38 | { 39 | $taskId = $command->taskId(); 40 | if (null == $task = $this->taskRepository->fetchById($taskId)) { 41 | throw new TaskNotFoundException(); 42 | } 43 | 44 | $task->remove(); 45 | $this->taskRepository->save($task); 46 | } 47 | } -------------------------------------------------------------------------------- /src/Slx/Infrastructure/Persistence/Doctrine/Repository/Task/TaskDoctrineRepository.php: -------------------------------------------------------------------------------- 1 | getEntityManager()->persist($task); 26 | $this->getEntityManager()->flush(); 27 | } 28 | 29 | /** 30 | * @param $taskId 31 | * @return mixed 32 | */ 33 | public function fetchById($taskId) 34 | { 35 | return $this->getEntityManager()->find(Task::class, $taskId); 36 | } 37 | 38 | /** 39 | * Fetch only available tasks given user 40 | * 41 | * @param UserId $userId 42 | * @return mixed 43 | */ 44 | public function fetchAvailable(UserId $userId) 45 | { 46 | return $this->fetchBy([ 47 | 'userAssigned' => $userId->id(), 48 | 'status' => Task::OPEN 49 | ]); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Slx/Application/EmailTemplate/User/TaskCreatedUserEmail.php: -------------------------------------------------------------------------------- 1 | title = $title; 33 | $this->description = $description; 34 | $this->user = $user; 35 | } 36 | 37 | /** 38 | * Path to email template 39 | * 40 | * @return string 41 | */ 42 | public function templatePath(): string 43 | { 44 | return 'emails/user/newtask.html.twig'; 45 | } 46 | 47 | /** 48 | * Parameters to twig templateemail 49 | * 50 | * @return array 51 | */ 52 | public function parameters(): array 53 | { 54 | return [ 55 | 'taskTitle' => $this->title, 56 | ]; 57 | } 58 | 59 | /** 60 | * @return string 61 | */ 62 | public function emailTo(): string 63 | { 64 | return $this->user->email(); 65 | } 66 | } -------------------------------------------------------------------------------- /src/Slx/UserInterface/Controllers/controllers.php: -------------------------------------------------------------------------------- 1 | get('/', function () use ($app) { 12 | return $app['twig']->render('index.html.twig', array()); 13 | })->bind('homepage'); 14 | 15 | $app->match('/signin', "signin.controller:indexAction")->bind('signin'); 16 | $app->match('/signup', 'signup.controller:indexAction')->bind('signup'); 17 | $app->match('/signout', 'signout.controller:indexAction')->bind('signout')->before($isUserLoggedCallback); 18 | $app->get('/home', 'home.controller:indexAction')->bind('home')->before($isUserLoggedCallback); 19 | $app->match('/task/add', 'createtask.controller:indexAction')->bind('createtask')->before($isUserLoggedCallback); 20 | $app->get('/task', 'listtask.controller:indexAction')->bind('listtask')->before($isUserLoggedCallback); 21 | $app->delete('/task/delete', 'removetask.controller:indexAction')->bind('removetask')->before($isUserLoggedCallback); 22 | 23 | 24 | $app->error(function (\Exception $e, Request $request, $code) use ($app) { 25 | if ($app['debug']) { 26 | return; 27 | } 28 | 29 | switch ($code) { 30 | case 500: 31 | return new Response($app['twig']->render('errors/5xx.html.twig'), 404); 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /src/Slx/Domain/Event/DomainEventDispatcher.php: -------------------------------------------------------------------------------- 1 | listeners = []; 32 | } 33 | 34 | /** 35 | * @return DomainEventDispatcher|static 36 | */ 37 | public static function instance() 38 | { 39 | if (null === static::$selfInstance) { 40 | static::$selfInstance = new self(); 41 | } 42 | 43 | return static::$selfInstance; 44 | } 45 | 46 | /** 47 | * Add listener 48 | * 49 | * @param string $domainEventName 50 | * @param Listener $listener 51 | */ 52 | public function addListener(string $domainEventName, Listener $listener) 53 | { 54 | $this->listeners[$domainEventName][] = $listener; 55 | } 56 | 57 | /** 58 | * Dispatch a domain event 59 | * 60 | * @param DomainEvent $event 61 | */ 62 | public function dispatch(DomainEvent $event) 63 | { 64 | foreach ($this->listeners[$event->eventName()] as $listener) { 65 | $listener->handle($event); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /src/Slx/Application/Command/User/SignUpUserCommand.php: -------------------------------------------------------------------------------- 1 | email = $email; 40 | $this->password = $password; 41 | $this->username = $userName; 42 | } 43 | 44 | /** 45 | * @return string 46 | */ 47 | public function email() 48 | { 49 | return $this->email; 50 | } 51 | 52 | /** 53 | * @return string 54 | */ 55 | public function password() 56 | { 57 | return $this->password; 58 | } 59 | 60 | /** 61 | * @return string 62 | */ 63 | public function username() 64 | { 65 | return $this->username; 66 | } 67 | 68 | /** 69 | * @return string 70 | */ 71 | public function commandHandler(): string 72 | { 73 | return 'signup.service'; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Slx/Application/EmailTemplate/User/RegisterUserEmail.php: -------------------------------------------------------------------------------- 1 | userEmail = $userEmail; 34 | $this->username = $username; 35 | } 36 | 37 | /** 38 | * Path to email template 39 | * 40 | * @return string 41 | */ 42 | public function templatePath(): string 43 | { 44 | return 'emails/user/register.html.twig'; 45 | } 46 | 47 | /** 48 | * Parameters to twig template email 49 | * 50 | * @return array 51 | */ 52 | public function parameters(): array 53 | { 54 | return [ 55 | 'email' => $this->userEmail, 56 | 'username' => $this->username, 57 | ]; 58 | } 59 | 60 | /** 61 | * User email to send email 62 | * 63 | * @return string 64 | */ 65 | public function emailTo(): string 66 | { 67 | return $this->userEmail; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fabpot/silex-skeleton", 3 | "description": "A pre-configured skeleton for the Silex microframework", 4 | "license": "MIT", 5 | "type": "project", 6 | "require": { 7 | "php": ">=5.5.9", 8 | "silex/silex": "~2.0", 9 | "silex/web-profiler": "~2.0", 10 | "symfony/asset": "~2.8|3.0.*", 11 | "symfony/browser-kit": "~2.8|3.0.*", 12 | "symfony/class-loader": "~2.8|3.0.*", 13 | "symfony/config": "^3.2", 14 | "symfony/console": "~2.8|3.0.*", 15 | "symfony/css-selector": "~2.8|3.0.*", 16 | "symfony/debug": "~2.8|3.0.*", 17 | "symfony/finder": "~2.8|3.0.*", 18 | "symfony/form": "^3.2", 19 | "symfony/monolog-bridge": "~2.8|3.0.*", 20 | "symfony/process": "~2.8|3.0.*", 21 | "symfony/security": "~2.8|3.0.*", 22 | "symfony/translation": "^3.2", 23 | "symfony/twig-bridge": "^3.2", 24 | "symfony/validator": "^3.2", 25 | "ramsey/uuid": "^3.4", 26 | "symfony/yaml": "^3.2", 27 | "doctrine/orm": "^2.0", 28 | "dflydev/doctrine-orm-service-provider": "2.0.1", 29 | "monolog/monolog": "^1.22", 30 | "phpmailer/phpmailer": "^5.2" 31 | }, 32 | "require-dev": { 33 | "phpunit/phpunit": "^5.0" 34 | }, 35 | "autoload": { 36 | "psr-4": { 37 | "": "src/" 38 | }, 39 | "psr-0": { 40 | "Test\\": "tests/" 41 | } 42 | }, 43 | "extra": { 44 | "branch-alias": { 45 | "dev-master": "2.0.x-dev" 46 | } 47 | }, 48 | "scripts": { 49 | "run": [ 50 | "echo 'Started web server on http://localhost:8888'", 51 | "php -S localhost:8888 -t web" 52 | ] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Slx/Application/Command/Task/CreateTaskCommand.php: -------------------------------------------------------------------------------- 1 | title = $title; 42 | $this->description = $description; 43 | $this->user = $user; 44 | } 45 | 46 | /** 47 | * @return string 48 | */ 49 | public function title(): string 50 | { 51 | return $this->title; 52 | } 53 | 54 | /** 55 | * @return string 56 | */ 57 | public function description(): string 58 | { 59 | return $this->description; 60 | } 61 | 62 | /** 63 | * @return string 64 | */ 65 | public function user(): string 66 | { 67 | return $this->user; 68 | } 69 | /** 70 | * @return string 71 | */ 72 | public function commandHandler(): string 73 | { 74 | return 'createtask.service'; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Slx/UserInterface/Twig/Views/templates/views/task/list.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "layout.html.twig" %} 2 | 3 | {% block content %} 4 |
5 |

List of tasks

6 |
7 |
8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {% for task in tasks %} 19 | 20 | 21 | 22 | 23 | 30 | 31 | {% endfor %} 32 | 33 |
#Task titleTask description
{{ loop.index }}{{ task.title }}{{ task.description }} 24 |
25 | 26 | 27 | 28 |
29 |
34 |
35 |
36 |
37 | Add task 38 |
39 |
40 | {% endblock %} 41 | -------------------------------------------------------------------------------- /src/Slx/Domain/ValueObject/Password/Password.php: -------------------------------------------------------------------------------- 1 | checkIfPasswordIsValid($pwd); 31 | $this->password = $pwd; 32 | } 33 | 34 | /** 35 | * @param string $pwd 36 | * 37 | * @return Password 38 | */ 39 | public static function fromString(string $pwd): self 40 | { 41 | return new self($pwd); 42 | } 43 | 44 | /** 45 | * Get password. 46 | * 47 | * @return string 48 | */ 49 | public function password(): string 50 | { 51 | return $this->password; 52 | } 53 | 54 | /** 55 | * Check if password is valid 56 | * 57 | * @param $pwd 58 | * @throws PasswordIsNotValidException 59 | */ 60 | private function checkIfPasswordIsValid($pwd) 61 | { 62 | if (strlen($pwd) < self::MIN_LENGTH) { 63 | throw new PasswordIsNotValidException('Password must be at least of 8 characters'); 64 | } 65 | 66 | if (!(preg_match('/^[0-9a-zA-Z]+$/', $pwd))) { 67 | throw new PasswordIsNotValidException('Password must contain at least one number'); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Slx/UserInterface/Form/form.php: -------------------------------------------------------------------------------- 1 | createBuilder(FormType::class, [ 12 | 'password' => '', 13 | 'email' => '', 14 | ]) 15 | ->add('email', TextType::class, [ 16 | 'constraints' => [ 17 | new Assert\NotBlank(), 18 | new Assert\Email(), 19 | new Assert\Length(['min' => Email::MIN_LENGTH, 'max' => Email::MAX_LENGTH])] 20 | ]) 21 | ->add('password', PasswordType::class, [ 22 | 'constraints' => [ 23 | new Assert\NotBlank(), 24 | ] 25 | ]) 26 | ->getForm(); 27 | 28 | $app['sign_up_form'] = $app['form.factory']->createBuilder(FormType::class, [ 29 | 'username' => '', 30 | 'password' => '', 31 | 'email' => '', 32 | ]) 33 | ->add('username', TextType::class, [ 34 | 'constraints' => [ 35 | new Assert\NotBlank(), 36 | ] 37 | ]) 38 | ->add('email', TextType::class, [ 39 | 'constraints' => [ 40 | new Assert\NotBlank(), 41 | new Assert\Email(), 42 | new Assert\Length(['min' => Email::MIN_LENGTH, 'max' => Email::MAX_LENGTH])] 43 | ]) 44 | ->add('password', PasswordType::class, [ 45 | 'constraints' => [ 46 | new Assert\NotBlank(), 47 | ] 48 | ]) 49 | ->getForm(); 50 | 51 | -------------------------------------------------------------------------------- /src/Slx/UserInterface/Twig/Views/templates/views/task/create.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "layout.html.twig" %} 2 | 3 | {% block content %} 4 |
5 |

Create new Task

6 | {{ form_start(form, {'method': 'POST'}) }} 7 | {{ form_errors(form) }} 8 | 9 |
10 | {{ form_label(form.title, null, { 'label_attr' : { 'class' : 'col-2 col-form-label'}}) }} 11 |
12 | {{ form_widget(form.title) }} 13 | {{ form_errors(form.title) }} 14 |
15 |
16 | 17 |
18 | {{ form_label(form.description, null, { 'label_attr' : { 'class' : 'col-2 col-form-label'}}) }} 19 |
20 | {{ form_widget(form.description) }} 21 | {{ form_errors(form.description) }} 22 |
23 |
24 | 25 |
26 | {{ form_label(form.users, null, { 'label_attr' : { 'class' : 'col-2 col-form-label'}}) }} 27 |
28 | {{ form_widget(form.users) }} 29 | {{ form_errors(form.users) }} 30 |
31 |
32 | 33 |
34 |
35 | 36 |
37 |
38 | 39 | {{ form_end(form) }} 40 |
41 | {% endblock %} 42 | -------------------------------------------------------------------------------- /src/Slx/Domain/ValueObject/Email/Email.php: -------------------------------------------------------------------------------- 1 | setEmail($email); 28 | } 29 | 30 | /** 31 | * Named constructor to build an email from string 32 | * 33 | * @param string $email 34 | * 35 | * @return Email 36 | */ 37 | public static function fromString(string $email) 38 | { 39 | return new self($email); 40 | } 41 | 42 | /** 43 | * Get email string. 44 | * 45 | * @return string 46 | */ 47 | public function email(): string 48 | { 49 | return $this->email; 50 | } 51 | 52 | /** 53 | * Email setter 54 | * 55 | * @param string $email 56 | */ 57 | public function setEmail(string $email) 58 | { 59 | $this->checkValidEmail($email); 60 | $this->email = strtolower($email); 61 | } 62 | 63 | /** 64 | * @return string 65 | */ 66 | public function __toString() 67 | { 68 | return $this->email; 69 | } 70 | 71 | /** 72 | * Check if user email is valid 73 | * 74 | * @param string $email 75 | * @throws EmailNotWellFormedException 76 | */ 77 | private function checkValidEmail(string $email) 78 | { 79 | if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { 80 | throw new EmailNotWellFormedException(); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Slx/UserInterface/Twig/Views/templates/views/user/signup.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "layout.html.twig" %} 2 | 3 | {% block content %} 4 |
5 |

Sign up

6 | {{ form_start(form, {'method': 'POST'}) }} 7 | {{ form_errors(form) }} 8 |
9 | {{ form_label(form.username, null, { 'label_attr' : { 'class' : 'col-2 col-form-label'}}) }} 10 |
11 | {{ form_widget(form.username, { 'attr': {'class': 'form-control'} }) }} 12 | {{ form_errors(form.username) }} 13 |
14 |
15 | 16 |
17 | {{ form_label(form.email, null, { 'label_attr' : { 'class' : 'col-2 col-form-label'}}) }} 18 |
19 | {{ form_widget(form.email, { 'attr' : { 'class' : 'form-control'}}) }} 20 | {{ form_errors(form.email) }} 21 |
22 |
23 | 24 |
25 | {{ form_label(form.password, null, { 'label_attr' : { 'class' : 'col-2 col-form-label'}}) }} 26 |
27 | {{ form_widget(form.password, { 'attr' : { 'class' : 'form-control'}}) }} 28 | {{ form_errors(form.password) }} 29 |
30 |
31 | 32 |
33 |
34 | 35 |
36 |
37 | 38 | {{ form_end(form) }} 39 |
40 | 41 | {% endblock %} 42 | -------------------------------------------------------------------------------- /src/Slx/Domain/Event/User/UserRegistered.php: -------------------------------------------------------------------------------- 1 | occurredOn = new \DateTimeImmutable(); 48 | $this->userId = $userId; 49 | $this->userEmail = $email; 50 | $this->username = $username; 51 | } 52 | 53 | /** 54 | * @return Email 55 | */ 56 | public function userEmail(): Email 57 | { 58 | return $this->userEmail; 59 | } 60 | 61 | /** 62 | * Username 63 | * 64 | * @return string 65 | */ 66 | public function username(): string 67 | { 68 | return $this->username; 69 | } 70 | 71 | /** 72 | * When occurred the event 73 | * 74 | * @return mixed 75 | */ 76 | public function occurredOn(): \DateTimeImmutable 77 | { 78 | return $this->occurredOn; 79 | } 80 | 81 | /** 82 | * @return string 83 | */ 84 | public function eventName(): string 85 | { 86 | return self::EVENT_NAME; 87 | } 88 | } -------------------------------------------------------------------------------- /src/Slx/Application/CommandHandler/Task/CreateTaskCommandHandler.php: -------------------------------------------------------------------------------- 1 | userRepository = $userRepository; 41 | $this->taskRepository = $taskRepository; 42 | } 43 | 44 | /** 45 | * 46 | * @param CommandInterface|CreateTaskCommand $command 47 | * @return mixed 48 | * @throws UserDoesNotExistsException 49 | */ 50 | public function execute(CommandInterface $command) 51 | { 52 | /** @var User $user */ 53 | $user = $this->userRepository->find($command->user()); 54 | if (null == $user) { 55 | throw new UserDoesNotExistsException(); 56 | } 57 | $task = Task::build($command->title(), $user, Task::OPEN, $command->description()); 58 | $this->taskRepository->add($task); 59 | } 60 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Silex ddd skeleton 2 | 3 | This is a simple project to improve my DDD skills and learn a little of Silex Php framework. 4 | 5 | ## Getting started 6 | These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. 7 | 8 | ### Prerequisites 9 | You must have installed docker on your local machine to run it easily. 10 | To install [docker](https://docs.docker.com/engine/installation/) and [docker-compose](https://docs.docker.com/compose/install/) 11 | 12 | ### Installing 13 | Open your command line interface and write: 14 | 15 | ```bash 16 | docker-compose up -d 17 | ``` 18 | 19 | This will build the docker image and start needed container in background. 20 | 21 | Download composer packages with the next command: 22 | 23 | ```bash 24 | docker run --rm -v $(pwd):/app -u $(id -u):$(id -g) composer/composer install 25 | ``` 26 | 27 | ## Running the tests 28 | 29 | Use the following command: 30 | 31 | ```bash 32 | vendor/phpunit/phpunit/phpunit 33 | ``` 34 | 35 | ## Another useful Docker commands 36 | 37 | ```bash 38 | # List containers 39 | docker-compose ps 40 | 41 | # View logs 42 | docker-compose logs 43 | 44 | # Restart containers 45 | docker-compose restart 46 | 47 | # Stop containers 48 | docker-compose stop 49 | 50 | # Stop and remove containers. 51 | docker-compose down 52 | 53 | # Start a terminal session for php-apache container 54 | docker-compose exec silexdddskeleton_web_1 bash 55 | 56 | # Execute command into mysql container 57 | docker-compose exec silexdddskeleton_db_1 mysql -uroot -p -e 'COMMAND' 58 | ``` 59 | 60 | ## Built with 61 | * [Silex](http://silex.sensiolabs.org/) - Microframework for PHP 62 | * [Composer](https://getcomposer.org/) - Dependencies management 63 | * [Bootstrap 4](https://v4-alpha.getbootstrap.com/) - Front end framework 64 | 65 | ## Author 66 | Me, Daniel Tomé Fernández 67 | Javi Sabalete 68 | 69 | ## License 70 | This project is licensed under the MIT [License](LICENSE.md) - see the LICENSE.md file for details -------------------------------------------------------------------------------- /src/Slx/Infrastructure/Service/Mail/Mailer.php: -------------------------------------------------------------------------------- 1 | getMailerParameters(); 35 | $this->phpMailer = new PHPMailer; 36 | $this->phpMailer->isSMTP(); 37 | // $this->phpMailer->SMTPDebug = 2; 38 | $this->phpMailer->Debugoutput = 'html'; 39 | $this->phpMailer->Host = $parameters['mail']['host']; 40 | $this->phpMailer->Port = 587; 41 | $this->phpMailer->SMTPSecure = 'tls'; 42 | $this->phpMailer->SMTPAuth = true; 43 | $this->phpMailer->Username = $parameters['mail']['username']; 44 | $this->phpMailer->Password = $parameters['mail']['password']; 45 | $this->phpMailer->setFrom($parameters['mail']['username']); 46 | 47 | $this->twig = $twig; 48 | } 49 | 50 | public function send(EmailTemplateInterface $emailTemplate) 51 | { 52 | $this->phpMailer->addAddress($emailTemplate->emailTo()); 53 | $this->phpMailer->msgHTML($this->twig->render($emailTemplate->templatePath(), $emailTemplate->parameters())); 54 | 55 | if (!$this->phpMailer->send()) { 56 | throw new EmailNoSendedException(); 57 | } 58 | } 59 | 60 | private function getMailerParameters(): array 61 | { 62 | $parser = new YamlParser(); 63 | if (!file_exists($this->configPath)) { 64 | throw new ConfigFileDoesNotExistException(); 65 | } 66 | return $parser->parse(file_get_contents($this->configPath)); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Slx/Application/CommandHandler/User/SignInUserCommandHandler.php: -------------------------------------------------------------------------------- 1 | userRepository = $userRepository; 41 | $this->hashingService = $hashingService; 42 | } 43 | 44 | /** 45 | * Sign in user in web app 46 | * 47 | * @param SignInUserCommand $userRequest 48 | * @return User 49 | * @throws UserDoesNotExistsException 50 | * @throws UserPasswordDoesNotMatchException 51 | */ 52 | public function execute(SignInUserCommand $userRequest): User 53 | { 54 | /** @var User $user */ 55 | $user = $this->userRepository->fetchByEmail($userRequest->email()); 56 | if (null == $user) { 57 | throw new UserDoesNotExistsException(); 58 | } 59 | $isVerified = $this->hashingService->verifyPassword($user->password(), $userRequest->password()); 60 | if (!$isVerified) { 61 | throw new UserPasswordDoesNotMatchException(); 62 | } 63 | 64 | return $user; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Slx/UserInterface/Twig/Views/templates/layout.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% block title '' %} - My Silex Application 5 | 6 | 7 | 9 | 23 | 24 | 25 |
26 | 43 |
44 |
45 | {% block content %}{% endblock %} 46 |
47 |
48 |
49 | 50 | 51 | -------------------------------------------------------------------------------- /src/Slx/UserInterface/Controllers/User/SignUpController.php: -------------------------------------------------------------------------------- 1 | application = $application; 33 | } 34 | 35 | public function indexAction() 36 | { 37 | /** @var Form $form */ 38 | $form = $this->application['sign_up_form']; 39 | $form->handleRequest($this->application['request_stack']->getCurrentRequest()); 40 | try { 41 | if ($form->isValid()) { 42 | $isSignedUp = $this->application['commandhandler.service']->execute( 43 | new SignUpUserCommand( 44 | $form->get('username')->getData(), 45 | $form->get('email')->getData(), 46 | $form->get('password')->getData() 47 | ) 48 | ); 49 | 50 | if ($isSignedUp) { 51 | return $this->application->redirect($this->application['url_generator']->generate('signin')); 52 | } 53 | } 54 | } catch (UserAlreadyExistsException $exception) { 55 | $form->get('email')->addError(new FormError('Email is already registered by another user')); 56 | } catch (PasswordIsNotValidException $passwordIsNotValidException) { 57 | $form->get('password')->addError(new FormError($passwordIsNotValidException->getMessage())); 58 | } 59 | 60 | return $this->application['twig']->render('views/user/signup.html.twig', 61 | [ 62 | 'form' => $this->application['sign_up_form']->createView() 63 | ] 64 | ); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/Slx/UserInterface/Controllers/User/SignInController.php: -------------------------------------------------------------------------------- 1 | application = $application; 35 | } 36 | 37 | /** 38 | * @return mixed 39 | */ 40 | public function indexAction() 41 | { 42 | /** @var Form $form */ 43 | $form = $this->application['sign_in_form']; 44 | $form->handleRequest($this->application['request_stack']->getCurrentRequest()); 45 | 46 | try { 47 | if ($form->isValid()) { 48 | $user = $this->application['commandhandler.service']->execute( 49 | new SignInUserCommand( 50 | $form->get('email')->getData(), 51 | $form->get('password')->getData() 52 | ) 53 | ); 54 | if (null != $user) { 55 | (new AuthenticateUserService($this->application['session']))->authenticate($user); 56 | return $this->application->redirect($this->application['url_generator']->generate('home')); 57 | } 58 | } 59 | } catch (UserPasswordDoesNotMatchException $passwordDoesNotMatchException) { 60 | $form->get('password')->addError(new FormError('Password does not match')); 61 | } 62 | 63 | return $this->application['twig']->render('views/user/login.html.twig', 64 | [ 65 | 'form' => $this->application['sign_in_form']->createView() 66 | ] 67 | ); 68 | } 69 | } -------------------------------------------------------------------------------- /src/Slx/Application/CommandHandler/User/SignUpUserCommandHandler.php: -------------------------------------------------------------------------------- 1 | userRepository = $userRepository; 34 | $this->hashingService = $hashingService; 35 | } 36 | 37 | /** 38 | * Sign in user in web app 39 | * 40 | * @param SignUpUserCommand $userRequest 41 | * @return bool 42 | * @throws UserAlreadyExistsException 43 | */ 44 | public function execute(CommandInterface $userRequest) 45 | { 46 | $user = $this->userRepository->fetchByEmail($userRequest->email()); 47 | if (null != $user) { 48 | throw new UserAlreadyExistsException(); 49 | } 50 | $user = $this->buildNewUser( 51 | $userRequest->username(), 52 | $userRequest->email(), 53 | Password::fromString($userRequest->password()) 54 | ); 55 | 56 | $this->userRepository->add($user); 57 | 58 | return true; 59 | } 60 | 61 | /** 62 | * Build new user 63 | * 64 | * @param $username 65 | * @param $email 66 | * @param $password 67 | * @codeCoverageIgnore 68 | * 69 | * @return User 70 | */ 71 | protected function buildNewUser($username, $email, $password) 72 | { 73 | return new User( 74 | UserId::generateUserId(), 75 | $username, 76 | $email, 77 | $this->hashingService->hash($password) 78 | ); 79 | } 80 | } -------------------------------------------------------------------------------- /tests/Application/CommandHandler/CommandHandlerTest.php: -------------------------------------------------------------------------------- 1 | execute(new FakeSignUpUserCommand()); 31 | } 32 | 33 | public function testCommandHandlerWasNotFoundException() 34 | { 35 | $this->expectException(CommandHandlerNotFoundException::class); 36 | $commandHandler = new CommandHandler(new FakeApplication()); 37 | $commandHandler->execute(new FakeCommandWithNoCommandHandler()); 38 | } 39 | } 40 | 41 | class FakeApplication extends Application 42 | { 43 | public function __construct(array $values = array()) 44 | { 45 | $this['customHandler'] = new FakeCustomCommandHandler(); 46 | } 47 | } 48 | 49 | class FakeSignUpUserCommand implements CommandInterface 50 | { 51 | /** 52 | * @return string 53 | */ 54 | public function commandHandler(): string 55 | { 56 | return 'customHandler'; 57 | } 58 | } 59 | 60 | class FakeCommandWithNoCommandHandler implements CommandInterface 61 | { 62 | 63 | /** 64 | * @return string 65 | */ 66 | public function commandHandler(): string 67 | { 68 | return 'notexists'; 69 | } 70 | } 71 | 72 | class FakeCustomCommandHandler implements CommandHandlerInterface 73 | { 74 | /** 75 | * 76 | * 77 | * @param $command 78 | * @return mixed 79 | */ 80 | public function execute(CommandInterface $command) 81 | { 82 | return true; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Slx/UserInterface/Console/console.php: -------------------------------------------------------------------------------- 1 | getDefinition()->addOption(new InputOption('--env', '-e', InputOption::VALUE_REQUIRED, 'The Environment name.', 'dev')); 11 | $console->setDispatcher($app['dispatcher']); 12 | $console 13 | ->register('my-command') 14 | ->setDefinition(array(// new InputOption('some-option', null, InputOption::VALUE_NONE, 'Some help'), 15 | )) 16 | ->setDescription('My command description') 17 | ->setCode(function (InputInterface $input, OutputInterface $output) use ($app) { 18 | // do something 19 | }); 20 | 21 | $console->setHelperSet(new Symfony\Component\Console\Helper\HelperSet(array( 22 | 'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($app["db"]), 23 | 'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($app["orm.em"]) 24 | ))); 25 | 26 | $console->addCommands(array( 27 | new \Doctrine\ORM\Tools\Console\Command\ClearCache\MetadataCommand, 28 | new \Doctrine\ORM\Tools\Console\Command\ClearCache\QueryCommand, 29 | new \Doctrine\ORM\Tools\Console\Command\ClearCache\ResultCommand, 30 | new \Doctrine\ORM\Tools\Console\Command\SchemaTool\CreateCommand, 31 | new \Doctrine\ORM\Tools\Console\Command\SchemaTool\DropCommand, 32 | new \Doctrine\ORM\Tools\Console\Command\SchemaTool\UpdateCommand, 33 | new \Doctrine\ORM\Tools\Console\Command\ConvertDoctrine1SchemaCommand, 34 | new \Doctrine\ORM\Tools\Console\Command\ConvertMappingCommand, 35 | new \Doctrine\ORM\Tools\Console\Command\EnsureProductionSettingsCommand, 36 | new \Doctrine\ORM\Tools\Console\Command\GenerateEntitiesCommand, 37 | new \Doctrine\ORM\Tools\Console\Command\GenerateProxiesCommand, 38 | new \Doctrine\ORM\Tools\Console\Command\GenerateRepositoriesCommand, 39 | new \Doctrine\ORM\Tools\Console\Command\InfoCommand, 40 | new \Doctrine\ORM\Tools\Console\Command\RunDqlCommand, 41 | new \Doctrine\ORM\Tools\Console\Command\ValidateSchemaCommand, 42 | new \Doctrine\DBAL\Tools\Console\Command\ImportCommand, 43 | new \Doctrine\DBAL\Tools\Console\Command\ReservedWordsCommand, 44 | new \Doctrine\DBAL\Tools\Console\Command\RunSqlCommand 45 | )); 46 | 47 | 48 | return $console; 49 | -------------------------------------------------------------------------------- /tests/Application/CommandHandler/User/SignUpUserCommandHandlerTest.php: -------------------------------------------------------------------------------- 1 | expectException(UserAlreadyExistsException::class); 31 | $command = new SignUpUserCommand( 32 | 'mongufi', 33 | 'specialOne@mongufi.com', 34 | 'easyPass' 35 | ); 36 | (new SignUpUserCommandHandler( 37 | new FakeUserRepository(), 38 | new FakePasswordHashService() 39 | ))->execute($command); 40 | } 41 | 42 | public function testUserRegister() 43 | { 44 | $command = new SignUpUserCommand( 45 | 'mongufi', 46 | 'specialOne@mongufi.com', 47 | 'easyPass' 48 | ); 49 | (new FakeSignUpUserCommand( 50 | new FakeEmptyUserRepo(), 51 | new FakePasswordHashService() 52 | ))->execute($command); 53 | 54 | } 55 | } 56 | 57 | class FakeSignUpUserCommand extends SignUpUserCommandHandler 58 | { 59 | protected function buildNewUser($username, $email, $password): UserTesting 60 | { 61 | return new UserTesting(); 62 | } 63 | } 64 | 65 | class FakeEmptyUserRepo implements UserRepositoryInterface 66 | { 67 | 68 | /** 69 | * @param User $user 70 | * 71 | * @return mixed 72 | */ 73 | public function add(User $user) 74 | { 75 | } 76 | 77 | /** 78 | * @param $email 79 | * 80 | * @return mixed 81 | */ 82 | public function fetchByEmail($email) 83 | { 84 | return null; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Slx/Domain/Event/Task/TaskWasCreated.php: -------------------------------------------------------------------------------- 1 | taskId = $taskId; 56 | $this->taskTitle = $taskTitle; 57 | $this->ocurredOn = new \DateTimeImmutable(); 58 | $this->userAssigned = $userAssigned; 59 | $this->taskDescription = $taskDescription; 60 | } 61 | 62 | /** 63 | * @return User 64 | */ 65 | public function userAssigned() 66 | { 67 | return $this->userAssigned; 68 | } 69 | 70 | /** 71 | * @return string 72 | */ 73 | public function taskId(): string 74 | { 75 | return $this->taskId; 76 | } 77 | 78 | /** 79 | * @return string 80 | */ 81 | public function taskTitle(): string 82 | { 83 | return $this->taskTitle; 84 | } 85 | 86 | /** 87 | * @return string 88 | */ 89 | public function taskDescription(): string 90 | { 91 | return $this->taskDescription; 92 | } 93 | 94 | /** 95 | * When occurred the event 96 | * 97 | * @return mixed 98 | */ 99 | public function occurredOn(): \DateTimeImmutable 100 | { 101 | return $this->ocurredOn; 102 | } 103 | 104 | /** 105 | * name event to listen 106 | * 107 | * @return mixed 108 | */ 109 | public function eventName(): string 110 | { 111 | return self::EVENT_NAME; 112 | } 113 | } -------------------------------------------------------------------------------- /src/Slx/Infrastructure/app.php: -------------------------------------------------------------------------------- 1 | register(new FormServiceProvider()); 16 | $app->register(new ServiceControllerServiceProvider()); 17 | $app->register(new Silex\Provider\ValidatorServiceProvider()); 18 | $app->register(new Silex\Provider\LocaleServiceProvider()); 19 | $app->register(new Silex\Provider\SessionServiceProvider()); 20 | $app->register(new Silex\Provider\TranslationServiceProvider(), array( 21 | 'locale' => 'en', 22 | )); 23 | $app->extend('translator', function ($translator, $app) { 24 | $translator->addLoader('yaml', new YamlFileLoader()); 25 | $translator->addResource('yaml', __DIR__ . '/Resources/Translations/en.yml', 'en'); 26 | 27 | return $translator; 28 | }); 29 | $app->register(new AssetServiceProvider()); 30 | $app->register(new TwigServiceProvider()); 31 | $app->register(new HttpFragmentServiceProvider()); 32 | $app['twig'] = $app->extend('twig', function ($twig, $app) { 33 | // add custom globals, filters, tags, ... 34 | 35 | return $twig; 36 | }); 37 | //$app->register(new MonologServiceProvider(), array( 38 | // 'monolog.logfile' => __DIR__.'/development.log', 39 | //)); 40 | $app['database_config'] = parse_ini_file(__DIR__ . '/../../../config/config.ini'); 41 | $app->register(new Silex\Provider\DoctrineServiceProvider(), array( 42 | 'db.options' => array( 43 | 'driver' => $app['database_config']['driver'], 44 | 'host' => $app['database_config']['host'], 45 | 'dbname' => $app['database_config']['dbname'], 46 | 'user' => $app['database_config']['user'], 47 | 'password' => $app['database_config']['password'], 48 | 'charset' => $app['database_config']['charset'], 49 | 'driverOptions' => array(1002 => 'SET NAMES utf8',), 50 | ), 51 | )); 52 | 53 | $app->register(new DoctrineOrmServiceProvider, array( 54 | "orm.proxies_dir" => __DIR__ . "/../../../var/cache/doctrine/proxy", 55 | 'orm.em.options' => array( 56 | 'mappings' => array( 57 | array( 58 | 'type' => 'simple_yml', 59 | 'namespace' => 'Slx\Domain\Entity', 60 | 'path' => __DIR__ . '/../Infrastructure/Persistence/Doctrine/Mapping/Entity', 61 | ), 62 | array( 63 | 'type' => 'simple_yml', 64 | 'namespace' => 'Slx\Domain\ValueObject', 65 | 'path' => __DIR__ . '/../Infrastructure/Persistence/Doctrine/Mapping/ValueObject', 66 | ), 67 | ), 68 | ), 69 | )); 70 | $app['em'] = $app["orm.em"]; 71 | 72 | return $app; 73 | -------------------------------------------------------------------------------- /src/Slx/UserInterface/Controllers/Task/CreateTaskController.php: -------------------------------------------------------------------------------- 1 | application = $application; 33 | } 34 | 35 | public function indexAction() 36 | { 37 | /** @var Form $form */ 38 | $form = $this->getForm($this->application['user_repository']->findAll()); 39 | $form->handleRequest($this->application['request_stack']->getCurrentRequest()); 40 | 41 | if ($form->isValid()) { 42 | $this->application['commandhandler.service']->execute( 43 | new CreateTaskCommand( 44 | $form->get('title')->getData(), 45 | $form->get('description')->getData(), 46 | $form->get('users')->getData() 47 | ) 48 | ); 49 | } 50 | 51 | return $this->application['twig']->render('views/task/create.html.twig', 52 | [ 53 | 'form' => $form->createView(), 54 | ] 55 | ); 56 | } 57 | 58 | private function getForm($users) 59 | { 60 | $formUsers = []; 61 | /** @var User $user */ 62 | foreach ($users as $user) { 63 | $formUsers[$user->email()->email()] = $user->id()->id(); 64 | } 65 | 66 | return $this->application['form.factory']->createBuilder(FormType::class, []) 67 | ->add('title', TextType::class, [ 68 | 'constraints' => [ 69 | new Assert\NotBlank(), 70 | ], 71 | 'attr' => [ 72 | 'class' => 'form-control' 73 | ] 74 | ]) 75 | ->add('description', TextareaType::class, [ 76 | 'constraints' => [ 77 | new Assert\NotBlank() 78 | ], 79 | 'attr' => [ 80 | 'class' => 'form-control' 81 | ] 82 | ]) 83 | ->add('users', ChoiceType::class, [ 84 | 'constraints' => [ 85 | new Assert\NotBlank() 86 | ], 87 | 'attr' => [ 88 | 'class' => 'form-control' 89 | ], 90 | 'choices' => $formUsers 91 | ]) 92 | ->getForm(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Slx/Domain/Entity/User/User.php: -------------------------------------------------------------------------------- 1 | uid = $userId; 66 | $this->username = $userName; 67 | $this->email = Email::fromString($email); 68 | $this->password = Password::fromString($passwd); 69 | $this->createdOn = new \DateTime(); 70 | $this->updatedOn = new \DateTime(); 71 | $this->dispatchUserWasRegisteredEvent(); 72 | } 73 | 74 | /** 75 | * @return UserId 76 | */ 77 | public function id(): UserId 78 | { 79 | return $this->uid; 80 | } 81 | 82 | /** 83 | * @return Password 84 | */ 85 | public function password(): Password 86 | { 87 | return $this->password; 88 | } 89 | 90 | /** 91 | * Change password 92 | * 93 | * @param string $password 94 | */ 95 | public function changePassword(string $password) 96 | { 97 | $this->password = $password; 98 | } 99 | 100 | /** 101 | * @return string 102 | */ 103 | public function username(): string 104 | { 105 | return $this->username; 106 | } 107 | 108 | /** 109 | * @return Email 110 | */ 111 | public function email(): Email 112 | { 113 | return $this->email; 114 | } 115 | 116 | /** 117 | * @return array 118 | */ 119 | public function tasks() 120 | { 121 | return $this->tasks; 122 | } 123 | 124 | /** 125 | * @param Task $task 126 | */ 127 | public function addTask(Task $task) 128 | { 129 | $this->tasks[] = $task; 130 | } 131 | 132 | /** 133 | * Dispatch event of user registered 134 | */ 135 | protected function dispatchUserWasRegisteredEvent() 136 | { 137 | DomainEventDispatcher::instance()->dispatch( 138 | new UserRegistered( 139 | $this->id(), 140 | $this->email(), 141 | $this->username() 142 | ) 143 | ); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/Slx/Infrastructure/Service/service.php: -------------------------------------------------------------------------------- 1 | getRepository('Slx\Domain\Entity\User\User'); 83 | }; 84 | $app['task_repository'] = function () use ($app) { 85 | return $app['em']->getRepository('Slx\Domain\Entity\Task\Task'); 86 | }; 87 | 88 | -------------------------------------------------------------------------------- /src/Slx/Domain/Entity/Task/Task.php: -------------------------------------------------------------------------------- 1 | id = TaskId::generate(); 64 | $this->title = $title; 65 | $this->userAssigned = $user; 66 | $this->setStatus($status); 67 | $this->description = $description; 68 | $this->createdOn = new \DateTimeImmutable(); 69 | $this->updatedOn = new \DateTimeImmutable(); 70 | DomainEventDispatcher::instance()->dispatch( 71 | new TaskWasCreated( 72 | $this->id, 73 | $this->title(), 74 | $this->description(), 75 | $this->userAssigned() 76 | ) 77 | ); 78 | } 79 | 80 | /** 81 | * Build new Task instance 82 | * 83 | * @param string $title 84 | * @param User $user 85 | * @param string $status 86 | * @param string $description 87 | * @return Task 88 | */ 89 | public static function build(string $title, User $user, string $status, string $description) 90 | { 91 | return new self($title, $user, $status, $description); 92 | } 93 | 94 | /** 95 | * @return int|TaskId 96 | */ 97 | public function id() 98 | { 99 | return $this->id; 100 | } 101 | 102 | /** 103 | * Set a task removed 104 | */ 105 | public function remove() 106 | { 107 | $this->status = self::REMOVED; 108 | } 109 | 110 | /** 111 | * @return string 112 | */ 113 | public function title(): string 114 | { 115 | return $this->title; 116 | } 117 | 118 | /** 119 | * @return string 120 | */ 121 | public function description(): string 122 | { 123 | return $this->description; 124 | } 125 | 126 | /** 127 | * @return User 128 | */ 129 | public function userAssigned(): User 130 | { 131 | return $this->userAssigned; 132 | } 133 | 134 | /** 135 | * @return string 136 | */ 137 | public function status(): string 138 | { 139 | return $this->status; 140 | } 141 | 142 | /** 143 | * @return \DateTimeImmutable 144 | */ 145 | public function createdOn(): \DateTimeImmutable 146 | { 147 | return $this->createdOn; 148 | } 149 | 150 | /** 151 | * @return \DateTimeImmutable 152 | */ 153 | public function updatedOn(): \DateTimeImmutable 154 | { 155 | return $this->updatedOn; 156 | } 157 | 158 | /** 159 | * @param string $status 160 | * @throws TaskStatusDoesNotExistsException 161 | */ 162 | public function setStatus(string $status) 163 | { 164 | if ($status !== self::OPEN && $status !== self::CLOSED) { 165 | throw new TaskStatusDoesNotExistsException(); 166 | } 167 | 168 | $this->status = $status; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /tests/Application/CommandHandler/User/SignInUserCommandHandlerTest.php: -------------------------------------------------------------------------------- 1 | expectException(UserDoesNotExistsException::class); 28 | $commandHandler = new SignInUserCommandHandler( 29 | new FakeEmptyUserRepository(), 30 | new FakePasswordHashService() 31 | ); 32 | 33 | $commandHandler->execute(new SignInUserCommand('email', 'password')); 34 | } 35 | 36 | public function testUserExists() 37 | { 38 | $commandHandler = new SignInUserCommandHandler( 39 | new FakeUserRepository(), 40 | new FakePasswordHashService() 41 | ); 42 | 43 | $user = $commandHandler->execute( 44 | new SignInUserCommand( 45 | 'thebestemail@domain.com', 46 | 'password' 47 | ) 48 | ); 49 | 50 | $this->assertEquals('thebestemail@domain.com', $user->email()->email()); 51 | $this->assertEquals('password', $user->password()->password()); 52 | } 53 | 54 | public function testPasswordNotVerified() 55 | { 56 | $this->expectException(UserPasswordDoesNotMatchException::class); 57 | $commandHandler = new SignInUserCommandHandler( 58 | new FakeUserRepository(), 59 | new FakePasswordHashService() 60 | ); 61 | 62 | $user = $commandHandler->execute( 63 | new SignInUserCommand( 64 | 'thebestemail@domain.com', 65 | 'badPassword' 66 | ) 67 | ); 68 | 69 | } 70 | } 71 | 72 | class FakeEmptyUserRepository implements UserRepositoryInterface 73 | { 74 | 75 | /** 76 | * @param User $user 77 | * 78 | * @return mixed 79 | */ 80 | public function add(User $user) 81 | { 82 | return null; 83 | } 84 | 85 | /** 86 | * @param $email 87 | * 88 | * @return mixed 89 | */ 90 | public function fetchByEmail($email) 91 | { 92 | return null; 93 | } 94 | } 95 | 96 | class FakeUserRepository implements UserRepositoryInterface 97 | { 98 | 99 | /** 100 | * @param User $user 101 | * 102 | * @return mixed 103 | */ 104 | public function add(User $user) 105 | { 106 | return null; 107 | } 108 | 109 | /** 110 | * @param $email 111 | * 112 | * @return mixed 113 | */ 114 | public function fetchByEmail($email) 115 | { 116 | return new UserTesting(); 117 | } 118 | } 119 | 120 | class FakePasswordHashService implements PasswordHashingService 121 | { 122 | 123 | /** 124 | * @param Password $password 125 | * 126 | * @return mixed 127 | */ 128 | public function hash(Password $password) 129 | { 130 | return 'hashedPassword'; 131 | } 132 | 133 | /** 134 | * @param Password $userPassword 135 | * @param string $passwordToVerify 136 | * 137 | * @return mixed 138 | */ 139 | public function verifyPassword(Password $userPassword, string $passwordToVerify) 140 | { 141 | return $userPassword->password() == $passwordToVerify; 142 | } 143 | } 144 | 145 | class UserTesting extends User 146 | { 147 | public function __construct() 148 | { 149 | } 150 | 151 | public function email(): Email 152 | { 153 | return Email::fromString('thebestemail@domain.com'); 154 | } 155 | 156 | public function username(): string 157 | { 158 | return 'amaizingUsername'; 159 | } 160 | 161 | public function password(): Password 162 | { 163 | return Password::fromString('password'); 164 | } 165 | 166 | public function dispatchUserWasRegisteredEvent() 167 | { 168 | } 169 | } 170 | --------------------------------------------------------------------------------