├── 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 |
5 |
Sign in
6 | {{ form_start(form, {'method': 'POST'}) }}
7 | {{ form_errors(form) }}
8 |
9 |
16 |
17 |
24 |
25 |
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 |
Create new Task
6 | {{ form_start(form, {'method': 'POST'}) }}
7 | {{ form_errors(form) }}
8 |
9 |
16 |
17 |
24 |
25 |
32 |
33 |
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 |
15 |
16 |
23 |
24 |
31 |
32 |
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