├── .env
├── .github
├── FUNDING.yml
└── workflows
│ └── ci.yml
├── .gitignore
├── Dockerfile
├── Makefile
├── README.md
├── apps
├── backoffice
│ ├── backend
│ │ ├── bin
│ │ │ └── console
│ │ ├── config
│ │ │ ├── bundles.php
│ │ │ ├── routes
│ │ │ │ ├── courses.yaml
│ │ │ │ └── health-check.yaml
│ │ │ ├── services.yaml
│ │ │ ├── services
│ │ │ │ └── framework.yaml
│ │ │ └── services_test.yaml
│ │ ├── public
│ │ │ └── index.php
│ │ └── src
│ │ │ ├── BackofficeBackendKernel.php
│ │ │ └── Controller
│ │ │ ├── Courses
│ │ │ └── CoursesGetController.php
│ │ │ └── HealthCheck
│ │ │ └── HealthCheckGetController.php
│ └── frontend
│ │ ├── bin
│ │ └── console
│ │ ├── config
│ │ ├── bundles.php
│ │ ├── routes
│ │ │ ├── api_courses.yaml
│ │ │ ├── courses.yaml
│ │ │ ├── health-check.yaml
│ │ │ └── home.yaml
│ │ ├── services.yaml
│ │ ├── services
│ │ │ └── framework.yaml
│ │ └── services_test.yaml
│ │ ├── public
│ │ ├── images
│ │ │ └── logo.png
│ │ └── index.php
│ │ ├── src
│ │ ├── BackofficeFrontendKernel.php
│ │ ├── Command
│ │ │ └── ImportCoursesToElasticsearchCommand.php
│ │ └── Controller
│ │ │ ├── Courses
│ │ │ ├── CoursesGetWebController.php
│ │ │ └── CoursesPostWebController.php
│ │ │ ├── HealthCheck
│ │ │ └── HealthCheckGetController.php
│ │ │ └── Home
│ │ │ └── HomeGetWebController.php
│ │ └── templates
│ │ ├── master.html.twig
│ │ ├── pages
│ │ ├── courses
│ │ │ ├── courses.html.twig
│ │ │ └── partials
│ │ │ │ ├── list_courses.html.twig
│ │ │ │ └── new_course_form.html.twig
│ │ └── home.html.twig
│ │ └── partials
│ │ ├── footer.html.twig
│ │ └── header.html.twig
├── bootstrap.php
└── mooc
│ ├── backend
│ ├── bin
│ │ └── console
│ ├── build
│ │ └── supervisor
│ │ │ └── .gitkeep
│ ├── config
│ │ ├── bundles.php
│ │ ├── routes
│ │ │ ├── courses.yaml
│ │ │ ├── courses_counter.yaml
│ │ │ └── health-check.yaml
│ │ ├── services.yaml
│ │ ├── services
│ │ │ └── framework.yaml
│ │ └── services_test.yaml
│ ├── public
│ │ └── index.php
│ └── src
│ │ ├── Command
│ │ └── DomainEvents
│ │ │ ├── MySql
│ │ │ └── ConsumeMySqlDomainEventsCommand.php
│ │ │ └── RabbitMq
│ │ │ ├── ConfigureRabbitMqCommand.php
│ │ │ ├── ConsumeRabbitMqDomainEventsCommand.php
│ │ │ └── GenerateSupervisorRabbitMqConsumerFilesCommand.php
│ │ ├── Controller
│ │ ├── Courses
│ │ │ └── CoursesPutController.php
│ │ ├── CoursesCounter
│ │ │ └── CoursesCounterGetController.php
│ │ └── HealthCheck
│ │ │ └── HealthCheckGetController.php
│ │ └── MoocBackendKernel.php
│ └── frontend
│ └── src
│ └── .gitkeep
├── behat.yml
├── composer.json
├── composer.lock
├── databases
├── backoffice
│ └── courses.json
└── mooc.sql
├── doc
└── endpoints
│ └── backoffice_frontend.http
├── docker-compose.yml
├── etc
└── infrastructure
│ ├── nginx
│ └── default.conf
│ └── php
│ ├── conf.d
│ ├── apcu.ini
│ ├── opcache.ini
│ └── xdebug.ini
│ └── php.ini
├── phpunit.xml
├── src
├── Analytics
│ └── DomainEvents
│ │ ├── Application
│ │ └── Store
│ │ │ ├── DomainEventStorer.php
│ │ │ └── StoreDomainEventOnOccurred.php
│ │ └── Domain
│ │ ├── AnalyticsDomainEvent.php
│ │ ├── AnalyticsDomainEventAggregateId.php
│ │ ├── AnalyticsDomainEventBody.php
│ │ ├── AnalyticsDomainEventId.php
│ │ ├── AnalyticsDomainEventName.php
│ │ └── DomainEventsRepository.php
├── Backoffice
│ ├── Auth
│ │ ├── Application
│ │ │ └── Authenticate
│ │ │ │ ├── AuthenticateUserCommand.php
│ │ │ │ ├── AuthenticateUserCommandHandler.php
│ │ │ │ └── UserAuthenticator.php
│ │ ├── Domain
│ │ │ ├── AuthPassword.php
│ │ │ ├── AuthRepository.php
│ │ │ ├── AuthUser.php
│ │ │ ├── AuthUsername.php
│ │ │ ├── InvalidAuthCredentials.php
│ │ │ └── InvalidAuthUsername.php
│ │ └── Infrastructure
│ │ │ └── Persistence
│ │ │ └── InMemoryAuthRepository.php
│ ├── Courses
│ │ ├── Application
│ │ │ ├── BackofficeCourseResponse.php
│ │ │ ├── BackofficeCoursesResponse.php
│ │ │ ├── Create
│ │ │ │ ├── BackofficeCourseCreator.php
│ │ │ │ └── CreateBackofficeCourseOnCourseCreated.php
│ │ │ ├── SearchAll
│ │ │ │ ├── AllBackofficeCoursesSearcher.php
│ │ │ │ ├── SearchAllBackofficeCoursesQuery.php
│ │ │ │ └── SearchAllBackofficeCoursesQueryHandler.php
│ │ │ └── SearchByCriteria
│ │ │ │ ├── BackofficeCoursesByCriteriaSearcher.php
│ │ │ │ ├── SearchBackofficeCoursesByCriteriaQuery.php
│ │ │ │ └── SearchBackofficeCoursesByCriteriaQueryHandler.php
│ │ ├── Domain
│ │ │ ├── BackofficeCourse.php
│ │ │ └── BackofficeCourseRepository.php
│ │ └── Infrastructure
│ │ │ └── Persistence
│ │ │ ├── Doctrine
│ │ │ └── BackofficeCourse.orm.xml
│ │ │ ├── ElasticsearchBackofficeCourseRepository.php
│ │ │ ├── InMemoryCacheBackofficeCourseRepository.php
│ │ │ └── MySqlBackofficeCourseRepository.php
│ └── Shared
│ │ └── Infrastructure
│ │ └── Symfony
│ │ └── DependencyInjection
│ │ └── backoffice_services.yaml
├── Mooc
│ ├── Courses
│ │ ├── Application
│ │ │ ├── Create
│ │ │ │ ├── CourseCreator.php
│ │ │ │ ├── CreateCourseCommand.php
│ │ │ │ └── CreateCourseCommandHandler.php
│ │ │ ├── Find
│ │ │ │ └── CourseFinder.php
│ │ │ └── Update
│ │ │ │ └── CourseRenamer.php
│ │ ├── Domain
│ │ │ ├── Course.php
│ │ │ ├── CourseCreatedDomainEvent.php
│ │ │ ├── CourseDuration.php
│ │ │ ├── CourseName.php
│ │ │ ├── CourseNotExist.php
│ │ │ └── CourseRepository.php
│ │ └── Infrastructure
│ │ │ └── Persistence
│ │ │ ├── Doctrine
│ │ │ ├── Course.orm.xml
│ │ │ ├── CourseDuration.orm.xml
│ │ │ ├── CourseIdType.php
│ │ │ └── CourseName.orm.xml
│ │ │ ├── DoctrineCourseRepository.php
│ │ │ ├── Eloquent
│ │ │ └── CourseEloquentModel.php
│ │ │ ├── EloquentCourseRepository.php
│ │ │ └── FileCourseRepository.php
│ ├── CoursesCounter
│ │ ├── Application
│ │ │ ├── Find
│ │ │ │ ├── CoursesCounterFinder.php
│ │ │ │ ├── CoursesCounterResponse.php
│ │ │ │ ├── FindCoursesCounterQuery.php
│ │ │ │ └── FindCoursesCounterQueryHandler.php
│ │ │ └── Increment
│ │ │ │ ├── CoursesCounterIncrementer.php
│ │ │ │ └── IncrementCoursesCounterOnCourseCreated.php
│ │ ├── Domain
│ │ │ ├── CoursesCounter.php
│ │ │ ├── CoursesCounterId.php
│ │ │ ├── CoursesCounterIncrementedDomainEvent.php
│ │ │ ├── CoursesCounterNotExist.php
│ │ │ ├── CoursesCounterRepository.php
│ │ │ └── CoursesCounterTotal.php
│ │ └── Infrastructure
│ │ │ └── Persistence
│ │ │ ├── Doctrine
│ │ │ ├── CourseCounterIdType.php
│ │ │ ├── CourseIdsType.php
│ │ │ ├── CoursesCounter.orm.xml
│ │ │ └── CoursesCounterTotal.orm.xml
│ │ │ └── DoctrineCoursesCounterRepository.php
│ ├── Notifications
│ │ └── Application
│ │ │ ├── SendNewCommentReplyEmail
│ │ │ └── .gitkeep
│ │ │ ├── SendNewCommentReplyPush
│ │ │ └── .gitkeep
│ │ │ └── SendResetPasswordEmail
│ │ │ └── .gitkeep
│ ├── Shared
│ │ ├── Domain
│ │ │ └── Course
│ │ │ │ └── CourseId.php
│ │ └── Infrastructure
│ │ │ ├── Doctrine
│ │ │ ├── DbalTypesSearcher.php
│ │ │ ├── DoctrinePrefixesSearcher.php
│ │ │ └── MoocEntityManagerFactory.php
│ │ │ └── Symfony
│ │ │ └── DependencyInjection
│ │ │ └── mooc_services.yaml
│ └── Videos
│ │ ├── Application
│ │ └── .gitkeep
│ │ ├── Domain
│ │ └── .gitkeep
│ │ └── Infrastructure
│ │ └── .gitkeep
├── Retention
│ ├── Campaign
│ │ ├── Application
│ │ │ ├── NewCourseAvailable
│ │ │ │ ├── Schedule
│ │ │ │ │ └── .gitkeep
│ │ │ │ └── Trigger
│ │ │ │ │ └── .gitkeep
│ │ │ └── WelcomeUser
│ │ │ │ └── Trigger
│ │ │ │ └── .gitkeep
│ │ ├── Domain
│ │ │ └── .gitkeep
│ │ └── Infrastructure
│ │ │ └── .gitkeep
│ ├── Email
│ │ ├── Application
│ │ │ ├── SendNewCourseAvailable
│ │ │ │ └── .gitkeep
│ │ │ └── SendWelcomeUser
│ │ │ │ └── .gitkeep
│ │ ├── Domain
│ │ │ └── .gitkeep
│ │ └── Infrastructure
│ │ │ └── .gitkeep
│ ├── Push
│ │ ├── Application
│ │ │ └── SendNewCourseAvailable
│ │ │ │ └── .gitkeep
│ │ ├── Domain
│ │ │ └── .gitkeep
│ │ └── Infrastructure
│ │ │ └── .gitkeep
│ └── Sms
│ │ ├── Application
│ │ └── .gitkeep
│ │ ├── Domain
│ │ └── .gitkeep
│ │ └── Infrastructure
│ │ └── .gitkeep
└── Shared
│ ├── Domain
│ ├── Aggregate
│ │ └── AggregateRoot.php
│ ├── Assert.php
│ ├── Bus
│ │ ├── Command
│ │ │ ├── Command.php
│ │ │ ├── CommandBus.php
│ │ │ └── CommandHandler.php
│ │ ├── Event
│ │ │ ├── DomainEvent.php
│ │ │ ├── DomainEventSubscriber.php
│ │ │ └── EventBus.php
│ │ └── Query
│ │ │ ├── Query.php
│ │ │ ├── QueryBus.php
│ │ │ ├── QueryHandler.php
│ │ │ └── Response.php
│ ├── Collection.php
│ ├── Criteria
│ │ ├── Criteria.php
│ │ ├── Filter.php
│ │ ├── FilterField.php
│ │ ├── FilterOperator.php
│ │ ├── FilterValue.php
│ │ ├── Filters.php
│ │ ├── Order.php
│ │ ├── OrderBy.php
│ │ └── OrderType.php
│ ├── DomainError.php
│ ├── Logger.php
│ ├── Monitoring.php
│ ├── RandomNumberGenerator.php
│ ├── Utils.php
│ ├── UuidGenerator.php
│ └── ValueObject
│ │ ├── Enum.php
│ │ ├── IntValueObject.php
│ │ ├── StringValueObject.php
│ │ └── Uuid.php
│ └── Infrastructure
│ ├── Bus
│ ├── CallableFirstParameterExtractor.php
│ ├── Command
│ │ ├── CommandNotRegisteredError.php
│ │ └── InMemorySymfonyCommandBus.php
│ ├── Event
│ │ ├── DomainEventJsonDeserializer.php
│ │ ├── DomainEventJsonSerializer.php
│ │ ├── DomainEventMapping.php
│ │ ├── DomainEventSubscriberLocator.php
│ │ ├── InMemory
│ │ │ └── InMemorySymfonyEventBus.php
│ │ ├── MySql
│ │ │ ├── MySqlDoctrineDomainEventsConsumer.php
│ │ │ └── MySqlDoctrineEventBus.php
│ │ └── RabbitMq
│ │ │ ├── RabbitMqConfigurer.php
│ │ │ ├── RabbitMqConnection.php
│ │ │ ├── RabbitMqDomainEventsConsumer.php
│ │ │ ├── RabbitMqEventBus.php
│ │ │ ├── RabbitMqExchangeNameFormatter.php
│ │ │ └── RabbitMqQueueNameFormatter.php
│ └── Query
│ │ ├── InMemorySymfonyQueryBus.php
│ │ └── QueryNotRegisteredError.php
│ ├── Doctrine
│ ├── DatabaseConnections.php
│ ├── Dbal
│ │ ├── DbalCustomTypesRegistrar.php
│ │ └── DoctrineCustomType.php
│ └── DoctrineEntityManagerFactory.php
│ ├── Elasticsearch
│ ├── ElasticsearchClient.php
│ └── ElasticsearchClientFactory.php
│ ├── Logger
│ └── MonologLogger.php
│ ├── Persistence
│ ├── Doctrine
│ │ ├── DoctrineCriteriaConverter.php
│ │ ├── DoctrineRepository.php
│ │ └── UuidType.php
│ └── Elasticsearch
│ │ ├── ElasticQueryGenerator.php
│ │ ├── ElasticsearchCriteriaConverter.php
│ │ └── ElasticsearchRepository.php
│ ├── PhpRandomNumberGenerator.php
│ ├── RamseyUuidGenerator.php
│ └── Symfony
│ ├── AddJsonBodyToRequestListener.php
│ ├── ApiController.php
│ ├── ApiExceptionListener.php
│ ├── ApiExceptionsHttpStatusCodeMapping.php
│ ├── BasicHttpAuthMiddleware.php
│ ├── FlashSession.php
│ └── WebController.php
└── tests
├── apps
├── backoffice
│ ├── backend
│ │ └── .gitkeep
│ └── frontend
│ │ └── .gitkeep
└── mooc
│ ├── backend
│ ├── features
│ │ ├── courses
│ │ │ └── course_put.feature
│ │ ├── courses_counter
│ │ │ └── courses_counter_get.feature
│ │ └── health_check
│ │ │ └── health_check_get.feature
│ └── mooc_backend.yml
│ └── frontend
│ └── .gitkeep
└── src
├── Backoffice
├── Auth
│ ├── Application
│ │ └── Authenticate
│ │ │ ├── AuthenticateUserCommandHandlerTest.php
│ │ │ └── AuthenticateUserCommandMother.php
│ ├── AuthModuleUnitTestCase.php
│ └── Domain
│ │ ├── AuthPasswordMother.php
│ │ ├── AuthUserMother.php
│ │ └── AuthUsernameMother.php
└── Courses
│ ├── BackofficeCoursesModuleInfrastructureTestCase.php
│ ├── Domain
│ ├── BackofficeCourseCriteriaMother.php
│ └── BackofficeCourseMother.php
│ └── Infrastructure
│ └── Persistence
│ └── MySqlBackofficeCourseRepositoryTest.php
├── Mooc
├── Courses
│ ├── Application
│ │ ├── Create
│ │ │ ├── CreateCourseCommandHandlerTest.php
│ │ │ └── CreateCourseCommandMother.php
│ │ └── Update
│ │ │ └── CourseRenamerTest.php
│ ├── CoursesModuleInfrastructureTestCase.php
│ ├── CoursesModuleUnitTestCase.php
│ ├── Domain
│ │ ├── CourseCreatedDomainEventMother.php
│ │ ├── CourseDurationMother.php
│ │ ├── CourseIdMother.php
│ │ ├── CourseMother.php
│ │ └── CourseNameMother.php
│ └── Infrastructure
│ │ └── Persistence
│ │ └── CourseRepositoryTest.php
├── CoursesCounter
│ ├── Application
│ │ ├── Find
│ │ │ ├── CoursesCounterResponseMother.php
│ │ │ └── FindCoursesCounterQueryHandlerTest.php
│ │ └── Increment
│ │ │ └── IncrementCoursesCounterOnCourseCreatedTest.php
│ ├── CoursesCounterModuleUnitTestCase.php
│ └── Domain
│ │ ├── CoursesCounterIdMother.php
│ │ ├── CoursesCounterIncrementedDomainEventMother.php
│ │ ├── CoursesCounterMother.php
│ │ └── CoursesCounterTotalMother.php
├── Shared
│ ├── Domain
│ │ └── .gitkeep
│ └── Infrastructure
│ │ └── PhpUnit
│ │ ├── MoocContextInfrastructureTestCase.php
│ │ └── MoocEnvironmentArranger.php
└── Videos
│ ├── Application
│ └── .gitkeep
│ ├── Domain
│ └── .gitkeep
│ └── Infrastructure
│ └── .gitkeep
└── Shared
├── Domain
├── Criteria
│ ├── CriteriaMother.php
│ ├── FilterFieldMother.php
│ ├── FilterMother.php
│ ├── FilterValueMother.php
│ ├── FiltersMother.php
│ ├── OrderByMother.php
│ └── OrderMother.php
├── DuplicatorMother.php
├── IntegerMother.php
├── MotherCreator.php
├── RandomElementPicker.php
├── Repeater.php
├── TestUtils.php
├── UuidMother.php
└── WordMother.php
└── Infrastructure
├── Arranger
└── EnvironmentArranger.php
├── Behat
├── ApiContext.php
└── ApplicationFeatureContext.php
├── Bus
├── Command
│ ├── FakeCommand.php
│ └── InMemorySymfonyCommandBusTest.php
├── Event
│ ├── MySql
│ │ └── MySqlDoctrineEventBusTest.php
│ └── RabbitMq
│ │ ├── RabbitMqEventBusTest.php
│ │ └── TestAllWorksOnRabbitMqEventsPublished.php
└── Query
│ ├── FakeQuery.php
│ ├── FakeResponse.php
│ └── InMemorySymfonyQueryBusTest.php
├── ConstantRandomNumberGenerator.php
├── Doctrine
└── DatabaseCleaner.php
├── Mink
├── MinkHelper.php
└── MinkSessionRequestHelper.php
├── Mockery
└── CodelyTvMatcherIsSimilar.php
└── PhpUnit
├── Comparator
├── AggregateRootArraySimilarComparator.php
├── AggregateRootSimilarComparator.php
├── DateTimeSimilarComparator.php
├── DateTimeStringSimilarComparator.php
├── DomainEventArraySimilarComparator.php
└── DomainEventSimilarComparator.php
├── Constraint
└── CodelyTvConstraintIsSimilar.php
├── InfrastructureTestCase.php
└── UnitTestCase.php
/.env:
--------------------------------------------------------------------------------
1 | ### Symfony - framework-bundle
2 | APP_ENV=test
3 | APP_SECRET=29ac4a5187930cd4b689aa0f3ee7cbc0
4 | #TRUSTED_PROXIES=127.0.0.1,127.0.0.2
5 | #TRUSTED_HOSTS='^localhost|example\.com$'
6 |
7 | # MOOC #
8 | #--------------------------------#
9 | # MySql
10 | MOOC_DATABASE_DRIVER=pdo_mysql
11 | MOOC_DATABASE_HOST=codelytv-php_ddd_skeleton-mooc-mysql
12 | MOOC_DATABASE_PORT=3306
13 | MOOC_DATABASE_NAME=mooc
14 | MOOC_DATABASE_USER=root
15 | MOOC_DATABASE_PASSWORD=
16 |
17 | # BACKOFFICE #
18 | #--------------------------------#
19 | # MySql
20 | BACKOFFICE_DATABASE_DRIVER=pdo_mysql
21 | BACKOFFICE_DATABASE_HOST=codelytv-php_ddd_skeleton-mooc-mysql
22 | BACKOFFICE_DATABASE_PORT=3306
23 | BACKOFFICE_DATABASE_NAME=mooc
24 | BACKOFFICE_DATABASE_USER=root
25 | BACKOFFICE_DATABASE_PASSWORD=
26 |
27 | # Elasticsearch
28 | BACKOFFICE_ELASTICSEARCH_HOST=127.0.0.1
29 | BACKOFFICE_ELASTICSEARCH_INDEX_PREFIX=backoffice
30 |
31 | # COMMON #
32 | #--------------------------------#
33 | # RabbitMQ
34 | RABBITMQ_HOST=codelytv-php_ddd_skeleton-rabbitmq
35 | RABBITMQ_PORT=5672
36 | RABBITMQ_LOGIN=codelytv
37 | RABBITMQ_PASSWORD=c0d3ly
38 | RABBITMQ_EXCHANGE=domain_events
39 | RABBITMQ_MAX_RETRIES=5
40 | # RabbitMQ - Application Specific
41 | RABBITMQ_MOOC_VHOST=/
42 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | custom: https://bit.ly/CodelyTvPro
2 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v1
12 |
13 | - name: Install dependencies
14 | run: make composer-install
15 |
16 | - name: Start all the environment
17 | run: make start
18 |
19 | - name: Wait for the environment to get up
20 | run: |
21 | while ! docker exec codelytv-php_ddd_skeleton-mooc-mysql mysqladmin --user=root --password= --host "127.0.0.1" ping --silent &> /dev/null ; do
22 | echo "Waiting for database connection..."
23 | sleep 2
24 | done
25 |
26 | - name: Run the tests
27 | run: make test
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.env.local
2 | /.env.*.local
3 |
4 | /apps/*/*/var/
5 |
6 | /apps/*/*/build/
7 | !/apps/*/*/build/supervisor/.gitkeep
8 |
9 | /vendor/
10 | .phpunit.result.cache
11 |
12 | /build
13 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM php:7.3.6-fpm-alpine
2 | WORKDIR /app
3 |
4 | RUN apk --update upgrade \
5 | && apk add --no-cache autoconf automake make gcc g++ icu-dev rabbitmq-c rabbitmq-c-dev \
6 | && pecl install amqp-1.9.4 \
7 | && pecl install apcu-5.1.17 \
8 | && pecl install xdebug-2.7.0RC2 \
9 | && docker-php-ext-install -j$(nproc) \
10 | bcmath \
11 | opcache \
12 | intl \
13 | pdo_mysql \
14 | && docker-php-ext-enable \
15 | amqp \
16 | apcu \
17 | opcache
18 |
19 | COPY etc/infrastructure/php/ /usr/local/etc/php/
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | > [!WARNING]
2 | > This repository has been archived in favor of [PHP DDD Example](https://github.com/CodelyTV/php-ddd-example)
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Template to start from scratch a new Php project using DDD as architecture.
11 |
12 | ## Installation
13 |
14 | Use the dependency manager [Composer](https://getcomposer.org/) to create a new project.
15 |
16 | ```
17 | composer create-project codelytv/ddd-skeleton
18 | ```
19 |
20 | ## Usage with Docker
21 |
22 | Just run:
23 |
24 | ```
25 | make build
26 | ```
27 |
28 | Then go to `http://localhost:8030/health-check` to check all is ok.
29 |
30 | ## Usage from local
31 |
32 | First of all you should execute
33 |
34 | ```
35 | make prepare-local
36 | ```
37 |
38 | And then start local environment:
39 |
40 | ```
41 | make start-local
42 | ```
43 |
44 | And then going to `http://localhost:8030/health-check` to check all is ok.
45 |
46 | ## Contributing
47 | There are some things missing feel free to add this if you want! If you want
48 | some guidelines feel free to contact us :)
49 |
--------------------------------------------------------------------------------
/apps/backoffice/backend/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | getParameterOption(['--env', '-e'], null, true)) {
23 | putenv('APP_ENV='.$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = $env);
24 | }
25 |
26 | if ($input->hasParameterOption('--no-debug', true)) {
27 | putenv('APP_DEBUG='.$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '0');
28 | }
29 |
30 | require dirname(__DIR__) . '/../../bootstrap.php';
31 |
32 | if ($_SERVER['APP_DEBUG']) {
33 | umask(0000);
34 |
35 | if (class_exists(Debug::class)) {
36 | Debug::enable();
37 | }
38 | }
39 |
40 | $kernel = new BackofficeBackendKernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
41 | $application = new Application($kernel);
42 | $application->run($input);
43 |
--------------------------------------------------------------------------------
/apps/backoffice/backend/config/bundles.php:
--------------------------------------------------------------------------------
1 | ['all' => true],
5 | FriendsOfBehat\SymfonyExtension\Bundle\FriendsOfBehatSymfonyExtensionBundle::class => ['test' => true],
6 | ];
7 |
8 | $suggestedBundles = [];
9 |
10 | if (true) {
11 | $suggestedBundles[WouterJ\EloquentBundle\WouterJEloquentBundle::class] = ['test' => true];
12 | }
13 |
14 | return array_merge($bundles, $suggestedBundles);
15 |
--------------------------------------------------------------------------------
/apps/backoffice/backend/config/routes/courses.yaml:
--------------------------------------------------------------------------------
1 | courses_get:
2 | path: /courses
3 | controller: CodelyTv\Apps\Backoffice\Backend\Controller\Courses\CoursesGetController
4 | defaults: { auth: true }
5 | methods: [GET]
6 |
--------------------------------------------------------------------------------
/apps/backoffice/backend/config/routes/health-check.yaml:
--------------------------------------------------------------------------------
1 | health-check_get:
2 | path: /health-check
3 | controller: CodelyTv\Apps\Backoffice\Backend\Controller\HealthCheck\HealthCheckGetController
4 | methods: [GET]
5 |
--------------------------------------------------------------------------------
/apps/backoffice/backend/config/services/framework.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | secret: '%env(APP_SECRET)%'
3 | #csrf_protection: true
4 | #http_method_override: true
5 |
6 | # Enables session support. Note that the session will ONLY be started if you read or write from it.
7 | # Remove or comment this section to explicitly disable session support.
8 | session:
9 | handler_id: null
10 | cookie_secure: auto
11 | cookie_samesite: lax
12 | enabled: true
13 |
14 | #esi: true
15 | #fragments: true
16 | php_errors:
17 | log: true
18 |
--------------------------------------------------------------------------------
/apps/backoffice/backend/config/services_test.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | test: true
3 |
4 | wouterj_eloquent:
5 | driver: mysql
6 | host: '%env(MOOC_DATABASE_HOST)%'
7 | port: '%env(MOOC_DATABASE_PORT)%'
8 | database: '%env(MOOC_DATABASE_NAME)%'
9 | username: '%env(MOOC_DATABASE_USER)%'
10 | password: '%env(MOOC_DATABASE_PASSWORD)%'
11 | prefix: ~
12 | eloquent: ~
13 |
14 | services:
15 | _defaults:
16 | autoconfigure: true
17 | autowire: true
18 |
19 | CodelyTv\Tests\:
20 | resource: '../../../../tests/src'
21 |
22 | # -- IMPLEMENTATIONS SELECTOR --
23 | CodelyTv\Shared\Domain\Bus\Event\EventBus: '@CodelyTv\Shared\Infrastructure\Bus\Event\InMemory\InMemorySymfonyEventBus'
24 |
--------------------------------------------------------------------------------
/apps/backoffice/backend/public/index.php:
--------------------------------------------------------------------------------
1 | handle($request);
29 | $response->send();
30 | $kernel->terminate($request, $response);
31 |
--------------------------------------------------------------------------------
/apps/backoffice/backend/src/Controller/HealthCheck/HealthCheckGetController.php:
--------------------------------------------------------------------------------
1 | 'ok',
18 | ]
19 | );
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/apps/backoffice/frontend/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | getParameterOption(['--env', '-e'], null, true)) {
23 | putenv('APP_ENV='.$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = $env);
24 | }
25 |
26 | if ($input->hasParameterOption('--no-debug', true)) {
27 | putenv('APP_DEBUG='.$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '0');
28 | }
29 |
30 | require dirname(__DIR__) . '/../../bootstrap.php';
31 |
32 | if ($_SERVER['APP_DEBUG']) {
33 | umask(0000);
34 |
35 | if (class_exists(Debug::class)) {
36 | Debug::enable();
37 | }
38 | }
39 |
40 | $kernel = new BackofficeFrontendKernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
41 | $application = new Application($kernel);
42 | $application->run($input);
43 |
--------------------------------------------------------------------------------
/apps/backoffice/frontend/config/bundles.php:
--------------------------------------------------------------------------------
1 | ['all' => true],
5 | FriendsOfBehat\SymfonyExtension\Bundle\FriendsOfBehatSymfonyExtensionBundle::class => ['test' => true],
6 | Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
7 | ];
8 |
9 | $suggestedBundles = [];
10 |
11 | if (true) {
12 | $suggestedBundles[WouterJ\EloquentBundle\WouterJEloquentBundle::class] = ['test' => true];
13 | }
14 |
15 | return array_merge($bundles, $suggestedBundles);
16 |
--------------------------------------------------------------------------------
/apps/backoffice/frontend/config/routes/api_courses.yaml:
--------------------------------------------------------------------------------
1 | api_courses_get:
2 | path: /api/courses
3 | controller: CodelyTv\Apps\Backoffice\Frontend\Controller\Courses\ApiCoursesGetController
4 | methods: [GET]
5 |
--------------------------------------------------------------------------------
/apps/backoffice/frontend/config/routes/courses.yaml:
--------------------------------------------------------------------------------
1 | courses_get:
2 | path: /courses
3 | controller: CodelyTv\Apps\Backoffice\Frontend\Controller\Courses\CoursesGetController
4 | methods: [GET]
5 |
6 | courses_post:
7 | path: /courses
8 | controller: CodelyTv\Apps\Backoffice\Frontend\Controller\Courses\CoursesPostController
9 | methods: [POST]
10 |
--------------------------------------------------------------------------------
/apps/backoffice/frontend/config/routes/health-check.yaml:
--------------------------------------------------------------------------------
1 | health-check_get:
2 | path: /health-check
3 | controller: CodelyTv\Apps\Backoffice\Frontend\Controller\HealthCheck\HealthCheckGetController
4 | methods: [GET]
5 |
--------------------------------------------------------------------------------
/apps/backoffice/frontend/config/routes/home.yaml:
--------------------------------------------------------------------------------
1 | home_get:
2 | path: /
3 | controller: CodelyTv\Apps\Backoffice\Frontend\Controller\Home\HomeGetController
4 | methods: [GET]
5 |
--------------------------------------------------------------------------------
/apps/backoffice/frontend/config/services/framework.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | secret: '%env(APP_SECRET)%'
3 | #csrf_protection: true
4 | #http_method_override: true
5 |
6 | # Enables session support. Note that the session will ONLY be started if you read or write from it.
7 | # Remove or comment this section to explicitly disable session support.
8 | session:
9 | handler_id: null
10 | cookie_secure: auto
11 | cookie_samesite: lax
12 | enabled: true
13 |
14 | #esi: true
15 | #fragments: true
16 | php_errors:
17 | log: true
18 |
--------------------------------------------------------------------------------
/apps/backoffice/frontend/config/services_test.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | test: true
3 |
4 | wouterj_eloquent:
5 | driver: mysql
6 | host: '%env(MOOC_DATABASE_HOST)%'
7 | port: '%env(MOOC_DATABASE_PORT)%'
8 | database: '%env(MOOC_DATABASE_NAME)%'
9 | username: '%env(MOOC_DATABASE_USER)%'
10 | password: '%env(MOOC_DATABASE_PASSWORD)%'
11 | prefix: ~
12 | eloquent: ~
13 |
14 | services:
15 | _defaults:
16 | autoconfigure: true
17 | autowire: true
18 |
19 | CodelyTv\Tests\:
20 | resource: '../../../../tests/src'
21 |
22 | # -- IMPLEMENTATIONS SELECTOR --
23 | CodelyTv\Shared\Domain\Bus\Event\EventBus: '@CodelyTv\Shared\Infrastructure\Bus\Event\InMemory\InMemorySymfonyEventBus'
24 |
--------------------------------------------------------------------------------
/apps/backoffice/frontend/public/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/php-ddd-skeleton-deprecated/d8dec6b574d14071635c041619c8a02ec60b5cb2/apps/backoffice/frontend/public/images/logo.png
--------------------------------------------------------------------------------
/apps/backoffice/frontend/public/index.php:
--------------------------------------------------------------------------------
1 | handle($request);
29 | $response->send();
30 | $kernel->terminate($request, $response);
31 |
--------------------------------------------------------------------------------
/apps/backoffice/frontend/src/Command/ImportCoursesToElasticsearchCommand.php:
--------------------------------------------------------------------------------
1 | mySqlRepository = $mySqlRepository;
25 | $this->elasticRepository = $elasticRepository;
26 | }
27 |
28 | public function execute(InputInterface $input, OutputInterface $output)
29 | {
30 | $courses = $this->mySqlRepository->searchAll();
31 |
32 | foreach ($courses as $course) {
33 | $this->elasticRepository->save($course);
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/apps/backoffice/frontend/src/Controller/Courses/CoursesGetWebController.php:
--------------------------------------------------------------------------------
1 | ask(new FindCoursesCounterQuery());
20 |
21 | return $this->render(
22 | 'pages/courses/courses.html.twig',
23 | [
24 | 'title' => 'Courses',
25 | 'description' => 'Courses CodelyTV - Backoffice',
26 | 'courses_counter' => $coursesCounterResponse->total(),
27 | 'new_course_id' => Uuid::random()->value(),
28 | ]
29 | );
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/apps/backoffice/frontend/src/Controller/HealthCheck/HealthCheckGetController.php:
--------------------------------------------------------------------------------
1 | 'ok',
18 | ]
19 | );
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/apps/backoffice/frontend/src/Controller/Home/HomeGetWebController.php:
--------------------------------------------------------------------------------
1 | render(
16 | 'pages/home.html.twig',
17 | [
18 | 'title' => 'Welcome',
19 | 'description' => 'CodelyTV - Backoffice',
20 | ]
21 | );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/apps/backoffice/frontend/templates/pages/courses/courses.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'master.html.twig' %}
2 |
3 | {% block page_title %}Cursos{% endblock %}
4 |
5 | {% block main %}
6 |
7 |

8 |
9 |
Cursos
10 |
11 | Actualmente CodelyTV Pro cuenta con {{ courses_counter }} cursos.
12 |
13 |
14 |
15 | {{ include('pages/courses/partials/new_course_form.html.twig') }}
16 |
17 |
18 | {{ include('pages/courses/partials/list_courses.html.twig') }}
19 | {% endblock %}
20 |
--------------------------------------------------------------------------------
/apps/backoffice/frontend/templates/pages/home.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'master.html.twig' %}
2 |
3 | {% block page_title %}HOME{% endblock %}
4 |
5 | {% block main %}
6 | HOLIII HOME
7 | {% endblock %}
8 |
--------------------------------------------------------------------------------
/apps/backoffice/frontend/templates/partials/footer.html.twig:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/apps/backoffice/frontend/templates/partials/header.html.twig:
--------------------------------------------------------------------------------
1 |
2 |
28 |
29 |
--------------------------------------------------------------------------------
/apps/bootstrap.php:
--------------------------------------------------------------------------------
1 | =1.2)
11 | if (is_array($env = @include $rootPath.'/.env.local.php')) {
12 | foreach ($env as $k => $v) {
13 | $_ENV[$k] = $_ENV[$k] ?? (isset($_SERVER[$k]) && 0 !== strpos($k, 'HTTP_') ? $_SERVER[$k] : $v);
14 | }
15 | } elseif (!class_exists(Dotenv::class)) {
16 | throw new RuntimeException('Please run "composer require symfony/dotenv" to load the ".env" files configuring the application.');
17 | } else {
18 | // load all the .env files
19 | (new Dotenv(false))->loadEnv($rootPath.'/.env');
20 | }
21 |
22 | $_SERVER += $_ENV;
23 | $_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null) ?: 'dev';
24 | $_SERVER['APP_DEBUG'] = $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? 'prod' !== $_SERVER['APP_ENV'];
25 | $_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = (int) $_SERVER['APP_DEBUG'] || filter_var($_SERVER['APP_DEBUG'], FILTER_VALIDATE_BOOLEAN) ? '1' : '0';
26 |
--------------------------------------------------------------------------------
/apps/mooc/backend/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | getParameterOption(['--env', '-e'], null, true)) {
23 | putenv('APP_ENV='.$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = $env);
24 | }
25 |
26 | if ($input->hasParameterOption('--no-debug', true)) {
27 | putenv('APP_DEBUG='.$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '0');
28 | }
29 |
30 | require dirname(__DIR__) . '/../../bootstrap.php';
31 |
32 | if ($_SERVER['APP_DEBUG']) {
33 | umask(0000);
34 |
35 | if (class_exists(Debug::class)) {
36 | Debug::enable();
37 | }
38 | }
39 |
40 | $kernel = new MoocBackendKernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
41 | $application = new Application($kernel);
42 | $application->run($input);
43 |
--------------------------------------------------------------------------------
/apps/mooc/backend/build/supervisor/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/php-ddd-skeleton-deprecated/d8dec6b574d14071635c041619c8a02ec60b5cb2/apps/mooc/backend/build/supervisor/.gitkeep
--------------------------------------------------------------------------------
/apps/mooc/backend/config/bundles.php:
--------------------------------------------------------------------------------
1 | ['all' => true],
5 | FriendsOfBehat\SymfonyExtension\Bundle\FriendsOfBehatSymfonyExtensionBundle::class => ['test' => true],
6 | ];
7 |
8 | $suggestedBundles = [];
9 |
10 | if (true) {
11 | $suggestedBundles[WouterJ\EloquentBundle\WouterJEloquentBundle::class] = ['test' => true];
12 | }
13 |
14 | return array_merge($bundles, $suggestedBundles);
15 |
--------------------------------------------------------------------------------
/apps/mooc/backend/config/routes/courses.yaml:
--------------------------------------------------------------------------------
1 | courses_put:
2 | path: /courses/{id}
3 | controller: CodelyTv\Apps\Mooc\Backend\Controller\Courses\CoursesPutController
4 | methods: [PUT]
5 |
--------------------------------------------------------------------------------
/apps/mooc/backend/config/routes/courses_counter.yaml:
--------------------------------------------------------------------------------
1 | courses_counter_get:
2 | path: /courses-counter
3 | controller: CodelyTv\Apps\Mooc\Backend\Controller\CoursesCounter\CoursesCounterGetController
4 | methods: [GET]
5 |
--------------------------------------------------------------------------------
/apps/mooc/backend/config/routes/health-check.yaml:
--------------------------------------------------------------------------------
1 | health-check_get:
2 | path: /health-check
3 | controller: CodelyTv\Apps\Mooc\Backend\Controller\HealthCheck\HealthCheckGetController
4 | methods: [GET]
5 |
--------------------------------------------------------------------------------
/apps/mooc/backend/config/services/framework.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | secret: '%env(APP_SECRET)%'
3 | #csrf_protection: true
4 | #http_method_override: true
5 |
6 | # Enables session support. Note that the session will ONLY be started if you read or write from it.
7 | # Remove or comment this section to explicitly disable session support.
8 | session:
9 | handler_id: ~
10 | cookie_secure: auto
11 | cookie_samesite: lax
12 |
13 | #esi: true
14 | #fragments: true
15 | php_errors:
16 | log: true
17 |
--------------------------------------------------------------------------------
/apps/mooc/backend/config/services_test.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | test: true
3 |
4 | wouterj_eloquent:
5 | driver: mysql
6 | host: '%env(MOOC_DATABASE_HOST)%'
7 | port: '%env(MOOC_DATABASE_PORT)%'
8 | database: '%env(MOOC_DATABASE_NAME)%'
9 | username: '%env(MOOC_DATABASE_USER)%'
10 | password: '%env(MOOC_DATABASE_PASSWORD)%'
11 | prefix: ~
12 | eloquent: ~
13 |
14 | services:
15 | _defaults:
16 | autoconfigure: true
17 | autowire: true
18 |
19 | CodelyTv\Tests\:
20 | resource: '../../../../tests/src'
21 |
22 | # Instance selector
23 | CodelyTv\Shared\Domain\RandomNumberGenerator: '@CodelyTv\Tests\Shared\Infrastructure\ConstantRandomNumberGenerator'
24 | # CodelyTv\Shared\Domain\Bus\Event\EventBus: '@CodelyTv\Shared\Infrastructure\Bus\Event\InMemory\InMemorySymfonyEventBus'
25 |
--------------------------------------------------------------------------------
/apps/mooc/backend/public/index.php:
--------------------------------------------------------------------------------
1 | handle($request);
26 | $response->send();
27 | $kernel->terminate($request, $response);
28 |
--------------------------------------------------------------------------------
/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConfigureRabbitMqCommand.php:
--------------------------------------------------------------------------------
1 | configurer = $configurer;
25 | $this->exchangeName = $exchangeName;
26 | $this->subscribers = $subscribers;
27 | }
28 |
29 | protected function configure(): void
30 | {
31 | $this->setDescription('Configure the RabbitMQ to allow publish & consume domain events');
32 | }
33 |
34 | protected function execute(InputInterface $input, OutputInterface $output)
35 | {
36 | $this->configurer->configure($this->exchangeName, ...iterator_to_array($this->subscribers));
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/apps/mooc/backend/src/Controller/Courses/CoursesPutController.php:
--------------------------------------------------------------------------------
1 | bus = $bus;
19 | }
20 |
21 | public function __invoke(string $id, Request $request)
22 | {
23 | $this->bus->dispatch(
24 | new CreateCourseCommand(
25 | $id,
26 | $request->request->get('name'),
27 | $request->request->get('duration')
28 | )
29 | );
30 |
31 | return new Response('', Response::HTTP_CREATED);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/apps/mooc/backend/src/Controller/CoursesCounter/CoursesCounterGetController.php:
--------------------------------------------------------------------------------
1 | bus = $bus;
20 | }
21 |
22 | public function __invoke()
23 | {
24 | /** @var CoursesCounterResponse $response */
25 | $response = $this->bus->ask(new FindCoursesCounterQuery());
26 |
27 | return new JsonResponse(
28 | [
29 | 'total' => $response->total(),
30 | ]
31 | );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/apps/mooc/backend/src/Controller/HealthCheck/HealthCheckGetController.php:
--------------------------------------------------------------------------------
1 | generator = $generator;
19 | }
20 |
21 | public function __invoke(Request $request): Response
22 | {
23 | return new JsonResponse(
24 | [
25 | 'mooc-backend' => 'ok',
26 | 'rand' => $this->generator->generate(),
27 | ]
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/apps/mooc/frontend/src/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/php-ddd-skeleton-deprecated/d8dec6b574d14071635c041619c8a02ec60b5cb2/apps/mooc/frontend/src/.gitkeep
--------------------------------------------------------------------------------
/behat.yml:
--------------------------------------------------------------------------------
1 | imports:
2 | - tests/apps/mooc/backend/mooc_backend.yml
3 |
--------------------------------------------------------------------------------
/databases/backoffice/courses.json:
--------------------------------------------------------------------------------
1 | {
2 | "mappings": {
3 | "courses": {
4 | "properties": {
5 | "id": {
6 | "type": "keyword",
7 | "index": true
8 | },
9 | "name": {
10 | "type": "text",
11 | "index": true
12 | },
13 | "duration": {
14 | "type": "text",
15 | "index": true
16 | }
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/databases/mooc.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE `courses` (
2 | `id` CHAR(36) NOT NULL,
3 | `name` VARCHAR(255) NOT NULL,
4 | `duration` VARCHAR(255) NOT NULL,
5 | PRIMARY KEY (`id`)
6 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
7 |
8 | CREATE TABLE `courses_counter` (
9 | `id` CHAR(36) NOT NULL,
10 | `total` INT NOT NULL,
11 | `existing_courses` JSON NOT NULL,
12 | PRIMARY KEY (`id`)
13 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
14 |
15 | CREATE TABLE `domain_events` (
16 | `id` CHAR(36) NOT NULL,
17 | `aggregate_id` CHAR(36) NOT NULL,
18 | `name` VARCHAR(255) NOT NULL,
19 | `body` JSON NOT NULL,
20 | `occurred_on` timestamp NOT NULL,
21 | PRIMARY KEY (`id`)
22 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
23 |
24 | CREATE TABLE `backoffice_courses` (
25 | `id` CHAR(36) NOT NULL,
26 | `name` VARCHAR(255) NOT NULL,
27 | `duration` VARCHAR(255) NOT NULL,
28 | PRIMARY KEY (`id`)
29 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
30 |
--------------------------------------------------------------------------------
/doc/endpoints/backoffice_frontend.http:
--------------------------------------------------------------------------------
1 | # ELASTIC - Search
2 | POST localhost:9200/backoffice_courses/_search
3 | Content-Type: application/json
4 |
5 | {
6 | "query": {
7 | "term": {
8 | "name": "Pepe"
9 | }
10 | }
11 | }
12 |
13 | ###
14 | # ELASTIC - Search
15 | POST localhost:9200/backoffice_courses/_search
16 | Content-Type: application/json
17 |
18 | ###
19 |
20 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | services:
4 | mysql:
5 | container_name: codelytv-php_ddd_skeleton-mooc-mysql
6 | image: mysql:8
7 | ports:
8 | - "3360:3306"
9 | environment:
10 | - MYSQL_ROOT_PASSWORD=
11 | - MYSQL_ALLOW_EMPTY_PASSWORD=yes
12 | command: ["--default-authentication-plugin=mysql_native_password"]
13 |
14 | rabbitmq:
15 | container_name: codelytv-php_ddd_skeleton-rabbitmq
16 | image: 'rabbitmq:3.7-management'
17 | restart: unless-stopped
18 | ports:
19 | - 5630:5672
20 | - 8090:15672
21 | environment:
22 | - RABBITMQ_DEFAULT_USER=codelytv
23 | - RABBITMQ_DEFAULT_PASS=c0d3ly
24 |
25 | nginx:
26 | container_name: codelytv-ddd-skeleton-nginx
27 | image: nginx:1.15-alpine
28 | restart: unless-stopped
29 | ports:
30 | - "8030:80"
31 | volumes:
32 | - .:/app:delegated
33 | - ./etc/infrastructure/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
34 | depends_on:
35 | - php
36 |
37 | php:
38 | container_name: codelytv-php_ddd_skeleton-php
39 | build:
40 | context: .
41 | dockerfile: Dockerfile
42 | restart: unless-stopped
43 | ports:
44 | - "9090:9001"
45 | volumes:
46 | - .:/app:delegated
47 | env_file:
48 | - .env
49 | depends_on:
50 | - mysql
51 | - rabbitmq
52 |
--------------------------------------------------------------------------------
/etc/infrastructure/nginx/default.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80;
3 | server_name localhost api.codelytv.localhost;
4 | root /app/apps/mooc/backend/public;
5 |
6 | error_log stderr;
7 | access_log stdout;
8 |
9 | rewrite ^/index\.php/?(.*)$ /$1 permanent;
10 |
11 | try_files $uri @rewriteapp;
12 |
13 | location @rewriteapp {
14 | rewrite ^(.*)$ /index.php/$1 last;
15 | }
16 |
17 | location ~ /\. {
18 | deny all;
19 | }
20 |
21 | location ~ ^/(index)\.php(/|$) {
22 | fastcgi_split_path_info ^(.+\.php)(/.*)$;
23 | include fastcgi_params;
24 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
25 | fastcgi_index index.php;
26 | send_timeout 1800;
27 | fastcgi_read_timeout 1800;
28 | fastcgi_pass php:9000;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/etc/infrastructure/php/conf.d/apcu.ini:
--------------------------------------------------------------------------------
1 | apc.shm_segments=1
2 | apc.shm_size=256M
3 | apc.num_files_hint=7000
4 | apc.user_entries_hint=4096
5 | apc.ttl=7200
6 | apc.user_ttl=7200
7 | apc.gc_ttl=3600
8 | apc.max_file_size=1M
9 | apc.stat=1
10 |
--------------------------------------------------------------------------------
/etc/infrastructure/php/conf.d/opcache.ini:
--------------------------------------------------------------------------------
1 | opcache.memory_consumption=128
2 | opcache.interned_strings_buffer=8
3 | opcache.max_accelerated_files=4000
4 | opcache.revalidate_freq=60
5 | opcache.fast_shutdown=1
6 | opcache.enable_cli=1
7 |
--------------------------------------------------------------------------------
/etc/infrastructure/php/conf.d/xdebug.ini:
--------------------------------------------------------------------------------
1 | zend_extension = xdebug.so
2 |
3 | ;Debugging
4 | xdebug.remote_enable = 1;
5 | xdebug.remote_connect_back = 1;
6 | xdebug.remote_autostart = 1;
7 | xdebug.remote_port = 9001;
8 | xdebug.remote_host = host.docker.internal
9 |
10 | ;Profiling
11 | xdebug.profiler_enable = 0;
12 | xdebug.profiler_enable_trigger = 1;
13 | xdebug.profiler_output_dir = "/tmp/xdebug";
14 |
15 | xdebug.max_nesting_level = 500;
16 |
--------------------------------------------------------------------------------
/etc/infrastructure/php/php.ini:
--------------------------------------------------------------------------------
1 | date.timezone = "UTC"
2 | html_errors = "On"
3 | display_errors = "On"
4 | error_reporting = E_ALL
5 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | ./tests/src
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/Analytics/DomainEvents/Application/Store/DomainEventStorer.php:
--------------------------------------------------------------------------------
1 | repository = $repository;
21 | }
22 |
23 | public function store(
24 | AnalyticsDomainEventId $id,
25 | AnalyticsDomainEventAggregateId $aggregateId,
26 | AnalyticsDomainEventName $name,
27 | AnalyticsDomainEventBody $body
28 | ): void {
29 | $domainEvent = new AnalyticsDomainEvent($id, $aggregateId, $name, $body);
30 |
31 | $this->repository->save($domainEvent);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Analytics/DomainEvents/Application/Store/StoreDomainEventOnOccurred.php:
--------------------------------------------------------------------------------
1 | storer = $storer;
21 | }
22 |
23 | public static function subscribedTo(): array
24 | {
25 | return [DomainEvent::class];
26 | }
27 |
28 | public function __invoke(DomainEvent $event)
29 | {
30 | $id = new AnalyticsDomainEventId($event->eventId());
31 | $aggregateId = new AnalyticsDomainEventAggregateId($event->aggregateId());
32 | $name = new AnalyticsDomainEventName($event::eventName());
33 | $body = new AnalyticsDomainEventBody($event->toPrimitives());
34 |
35 | $this->storer->store($id, $aggregateId, $name, $body);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Analytics/DomainEvents/Domain/AnalyticsDomainEvent.php:
--------------------------------------------------------------------------------
1 | id = $id;
21 | $this->aggregateId = $aggregateId;
22 | $this->name = $name;
23 | $this->body = $body;
24 | }
25 |
26 | public function id(): AnalyticsDomainEventId
27 | {
28 | return $this->id;
29 | }
30 |
31 | public function aggregateId(): AnalyticsDomainEventAggregateId
32 | {
33 | return $this->aggregateId;
34 | }
35 |
36 | public function name(): AnalyticsDomainEventName
37 | {
38 | return $this->name;
39 | }
40 |
41 | public function body(): AnalyticsDomainEventBody
42 | {
43 | return $this->body;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Analytics/DomainEvents/Domain/AnalyticsDomainEventAggregateId.php:
--------------------------------------------------------------------------------
1 | value = $value;
14 | }
15 |
16 | public function value(): array
17 | {
18 | return $this->value;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Analytics/DomainEvents/Domain/AnalyticsDomainEventId.php:
--------------------------------------------------------------------------------
1 | username = $username;
17 | $this->password = $password;
18 | }
19 |
20 | public function username(): string
21 | {
22 | return $this->username;
23 | }
24 |
25 | public function password(): string
26 | {
27 | return $this->password;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Backoffice/Auth/Application/Authenticate/AuthenticateUserCommandHandler.php:
--------------------------------------------------------------------------------
1 | authenticator = $authenticator;
18 | }
19 |
20 | public function __invoke(AuthenticateUserCommand $command)
21 | {
22 | $username = new AuthUsername($command->username());
23 | $password = new AuthPassword($command->password());
24 |
25 | $this->authenticator->authenticate($username, $password);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Backoffice/Auth/Application/Authenticate/UserAuthenticator.php:
--------------------------------------------------------------------------------
1 | repository = $repository;
21 | }
22 |
23 | public function authenticate(AuthUsername $username, AuthPassword $password): void
24 | {
25 | $auth = $this->repository->search($username);
26 |
27 | $this->ensureUserExist($auth, $username);
28 | $this->ensureCredentialsAreValid($auth, $password);
29 | }
30 |
31 | private function ensureUserExist(?AuthUser $auth, AuthUsername $username): void
32 | {
33 | if (null === $auth) {
34 | throw new InvalidAuthUsername($username);
35 | }
36 | }
37 |
38 | private function ensureCredentialsAreValid(AuthUser $auth, AuthPassword $password): void
39 | {
40 | if (!$auth->passwordMatches($password)) {
41 | throw new InvalidAuthCredentials($auth->username());
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Backoffice/Auth/Domain/AuthPassword.php:
--------------------------------------------------------------------------------
1 | value() === $other->value();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Backoffice/Auth/Domain/AuthRepository.php:
--------------------------------------------------------------------------------
1 | username = $username;
15 | $this->password = $password;
16 | }
17 |
18 | public function passwordMatches(AuthPassword $password): bool
19 | {
20 | return $this->password->isEquals($password);
21 | }
22 |
23 | public function username(): AuthUsername
24 | {
25 | return $this->username;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Backoffice/Auth/Domain/AuthUsername.php:
--------------------------------------------------------------------------------
1 | are invalid', $username->value()));
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Backoffice/Auth/Domain/InvalidAuthUsername.php:
--------------------------------------------------------------------------------
1 | does not exists', $username->value()));
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Backoffice/Auth/Infrastructure/Persistence/InMemoryAuthRepository.php:
--------------------------------------------------------------------------------
1 | 'barbitas',
17 | 'rafa' => 'pelazo',
18 | ];
19 |
20 | public function search(AuthUsername $username): ?AuthUser
21 | {
22 | $password = get($username->value(), self::USERS);
23 |
24 | return null !== $password ? new AuthUser($username, new AuthPassword($password)) : null;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Backoffice/Courses/Application/BackofficeCourseResponse.php:
--------------------------------------------------------------------------------
1 | id = $id;
16 | $this->name = $name;
17 | $this->duration = $duration;
18 | }
19 |
20 | public function id(): string
21 | {
22 | return $this->id;
23 | }
24 |
25 | public function name(): string
26 | {
27 | return $this->name;
28 | }
29 |
30 | public function duration(): string
31 | {
32 | return $this->duration;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Backoffice/Courses/Application/BackofficeCoursesResponse.php:
--------------------------------------------------------------------------------
1 | courses = $courses;
16 | }
17 |
18 | public function courses(): array
19 | {
20 | return $this->courses;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Backoffice/Courses/Application/Create/BackofficeCourseCreator.php:
--------------------------------------------------------------------------------
1 | repository = $repository;
17 | }
18 |
19 | public function create(string $id, string $name, string $duration): void
20 | {
21 | $this->repository->save(new BackofficeCourse($id, $name, $duration));
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Backoffice/Courses/Application/Create/CreateBackofficeCourseOnCourseCreated.php:
--------------------------------------------------------------------------------
1 | creator = $creator;
17 | }
18 |
19 | public static function subscribedTo(): array
20 | {
21 | return [CourseCreatedDomainEvent::class];
22 | }
23 |
24 | public function __invoke(CourseCreatedDomainEvent $event)
25 | {
26 | $this->creator->create($event->aggregateId(), $event->name(), $event->duration());
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Backoffice/Courses/Application/SearchAll/AllBackofficeCoursesSearcher.php:
--------------------------------------------------------------------------------
1 | repository = $repository;
20 | }
21 |
22 | public function searchAll(): BackofficeCoursesResponse
23 | {
24 | return new BackofficeCoursesResponse(...map($this->toResponse(), $this->repository->searchAll()));
25 | }
26 |
27 | private function toResponse(): callable
28 | {
29 | return static function (BackofficeCourse $course) {
30 | return new BackofficeCourseResponse($course->id(), $course->name(), $course->duration());
31 | };
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Backoffice/Courses/Application/SearchAll/SearchAllBackofficeCoursesQuery.php:
--------------------------------------------------------------------------------
1 | searcher = $searcher;
17 | }
18 |
19 | public function __invoke(SearchAllBackofficeCoursesQuery $query): BackofficeCoursesResponse
20 | {
21 | return $this->searcher->searchAll();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Backoffice/Courses/Application/SearchByCriteria/BackofficeCoursesByCriteriaSearcher.php:
--------------------------------------------------------------------------------
1 | repository = $repository;
23 | }
24 |
25 | public function search(Filters $filters, Order $order, ?int $limit, ?int $offset): BackofficeCoursesResponse
26 | {
27 | $criteria = new Criteria($filters, $order, $offset, $limit);
28 |
29 | return new BackofficeCoursesResponse(...map($this->toResponse(), $this->repository->matching($criteria)));
30 | }
31 |
32 | private function toResponse(): callable
33 | {
34 | return static function (BackofficeCourse $course) {
35 | return new BackofficeCourseResponse($course->id(), $course->name(), $course->duration());
36 | };
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Backoffice/Courses/Application/SearchByCriteria/SearchBackofficeCoursesByCriteriaQuery.php:
--------------------------------------------------------------------------------
1 | filters = $filters;
25 | $this->orderBy = $orderBy;
26 | $this->order = $order;
27 | $this->limit = $limit;
28 | $this->offset = $offset;
29 | }
30 |
31 | public function filters(): array
32 | {
33 | return $this->filters;
34 | }
35 |
36 | public function orderBy(): ?string
37 | {
38 | return $this->orderBy;
39 | }
40 |
41 | public function order(): ?string
42 | {
43 | return $this->order;
44 | }
45 |
46 | public function limit(): ?int
47 | {
48 | return $this->limit;
49 | }
50 |
51 | public function offset(): ?int
52 | {
53 | return $this->offset;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Backoffice/Courses/Application/SearchByCriteria/SearchBackofficeCoursesByCriteriaQueryHandler.php:
--------------------------------------------------------------------------------
1 | searcher = $searcher;
19 | }
20 |
21 | public function __invoke(SearchBackofficeCoursesByCriteriaQuery $query): BackofficeCoursesResponse
22 | {
23 | $filters = Filters::fromValues($query->filters());
24 | $order = Order::fromValues($query->orderBy(), $query->order());
25 |
26 | return $this->searcher->search($filters, $order, $query->limit(), $query->offset());
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Backoffice/Courses/Domain/BackofficeCourse.php:
--------------------------------------------------------------------------------
1 | id = $id;
18 | $this->name = $name;
19 | $this->duration = $duration;
20 | }
21 |
22 | public function toPrimitives(): array
23 | {
24 | return [
25 | 'id' => $this->id,
26 | 'name' => $this->name,
27 | 'duration' => $this->duration,
28 | ];
29 | }
30 |
31 | public static function fromPrimitives(array $primitives): BackofficeCourse
32 | {
33 | return new self($primitives['id'], $primitives['name'], $primitives['duration']);
34 | }
35 |
36 | public function id(): string
37 | {
38 | return $this->id;
39 | }
40 |
41 | public function name(): string
42 | {
43 | return $this->name;
44 | }
45 |
46 | public function duration(): string
47 | {
48 | return $this->duration;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Backoffice/Courses/Domain/BackofficeCourseRepository.php:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/Backoffice/Courses/Infrastructure/Persistence/ElasticsearchBackofficeCourseRepository.php:
--------------------------------------------------------------------------------
1 | persist($course->id(), $course->toPrimitives());
23 | }
24 |
25 | public function searchAll(): array
26 | {
27 | return map($this->toCourse(), $this->searchAllInElastic());
28 | }
29 |
30 | public function matching(Criteria $criteria): array
31 | {
32 | return map($this->toCourse(), $this->searchByCriteria($criteria));
33 | }
34 |
35 | private function toCourse()
36 | {
37 | return static function (array $primitives) {
38 | return BackofficeCourse::fromPrimitives($primitives);
39 | };
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Backoffice/Courses/Infrastructure/Persistence/MySqlBackofficeCourseRepository.php:
--------------------------------------------------------------------------------
1 | persist($course);
18 | }
19 |
20 | public function searchAll(): array
21 | {
22 | return $this->repository(BackofficeCourse::class)->findAll();
23 | }
24 |
25 | public function matching(Criteria $criteria): array
26 | {
27 | $doctrineCriteria = DoctrineCriteriaConverter::convert($criteria);
28 |
29 | return $this->repository(BackofficeCourse::class)->matching($doctrineCriteria)->toArray();
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Backoffice/Shared/Infrastructure/Symfony/DependencyInjection/backoffice_services.yaml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/php-ddd-skeleton-deprecated/d8dec6b574d14071635c041619c8a02ec60b5cb2/src/Backoffice/Shared/Infrastructure/Symfony/DependencyInjection/backoffice_services.yaml
--------------------------------------------------------------------------------
/src/Mooc/Courses/Application/Create/CourseCreator.php:
--------------------------------------------------------------------------------
1 | repository = $repository;
22 | $this->bus = $bus;
23 | }
24 |
25 | public function __invoke(CourseId $id, CourseName $name, CourseDuration $duration)
26 | {
27 | $course = Course::create($id, $name, $duration);
28 |
29 | $this->repository->save($course);
30 | $this->bus->publish(...$course->pullDomainEvents());
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Mooc/Courses/Application/Create/CreateCourseCommand.php:
--------------------------------------------------------------------------------
1 | id = $id;
18 | $this->name = $name;
19 | $this->duration = $duration;
20 | }
21 |
22 | public function id(): string
23 | {
24 | return $this->id;
25 | }
26 |
27 | public function name(): string
28 | {
29 | return $this->name;
30 | }
31 |
32 | public function duration(): string
33 | {
34 | return $this->duration;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Mooc/Courses/Application/Create/CreateCourseCommandHandler.php:
--------------------------------------------------------------------------------
1 | creator = $creator;
19 | }
20 |
21 | public function __invoke(CreateCourseCommand $command)
22 | {
23 | $id = new CourseId($command->id());
24 | $name = new CourseName($command->name());
25 | $duration = new CourseDuration($command->duration());
26 |
27 | $this->creator->__invoke($id, $name, $duration);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Mooc/Courses/Application/Find/CourseFinder.php:
--------------------------------------------------------------------------------
1 | repository = $repository;
19 | }
20 |
21 | public function __invoke(CourseId $id): Course
22 | {
23 | $course = $this->repository->search($id);
24 |
25 | if (null === $course) {
26 | throw new CourseNotExist($id);
27 | }
28 |
29 | return $course;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Mooc/Courses/Application/Update/CourseRenamer.php:
--------------------------------------------------------------------------------
1 | repository = $repository;
22 | $this->finder = new CourseFinder($repository);
23 | $this->bus = $bus;
24 | }
25 |
26 | public function __invoke(CourseId $id, CourseName $newName): void
27 | {
28 | $course = $this->finder->__invoke($id);
29 |
30 | $course->rename($newName);
31 |
32 | $this->repository->save($course);
33 | $this->bus->publish(...$course->pullDomainEvents());
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Mooc/Courses/Domain/Course.php:
--------------------------------------------------------------------------------
1 | id = $id;
19 | $this->name = $name;
20 | $this->duration = $duration;
21 | }
22 |
23 | public static function create(CourseId $id, CourseName $name, CourseDuration $duration): self
24 | {
25 | $course = new self($id, $name, $duration);
26 |
27 | $course->record(new CourseCreatedDomainEvent($id->value(), $name->value(), $duration->value()));
28 |
29 | return $course;
30 | }
31 |
32 | public function id(): CourseId
33 | {
34 | return $this->id;
35 | }
36 |
37 | public function name(): CourseName
38 | {
39 | return $this->name;
40 | }
41 |
42 | public function duration(): CourseDuration
43 | {
44 | return $this->duration;
45 | }
46 |
47 | public function rename(CourseName $newName): void
48 | {
49 | $this->name = $newName;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Mooc/Courses/Domain/CourseCreatedDomainEvent.php:
--------------------------------------------------------------------------------
1 | name = $name;
24 | $this->duration = $duration;
25 | }
26 |
27 | public static function eventName(): string
28 | {
29 | return 'course.created';
30 | }
31 |
32 | public function toPrimitives(): array
33 | {
34 | return [
35 | 'name' => $this->name,
36 | 'duration' => $this->duration,
37 | ];
38 | }
39 |
40 | public static function fromPrimitives(
41 | string $aggregateId,
42 | array $body,
43 | string $eventId,
44 | string $occurredOn
45 | ): DomainEvent {
46 | return new self($aggregateId, $body['name'], $body['duration'], $eventId, $occurredOn);
47 | }
48 |
49 | public function name(): string
50 | {
51 | return $this->name;
52 | }
53 |
54 | public function duration(): string
55 | {
56 | return $this->duration;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Mooc/Courses/Domain/CourseDuration.php:
--------------------------------------------------------------------------------
1 | id = $id;
17 |
18 | parent::__construct();
19 | }
20 |
21 | public function errorCode(): string
22 | {
23 | return 'course_not_exist';
24 | }
25 |
26 | protected function errorMessage(): string
27 | {
28 | return sprintf('The course <%s> does not exist', $this->id->value());
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Mooc/Courses/Domain/CourseRepository.php:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/Mooc/Courses/Infrastructure/Persistence/Doctrine/CourseDuration.orm.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/Mooc/Courses/Infrastructure/Persistence/Doctrine/CourseIdType.php:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/Mooc/Courses/Infrastructure/Persistence/DoctrineCourseRepository.php:
--------------------------------------------------------------------------------
1 | persist($course);
17 | }
18 |
19 | public function search(CourseId $id): ?Course
20 | {
21 | return $this->repository(Course::class)->find($id);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Mooc/Courses/Infrastructure/Persistence/Eloquent/CourseEloquentModel.php:
--------------------------------------------------------------------------------
1 | id = $course->id()->value();
20 | $model->name = $course->name()->value();
21 | $model->duration = $course->duration()->value();
22 |
23 | $model->save();
24 | }
25 |
26 | public function search(CourseId $id): ?Course
27 | {
28 | $model = CourseEloquentModel::find($id->value());
29 |
30 | if (null === $model) {
31 | return null;
32 | }
33 |
34 | return new Course(new CourseId($model->id), new CourseName($model->name), new CourseDuration($model->duration));
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Mooc/Courses/Infrastructure/Persistence/FileCourseRepository.php:
--------------------------------------------------------------------------------
1 | fileName($course->id()->value()), serialize($course));
18 | }
19 |
20 | public function search(CourseId $id): ?Course
21 | {
22 | return file_exists($this->fileName($id->value()))
23 | ? unserialize(file_get_contents($this->fileName($id->value())))
24 | : null;
25 | }
26 |
27 | private function fileName(string $id): string
28 | {
29 | return sprintf('%s.%s.repo', self::FILE_PATH, $id);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Mooc/CoursesCounter/Application/Find/CoursesCounterFinder.php:
--------------------------------------------------------------------------------
1 | repository = $repository;
17 | }
18 |
19 | public function __invoke(): CoursesCounterResponse
20 | {
21 | $counter = $this->repository->search();
22 |
23 | if (null === $counter) {
24 | throw new CoursesCounterNotExist();
25 | }
26 |
27 | return new CoursesCounterResponse($counter->total()->value());
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Mooc/CoursesCounter/Application/Find/CoursesCounterResponse.php:
--------------------------------------------------------------------------------
1 | total = $total;
16 | }
17 |
18 | public function total(): int
19 | {
20 | return $this->total;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Mooc/CoursesCounter/Application/Find/FindCoursesCounterQuery.php:
--------------------------------------------------------------------------------
1 | finder = $finder;
16 | }
17 |
18 | public function __invoke(FindCoursesCounterQuery $query): CoursesCounterResponse
19 | {
20 | return $this->finder->__invoke();
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Mooc/CoursesCounter/Application/Increment/CoursesCounterIncrementer.php:
--------------------------------------------------------------------------------
1 | repository = $repository;
26 | $this->uuidGenerator = $uuidGenerator;
27 | $this->bus = $bus;
28 | }
29 |
30 | public function __invoke(CourseId $courseId)
31 | {
32 | $counter = $this->repository->search() ?: $this->initializeCounter();
33 |
34 | if (!$counter->hasIncremented($courseId)) {
35 | $counter->increment($courseId);
36 |
37 | $this->repository->save($counter);
38 | $this->bus->publish(...$counter->pullDomainEvents());
39 | }
40 | }
41 |
42 | private function initializeCounter(): CoursesCounter
43 | {
44 | return CoursesCounter::initialize(new CoursesCounterId($this->uuidGenerator->generate()));
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Mooc/CoursesCounter/Application/Increment/IncrementCoursesCounterOnCourseCreated.php:
--------------------------------------------------------------------------------
1 | incrementer = $incrementer;
19 | }
20 |
21 | public static function subscribedTo(): array
22 | {
23 | return [CourseCreatedDomainEvent::class];
24 | }
25 |
26 | public function __invoke(CourseCreatedDomainEvent $event): void
27 | {
28 | $courseId = new CourseId($event->aggregateId());
29 |
30 | apply($this->incrementer, [$courseId]);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Mooc/CoursesCounter/Domain/CoursesCounterId.php:
--------------------------------------------------------------------------------
1 | total = $total;
18 | }
19 |
20 | public static function eventName(): string
21 | {
22 | return 'courses_counter.incremented';
23 | }
24 |
25 | public function toPrimitives(): array
26 | {
27 | return [
28 | 'total' => $this->total
29 | ];
30 | }
31 |
32 | public static function fromPrimitives(
33 | string $aggregateId,
34 | array $body,
35 | string $eventId,
36 | string $occurredOn
37 | ): DomainEvent {
38 | return new self($aggregateId, $body['total'], $eventId, $occurredOn);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Mooc/CoursesCounter/Domain/CoursesCounterNotExist.php:
--------------------------------------------------------------------------------
1 | value() + 1);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Mooc/CoursesCounter/Infrastructure/Persistence/Doctrine/CourseCounterIdType.php:
--------------------------------------------------------------------------------
1 | values(), $value), $platform);
29 | }
30 |
31 | public function convertToPHPValue($value, AbstractPlatform $platform)
32 | {
33 | $scalars = parent::convertToPHPValue($value, $platform);
34 |
35 | return map($this->toCourseId(), $scalars);
36 | }
37 |
38 | private function values()
39 | {
40 | return static function (CourseId $id) {
41 | return $id->value();
42 | };
43 | }
44 |
45 | private function toCourseId()
46 | {
47 | return static function (string $value) {
48 | return new CourseId($value);
49 | };
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Mooc/CoursesCounter/Infrastructure/Persistence/Doctrine/CoursesCounter.orm.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/Mooc/CoursesCounter/Infrastructure/Persistence/Doctrine/CoursesCounterTotal.orm.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/Mooc/CoursesCounter/Infrastructure/Persistence/DoctrineCoursesCounterRepository.php:
--------------------------------------------------------------------------------
1 | persist($counter);
16 | }
17 |
18 | public function search(): ?CoursesCounter
19 | {
20 | return $this->repository(CoursesCounter::class)->findOneBy([]);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Mooc/Notifications/Application/SendNewCommentReplyEmail/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/php-ddd-skeleton-deprecated/d8dec6b574d14071635c041619c8a02ec60b5cb2/src/Mooc/Notifications/Application/SendNewCommentReplyEmail/.gitkeep
--------------------------------------------------------------------------------
/src/Mooc/Notifications/Application/SendNewCommentReplyPush/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/php-ddd-skeleton-deprecated/d8dec6b574d14071635c041619c8a02ec60b5cb2/src/Mooc/Notifications/Application/SendNewCommentReplyPush/.gitkeep
--------------------------------------------------------------------------------
/src/Mooc/Notifications/Application/SendResetPasswordEmail/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/php-ddd-skeleton-deprecated/d8dec6b574d14071635c041619c8a02ec60b5cb2/src/Mooc/Notifications/Application/SendResetPasswordEmail/.gitkeep
--------------------------------------------------------------------------------
/src/Mooc/Shared/Domain/Course/CourseId.php:
--------------------------------------------------------------------------------
1 | domainEvents;
17 | $this->domainEvents = [];
18 |
19 | return $domainEvents;
20 | }
21 |
22 | final protected function record(DomainEvent $domainEvent): void
23 | {
24 | $this->domainEvents[] = $domainEvent;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Shared/Domain/Assert.php:
--------------------------------------------------------------------------------
1 | is not an instance of <%s>', $class, get_class($item))
23 | );
24 | }
25 | }
26 |
27 | public static function money($value): void
28 | {
29 | if (!self::isValidMoneyAmount($value)) {
30 | throw new InvalidArgumentException(sprintf('The value <%s> is not a valid money amount', $value));
31 | }
32 | }
33 |
34 | private static function isValidMoneyAmount($value): bool
35 | {
36 | return is_numeric($value) && $value >= 0;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Shared/Domain/Bus/Command/Command.php:
--------------------------------------------------------------------------------
1 | aggregateId = $aggregateId;
20 | $this->eventId = $eventId ?: Uuid::random()->value();
21 | $this->occurredOn = $occurredOn ?: Utils::dateToString(new DateTimeImmutable());
22 | }
23 |
24 | abstract public static function eventName(): string;
25 |
26 | abstract public function toPrimitives(): array;
27 |
28 | abstract public static function fromPrimitives(
29 | string $aggregateId,
30 | array $body,
31 | string $eventId,
32 | string $occurredOn
33 | ): self;
34 |
35 | public function aggregateId(): string
36 | {
37 | return $this->aggregateId;
38 | }
39 |
40 | public function eventId(): string
41 | {
42 | return $this->eventId;
43 | }
44 |
45 | public function occurredOn(): string
46 | {
47 | return $this->occurredOn;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Shared/Domain/Bus/Event/DomainEventSubscriber.php:
--------------------------------------------------------------------------------
1 | type(), $items);
20 |
21 | $this->items = $items;
22 | }
23 |
24 | abstract protected function type(): string;
25 |
26 | public function getIterator()
27 | {
28 | return new ArrayIterator($this->items());
29 | }
30 |
31 | public function count()
32 | {
33 | return count($this->items());
34 | }
35 |
36 | protected function each(callable $fn)
37 | {
38 | each($fn, $this->items());
39 | }
40 |
41 | protected function items()
42 | {
43 | return $this->items;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Shared/Domain/Criteria/Criteria.php:
--------------------------------------------------------------------------------
1 | filters = $filters;
17 | $this->order = $order;
18 | $this->offset = $offset;
19 | $this->limit = $limit;
20 | }
21 |
22 | public function hasFilters(): bool
23 | {
24 | return $this->filters->count() > 0;
25 | }
26 |
27 | public function hasOrder(): bool
28 | {
29 | return !$this->order->isNone();
30 | }
31 |
32 | public function plainFilters(): array
33 | {
34 | return $this->filters->filters();
35 | }
36 |
37 | public function filters(): Filters
38 | {
39 | return $this->filters;
40 | }
41 |
42 | public function order(): Order
43 | {
44 | return $this->order;
45 | }
46 |
47 | public function offset(): ?int
48 | {
49 | return $this->offset;
50 | }
51 |
52 | public function limit(): ?int
53 | {
54 | return $this->limit;
55 | }
56 |
57 | public function serialize(): string
58 | {
59 | return sprintf(
60 | '%s~~%s~~%s~~%s',
61 | $this->filters->serialize(),
62 | $this->order->serialize(),
63 | $this->offset,
64 | $this->limit
65 | );
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/Shared/Domain/Criteria/Filter.php:
--------------------------------------------------------------------------------
1 | field = $field;
16 | $this->operator = $operator;
17 | $this->value = $value;
18 | }
19 |
20 | public static function fromValues(array $values): self
21 | {
22 | return new self(
23 | new FilterField($values['field']),
24 | new FilterOperator($values['operator']),
25 | new FilterValue($values['value'])
26 | );
27 | }
28 |
29 | public function field(): FilterField
30 | {
31 | return $this->field;
32 | }
33 |
34 | public function operator(): FilterOperator
35 | {
36 | return $this->operator;
37 | }
38 |
39 | public function value(): FilterValue
40 | {
41 | return $this->value;
42 | }
43 |
44 | public function serialize(): string
45 | {
46 | return sprintf('%s.%s.%s', $this->field->value(), $this->operator->value(), $this->value->value());
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Shared/Domain/Criteria/FilterField.php:
--------------------------------------------------------------------------------
1 | ';
20 | public const LT = '<';
21 | public const CONTAINS = 'CONTAINS';
22 | public const NOT_CONTAINS = 'NOT_CONTAINS';
23 |
24 | private static $containing = [self::CONTAINS, self::NOT_CONTAINS];
25 |
26 | public static function equal(): self
27 | {
28 | return new self('=');
29 | }
30 |
31 | public function isContaining(): bool
32 | {
33 | return in_array($this->value(), self::$containing, true);
34 | }
35 |
36 | protected function throwExceptionForInvalidValue($value): void
37 | {
38 | throw new InvalidArgumentException(sprintf('The filter <%s> is invalid', $value));
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Shared/Domain/Criteria/FilterValue.php:
--------------------------------------------------------------------------------
1 | items(), [$filter]));
25 | }
26 |
27 | public function filters(): array
28 | {
29 | return $this->items();
30 | }
31 |
32 | public function serialize(): string
33 | {
34 | return reduce(
35 | static function (string $accumulate, Filter $filter) {
36 | return sprintf('%s^%s', $accumulate, $filter->serialize());
37 | },
38 | $this->items(),
39 | ''
40 | );
41 | }
42 |
43 | private static function filterBuilder(): callable
44 | {
45 | return function (array $values) {
46 | return Filter::fromValues($values);
47 | };
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Shared/Domain/Criteria/Order.php:
--------------------------------------------------------------------------------
1 | orderBy = $orderBy;
15 | $this->orderType = $orderType;
16 | }
17 |
18 | public static function createDesc(OrderBy $orderBy): Order
19 | {
20 | return new self($orderBy, OrderType::desc());
21 | }
22 |
23 | public function orderBy(): OrderBy
24 | {
25 | return $this->orderBy;
26 | }
27 |
28 | public function orderType(): OrderType
29 | {
30 | return $this->orderType;
31 | }
32 |
33 | public static function fromValues(?string $orderBy, ?string $order): Order
34 | {
35 | return null === $orderBy ? self::none() : new Order(new OrderBy($orderBy), new OrderType($order));
36 | }
37 |
38 | public function isNone(): bool
39 | {
40 | return $this->orderType()->isNone();
41 | }
42 |
43 | public static function none(): Order
44 | {
45 | return new Order(new OrderBy(''), OrderType::none());
46 | }
47 |
48 | public function serialize(): string
49 | {
50 | return sprintf('%s.%s', $this->orderBy->value(), $this->orderType->value());
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Shared/Domain/Criteria/OrderBy.php:
--------------------------------------------------------------------------------
1 | equals(self::none());
24 | }
25 |
26 | protected function throwExceptionForInvalidValue($value): void
27 | {
28 | throw new InvalidArgumentException($value);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Shared/Domain/DomainError.php:
--------------------------------------------------------------------------------
1 | errorMessage());
14 | }
15 |
16 | abstract public function errorCode(): string;
17 |
18 | abstract protected function errorMessage(): string;
19 | }
20 |
--------------------------------------------------------------------------------
/src/Shared/Domain/Logger.php:
--------------------------------------------------------------------------------
1 | value = $value;
14 | }
15 |
16 | public function value(): int
17 | {
18 | return $this->value;
19 | }
20 |
21 | public function __toString()
22 | {
23 | return (string) $this->value();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Shared/Domain/ValueObject/StringValueObject.php:
--------------------------------------------------------------------------------
1 | value = $value;
14 | }
15 |
16 | public function value(): string
17 | {
18 | return $this->value;
19 | }
20 |
21 | public function __toString()
22 | {
23 | return $this->value();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Shared/Domain/ValueObject/Uuid.php:
--------------------------------------------------------------------------------
1 | ensureIsValidUuid($value);
17 |
18 | $this->value = $value;
19 | }
20 |
21 | public static function random(): self
22 | {
23 | return new self(RamseyUuid::uuid4()->toString());
24 | }
25 |
26 | public function value(): string
27 | {
28 | return $this->value;
29 | }
30 |
31 | private function ensureIsValidUuid($id): void
32 | {
33 | if (!RamseyUuid::isValid($id)) {
34 | throw new InvalidArgumentException(sprintf('<%s> does not allow the value <%s>.', static::class, $id));
35 | }
36 | }
37 |
38 | public function equals(Uuid $other): bool
39 | {
40 | return $this->value() === $other->value();
41 | }
42 |
43 | public function __toString()
44 | {
45 | return $this->value();
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Shared/Infrastructure/Bus/Command/CommandNotRegisteredError.php:
--------------------------------------------------------------------------------
1 | hasn't a command handler associated");
17 | }
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/src/Shared/Infrastructure/Bus/Command/InMemorySymfonyCommandBus.php:
--------------------------------------------------------------------------------
1 | bus = new MessageBus(
23 | [
24 | new HandleMessageMiddleware(
25 | new HandlersLocator(CallableFirstParameterExtractor::forCallables($commandHandlers))
26 | ),
27 | ]
28 | );
29 | }
30 |
31 | public function dispatch(Command $command): void
32 | {
33 | try {
34 | $this->bus->dispatch($command);
35 | } catch (NoHandlerForMessageException $unused) {
36 | throw new CommandNotRegisteredError($command);
37 | } catch (HandlerFailedException $error) {
38 | throw $error->getPrevious();
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Shared/Infrastructure/Bus/Event/DomainEventJsonDeserializer.php:
--------------------------------------------------------------------------------
1 | mapping = $mapping;
18 | }
19 |
20 | public function deserialize(string $domainEvent): DomainEvent
21 | {
22 | $eventData = Utils::jsonDecode($domainEvent);
23 | $eventName = $eventData['data']['type'];
24 | $eventClass = $this->mapping->for($eventName);
25 |
26 | if (null === $eventClass) {
27 | throw new RuntimeException("The event <$eventName> doesn't exist or has no subscribers");
28 | }
29 |
30 | return $eventClass::fromPrimitives(
31 | $eventData['data']['attributes']['id'],
32 | $eventData['data']['attributes'],
33 | $eventData['data']['id'],
34 | $eventData['data']['occurred_on']
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Shared/Infrastructure/Bus/Event/DomainEventJsonSerializer.php:
--------------------------------------------------------------------------------
1 | [
16 | 'id' => $domainEvent->eventId(),
17 | 'type' => $domainEvent::eventName(),
18 | 'occurred_on' => $domainEvent->occurredOn(),
19 | 'attributes' => array_merge($domainEvent->toPrimitives(), ['id' => $domainEvent->aggregateId()]),
20 | ],
21 | 'meta' => [],
22 | ]
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Shared/Infrastructure/Bus/Event/DomainEventMapping.php:
--------------------------------------------------------------------------------
1 | mapping = reduce($this->eventsExtractor(), $mapping, []);
19 | }
20 |
21 | public function for(string $name)
22 | {
23 | if (!isset($this->mapping[$name])) {
24 | throw new RuntimeException("The Domain Event Class for <$name> doesn't exists or have no subscribers");
25 | }
26 |
27 | return $this->mapping[$name];
28 | }
29 |
30 | public function all()
31 | {
32 | return $this->mapping;
33 | }
34 |
35 | private function eventsExtractor(): callable
36 | {
37 | return function (array $mapping, DomainEventSubscriber $subscriber) {
38 | return array_merge($mapping, reindex($this->eventNameExtractor(), $subscriber::subscribedTo()));
39 | };
40 | }
41 |
42 | private function eventNameExtractor(): callable
43 | {
44 | return static function (string $eventClass): string {
45 | return $eventClass::eventName();
46 | };
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Shared/Infrastructure/Bus/Event/InMemory/InMemorySymfonyEventBus.php:
--------------------------------------------------------------------------------
1 | bus = new MessageBus(
22 | [
23 | new HandleMessageMiddleware(
24 | new HandlersLocator(
25 | CallableFirstParameterExtractor::forPipedCallables($subscribers)
26 | )
27 | ),
28 | ]
29 | );
30 | }
31 |
32 | public function publish(DomainEvent ...$events): void
33 | {
34 | foreach ($events as $event) {
35 | try {
36 | $this->bus->dispatch($event);
37 | } catch (NoHandlerForMessageException $error) {}
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqExchangeNameFormatter.php:
--------------------------------------------------------------------------------
1 | bus = new MessageBus(
24 | [
25 | new HandleMessageMiddleware(
26 | new HandlersLocator(CallableFirstParameterExtractor::forCallables($queryHandlers))
27 | ),
28 | ]
29 | );
30 | }
31 |
32 | public function ask(Query $query): ?Response
33 | {
34 | try {
35 | /** @var HandledStamp $stamp */
36 | $stamp = $this->bus->dispatch($query)->last(HandledStamp::class);
37 |
38 | return $stamp->getResult();
39 | } catch (NoHandlerForMessageException $unused) {
40 | throw new QueryNotRegisteredError($query);
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Shared/Infrastructure/Bus/Query/QueryNotRegisteredError.php:
--------------------------------------------------------------------------------
1 | hasn't a query handler associated");
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Shared/Infrastructure/Doctrine/DatabaseConnections.php:
--------------------------------------------------------------------------------
1 | connections = iterator_to_array($connections);
19 | }
20 |
21 | public function clear(): void
22 | {
23 | each($this->clearer(), $this->connections);
24 | }
25 |
26 | public function allConnectionsClearer(): callable
27 | {
28 | return function (): void {
29 | $this->clear();
30 | };
31 | }
32 |
33 | public function truncate(): void
34 | {
35 | apply(new DatabaseCleaner(), array_values($this->connections));
36 | }
37 |
38 | private function clearer(): callable
39 | {
40 | return static function (EntityManager $entityManager) {
41 | $entityManager->clear();
42 | };
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Shared/Infrastructure/Doctrine/Dbal/DbalCustomTypesRegistrar.php:
--------------------------------------------------------------------------------
1 | client = $client;
17 | $this->indexPrefix = $indexPrefix;
18 | }
19 |
20 | public function persist(string $aggregateName, string $identifier, array $plainBody): void
21 | {
22 | $this->client->index(
23 | [
24 | 'index' => sprintf('%s_%s', $this->indexPrefix, $aggregateName),
25 | 'id' => $identifier,
26 | 'body' => $plainBody,
27 | ]
28 | );
29 | }
30 |
31 | public function client(): Client
32 | {
33 | return $this->client;
34 | }
35 |
36 | public function indexPrefix(): string
37 | {
38 | return $this->indexPrefix;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Shared/Infrastructure/Logger/MonologLogger.php:
--------------------------------------------------------------------------------
1 | logger = $logger;
16 | }
17 |
18 | public function info(string $message, array $context = []): void
19 | {
20 | $this->logger->info($message, $context);
21 | }
22 |
23 | public function warning(string $message, array $context = []): void
24 | {
25 | $this->logger->warning($message, $context);
26 | }
27 |
28 | public function critical(string $message, array $context = []): void
29 | {
30 | $this->logger->critical($message, $context);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Shared/Infrastructure/Persistence/Doctrine/DoctrineRepository.php:
--------------------------------------------------------------------------------
1 | entityManager = $entityManager;
18 | }
19 |
20 | protected function entityManager(): EntityManager
21 | {
22 | return $this->entityManager;
23 | }
24 |
25 | protected function persist(AggregateRoot $entity): void
26 | {
27 | $this->entityManager()->persist($entity);
28 | $this->entityManager()->flush($entity);
29 | }
30 |
31 | protected function remove(AggregateRoot $entity): void
32 | {
33 | $this->entityManager()->remove($entity);
34 | $this->entityManager()->flush($entity);
35 | }
36 |
37 | protected function repository($entityClass): EntityRepository
38 | {
39 | return $this->entityManager->getRepository($entityClass);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Shared/Infrastructure/Persistence/Doctrine/UuidType.php:
--------------------------------------------------------------------------------
1 | typeClassName();
22 |
23 | return new $className($value);
24 | }
25 |
26 | /** @var Uuid $value */
27 | public function convertToDatabaseValue($value, AbstractPlatform $platform)
28 | {
29 | return $value->value();
30 | }
31 |
32 | abstract protected function typeClassName(): string;
33 | }
34 |
--------------------------------------------------------------------------------
/src/Shared/Infrastructure/Persistence/Elasticsearch/ElasticsearchCriteriaConverter.php:
--------------------------------------------------------------------------------
1 | array_merge(
16 | ['from' => $criteria->offset() ?: 0, 'size' => $criteria->limit() ?: 1000],
17 | $this->formatQuery($criteria),
18 | $this->formatSort($criteria)
19 | ),
20 | ];
21 | }
22 |
23 | private function formatQuery(Criteria $criteria): array
24 | {
25 | if ($criteria->hasFilters()) {
26 | $multipleFilters = 1 < $criteria->filters()->count();
27 |
28 | return [
29 | 'query' => [
30 | 'bool' => reduce(new ElasticQueryGenerator(), $criteria->filters(), []),
31 | ],
32 | ];
33 | }
34 |
35 | return [];
36 | }
37 |
38 | private function formatSort(Criteria $criteria): array
39 | {
40 | if ($criteria->hasOrder()) {
41 | $order = $criteria->order();
42 |
43 | return ['sort' => [$order->orderBy()->value() => ['order' => $order->orderType()->value()]]];
44 | }
45 |
46 | return [];
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Shared/Infrastructure/PhpRandomNumberGenerator.php:
--------------------------------------------------------------------------------
1 | toString();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Shared/Infrastructure/Symfony/AddJsonBodyToRequestListener.php:
--------------------------------------------------------------------------------
1 | getRequest();
17 | $requestContents = $request->getContent();
18 |
19 | if (!empty($requestContents) && $this->containsHeader($request, 'Content-Type', 'application/json')) {
20 | $jsonData = json_decode($requestContents, true);
21 | if (!$jsonData) {
22 | throw new HttpException(Response::HTTP_BAD_REQUEST, 'Invalid json data');
23 | }
24 | $jsonDataLowerCase = [];
25 | foreach ($jsonData as $key => $value) {
26 | $jsonDataLowerCase[preg_replace_callback(
27 | '/_(.)/',
28 | static function ($matches) {
29 | return strtoupper($matches[1]);
30 | },
31 | $key
32 | )] = $value;
33 | }
34 | $request->request->replace($jsonDataLowerCase);
35 | }
36 | }
37 |
38 | private function containsHeader(Request $request, $name, $value): bool
39 | {
40 | return 0 === strpos($request->headers->get($name), $value);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Shared/Infrastructure/Symfony/ApiController.php:
--------------------------------------------------------------------------------
1 | queryBus = $queryBus;
26 | $this->commandBus = $commandBus;
27 | $this->exceptionHandler = $exceptionHandler;
28 |
29 | each($this->exceptionRegistrar(), $this->exceptions());
30 | }
31 |
32 | abstract protected function exceptions(): array;
33 |
34 | protected function ask(Query $query): ?Response
35 | {
36 | return $this->queryBus->ask($query);
37 | }
38 |
39 | protected function dispatch(Command $command): void
40 | {
41 | $this->commandBus->dispatch($command);
42 | }
43 |
44 | private function exceptionRegistrar(): callable
45 | {
46 | return function ($httpCode, $exception): void {
47 | $this->exceptionHandler->register($exception, $httpCode);
48 | };
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Shared/Infrastructure/Symfony/ApiExceptionListener.php:
--------------------------------------------------------------------------------
1 | exceptionHandler = $exceptionHandler;
20 | }
21 |
22 | public function onException(RequestEvent $event): void
23 | {
24 | $exception = $event->getException();
25 |
26 | $event->setResponse(
27 | new JsonResponse(
28 | [
29 | 'code' => $this->exceptionCodeFor($exception),
30 | 'message' => $exception->getMessage(),
31 | ],
32 | $this->exceptionHandler->statusCodeFor(get_class($exception))
33 | )
34 | );
35 | }
36 |
37 | private function exceptionCodeFor(Exception $error)
38 | {
39 | $domainErrorClass = DomainError::class;
40 |
41 | return $error instanceof $domainErrorClass ? $error->errorCode() : Utils::toSnakeCase(class_basename($error));
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Shared/Infrastructure/Symfony/ApiExceptionsHttpStatusCodeMapping.php:
--------------------------------------------------------------------------------
1 | Response::HTTP_BAD_REQUEST,
18 | NotFoundHttpException::class => Response::HTTP_NOT_FOUND,
19 | ];
20 |
21 | public function register($exceptionClass, $statusCode): void
22 | {
23 | $this->exceptions[$exceptionClass] = $statusCode;
24 | }
25 |
26 | public function exists($exceptionClass): bool
27 | {
28 | return array_key_exists($exceptionClass, $this->exceptions);
29 | }
30 |
31 | public function statusCodeFor($exceptionClass): int
32 | {
33 | return get($exceptionClass, $this->exceptions, self::DEFAULT_STATUS_CODE);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Shared/Infrastructure/Symfony/FlashSession.php:
--------------------------------------------------------------------------------
1 | getFlashBag()->all());
17 | }
18 |
19 | public function get(string $key, $default = null)
20 | {
21 | if (array_key_exists($key, self::$flashes)) {
22 | return self::$flashes[$key];
23 | }
24 |
25 | if (array_key_exists($key . '.0', self::$flashes)) {
26 | return self::$flashes[$key . '.0'];
27 | }
28 |
29 | if (array_key_exists($key . '.0.0', self::$flashes)) {
30 | return self::$flashes[$key . '.0.0'];
31 | }
32 |
33 | return $default;
34 | }
35 |
36 | public function has(string $key): bool
37 | {
38 | return array_key_exists($key, self::$flashes) ||
39 | array_key_exists($key . '.0', self::$flashes) ||
40 | array_key_exists($key . '.0.0', self::$flashes);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tests/apps/backoffice/backend/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/php-ddd-skeleton-deprecated/d8dec6b574d14071635c041619c8a02ec60b5cb2/tests/apps/backoffice/backend/.gitkeep
--------------------------------------------------------------------------------
/tests/apps/backoffice/frontend/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/php-ddd-skeleton-deprecated/d8dec6b574d14071635c041619c8a02ec60b5cb2/tests/apps/backoffice/frontend/.gitkeep
--------------------------------------------------------------------------------
/tests/apps/mooc/backend/features/courses/course_put.feature:
--------------------------------------------------------------------------------
1 | Feature: Create a new course
2 | In order to have courses on the platform
3 | As a user with admin permissions
4 | I want to create a new course
5 |
6 | Scenario: A valid non existing course
7 | Given I send a PUT request to "/courses/1aab45ba-3c7a-4344-8936-78466eca77fa" with body:
8 | """
9 | {
10 | "name": "The best course",
11 | "duration": "5 hours"
12 | }
13 | """
14 | Then the response status code should be 201
15 | And the response should be empty
16 |
--------------------------------------------------------------------------------
/tests/apps/mooc/backend/features/health_check/health_check_get.feature:
--------------------------------------------------------------------------------
1 | Feature: Api status
2 | In order to know the server is up and running
3 | As a health check
4 | I want to check the api status
5 |
6 | Scenario: Check the api status
7 | Given I send a GET request to "/health-check"
8 | Then the response content should be:
9 | """
10 | {
11 | "mooc-backend": "ok",
12 | "rand": 1
13 | }
14 | """
15 |
--------------------------------------------------------------------------------
/tests/apps/mooc/backend/mooc_backend.yml:
--------------------------------------------------------------------------------
1 | mooc_backend:
2 | extensions:
3 | FriendsOfBehat\SymfonyExtension:
4 | kernel:
5 | class: CodelyTv\Apps\Mooc\Backend\MoocBackendKernel
6 | bootstrap: apps/bootstrap.php
7 | Behat\MinkExtension:
8 | sessions:
9 | symfony:
10 | symfony: ~
11 | base_url: ''
12 |
13 | suites:
14 | health_check:
15 | paths: [ tests/apps/mooc/backend/features/health_check ]
16 | contexts:
17 | - CodelyTv\Tests\Shared\Infrastructure\Behat\ApiContext
18 |
19 | courses:
20 | paths: [ tests/apps/mooc/backend/features/courses ]
21 | contexts:
22 | - CodelyTv\Tests\Shared\Infrastructure\Behat\ApplicationFeatureContext
23 | - CodelyTv\Tests\Shared\Infrastructure\Behat\ApiContext
24 |
25 | courses_counter:
26 | paths: [ tests/apps/mooc/backend/features/courses_counter ]
27 | contexts:
28 | - CodelyTv\Tests\Shared\Infrastructure\Behat\ApplicationFeatureContext
29 | - CodelyTv\Tests\Shared\Infrastructure\Behat\ApiContext
30 |
--------------------------------------------------------------------------------
/tests/apps/mooc/frontend/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/php-ddd-skeleton-deprecated/d8dec6b574d14071635c041619c8a02ec60b5cb2/tests/apps/mooc/frontend/.gitkeep
--------------------------------------------------------------------------------
/tests/src/Backoffice/Auth/Application/Authenticate/AuthenticateUserCommandMother.php:
--------------------------------------------------------------------------------
1 | value(), $password->value());
18 | }
19 |
20 | public static function random(): AuthenticateUserCommand
21 | {
22 | return self::create(AuthUsernameMother::random(), AuthPasswordMother::random());
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tests/src/Backoffice/Auth/AuthModuleUnitTestCase.php:
--------------------------------------------------------------------------------
1 | repository()
20 | ->shouldReceive('search')
21 | ->with($this->similarTo($username))
22 | ->once()
23 | ->andReturn($authUser);
24 | }
25 |
26 | /** @return AuthRepository|MockInterface */
27 | protected function repository(): MockInterface
28 | {
29 | return $this->repository = $this->repository ?: $this->mock(AuthRepository::class);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/src/Backoffice/Auth/Domain/AuthPasswordMother.php:
--------------------------------------------------------------------------------
1 | username()),
23 | AuthPasswordMother::create($command->password())
24 | );
25 | }
26 |
27 | public static function withUsername(AuthUsername $username): AuthUser
28 | {
29 | return self::create($username, AuthPasswordMother::random());
30 | }
31 |
32 | public static function random(): AuthUser
33 | {
34 | return self::create(AuthUsernameMother::random(), AuthPasswordMother::random());
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tests/src/Backoffice/Auth/Domain/AuthUsernameMother.php:
--------------------------------------------------------------------------------
1 | service(EntityManager::class));
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/tests/src/Backoffice/Courses/Domain/BackofficeCourseCriteriaMother.php:
--------------------------------------------------------------------------------
1 | 'name',
22 | 'operator' => 'CONTAINS',
23 | 'value' => $text,
24 | ]
25 | )
26 | )
27 | );
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tests/src/Backoffice/Courses/Domain/BackofficeCourseMother.php:
--------------------------------------------------------------------------------
1 | value(),
23 | $name,
24 | CourseDurationMother::random()->value()
25 | );
26 | }
27 |
28 | public static function random(): BackofficeCourse
29 | {
30 | return self::create(
31 | CourseIdMother::random()->value(),
32 | CourseNameMother::random()->value(),
33 | CourseDurationMother::random()->value()
34 | );
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tests/src/Mooc/Courses/Application/Create/CreateCourseCommandHandlerTest.php:
--------------------------------------------------------------------------------
1 | handler = new CreateCourseCommandHandler(new CourseCreator($this->repository(), $this->eventBus()));
22 | }
23 |
24 | /** @test */
25 | public function it_should_create_a_valid_course(): void
26 | {
27 | $command = CreateCourseCommandMother::random();
28 |
29 | $course = CourseMother::fromRequest($command);
30 | $domainEvent = CourseCreatedDomainEventMother::fromCourse($course);
31 |
32 | $this->shouldSave($course);
33 | $this->shouldPublishDomainEvent($domainEvent);
34 |
35 | $this->dispatch($command, $this->handler);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/tests/src/Mooc/Courses/Application/Create/CreateCourseCommandMother.php:
--------------------------------------------------------------------------------
1 | value(), $name->value(), $duration->value());
20 | }
21 |
22 | public static function random(): CreateCourseCommand
23 | {
24 | return self::create(CourseIdMother::random(), CourseNameMother::random(), CourseDurationMother::random());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/src/Mooc/Courses/CoursesModuleInfrastructureTestCase.php:
--------------------------------------------------------------------------------
1 | service(CourseRepository::class);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/src/Mooc/Courses/CoursesModuleUnitTestCase.php:
--------------------------------------------------------------------------------
1 | repository()
20 | ->shouldReceive('save')
21 | ->with($this->similarTo($course))
22 | ->once()
23 | ->andReturnNull();
24 | }
25 |
26 | protected function shouldSearch(CourseId $id, ?Course $course): void
27 | {
28 | $this->repository()
29 | ->shouldReceive('search')
30 | ->with($this->similarTo($id))
31 | ->once()
32 | ->andReturn($course);
33 | }
34 |
35 | /** @return CourseRepository|MockInterface */
36 | protected function repository(): MockInterface
37 | {
38 | return $this->repository = $this->repository ?: $this->mock(CourseRepository::class);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/src/Mooc/Courses/Domain/CourseCreatedDomainEventMother.php:
--------------------------------------------------------------------------------
1 | value(), $name->value(), $duration->value());
18 | }
19 |
20 | public static function fromCourse(Course $course): CourseCreatedDomainEvent
21 | {
22 | return self::create($course->id(), $course->name(), $course->duration());
23 | }
24 |
25 | public static function random(): CourseCreatedDomainEvent
26 | {
27 | return self::create(CourseIdMother::random(), CourseNameMother::random(), CourseDurationMother::random());
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tests/src/Mooc/Courses/Domain/CourseDurationMother.php:
--------------------------------------------------------------------------------
1 | id()),
24 | CourseNameMother::create($request->name()),
25 | CourseDurationMother::create($request->duration())
26 | );
27 | }
28 |
29 | public static function random(): Course
30 | {
31 | return self::create(CourseIdMother::random(), CourseNameMother::random(), CourseDurationMother::random());
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tests/src/Mooc/Courses/Domain/CourseNameMother.php:
--------------------------------------------------------------------------------
1 | repository()->save($course);
19 | }
20 |
21 | /** @test */
22 | public function it_should_return_an_existing_course(): void
23 | {
24 | $course = CourseMother::random();
25 |
26 | $this->repository()->save($course);
27 |
28 | $this->assertEquals($course, $this->repository()->search($course->id()));
29 | }
30 |
31 | /** @test */
32 | public function it_should_not_return_a_non_existing_course(): void
33 | {
34 | $this->assertNull($this->repository()->search(CourseIdMother::random()));
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tests/src/Mooc/CoursesCounter/Application/Find/CoursesCounterResponseMother.php:
--------------------------------------------------------------------------------
1 | value());
16 | }
17 |
18 | public static function random(): CoursesCounterResponse
19 | {
20 | return self::create(CoursesCounterTotalMother::random());
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/src/Mooc/CoursesCounter/CoursesCounterModuleUnitTestCase.php:
--------------------------------------------------------------------------------
1 | repository()
19 | ->shouldReceive('save')
20 | ->once()
21 | ->with($this->similarTo($course))
22 | ->andReturnNull();
23 | }
24 |
25 | protected function shouldSearch(?CoursesCounter $counter): void
26 | {
27 | $this->repository()
28 | ->shouldReceive('search')
29 | ->once()
30 | ->andReturn($counter);
31 | }
32 |
33 | /** @return CoursesCounterRepository|MockInterface */
34 | protected function repository(): MockInterface
35 | {
36 | return $this->repository = $this->repository ?: $this->mock(CoursesCounterRepository::class);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tests/src/Mooc/CoursesCounter/Domain/CoursesCounterIdMother.php:
--------------------------------------------------------------------------------
1 | value(), $total->value());
19 | }
20 |
21 | public static function fromCounter(CoursesCounter $counter): CoursesCounterIncrementedDomainEvent
22 | {
23 | return self::create($counter->id(), $counter->total());
24 | }
25 |
26 | public static function random(): CoursesCounterIncrementedDomainEvent
27 | {
28 | return self::create(CoursesCounterIdMother::random(), CoursesCounterTotalMother::random());
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/src/Mooc/CoursesCounter/Domain/CoursesCounterTotalMother.php:
--------------------------------------------------------------------------------
1 | service(EntityManager::class));
17 |
18 | $arranger->arrange();
19 | }
20 |
21 | protected function tearDown(): void
22 | {
23 | $arranger = new MoocEnvironmentArranger($this->service(EntityManager::class));
24 |
25 | $arranger->close();
26 |
27 | parent::tearDown();
28 | }
29 |
30 | protected function clearUnitOfWork()
31 | {
32 | $this->service(EntityManager::class)->clear();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tests/src/Mooc/Shared/Infrastructure/PhpUnit/MoocEnvironmentArranger.php:
--------------------------------------------------------------------------------
1 | entityManager = $entityManager;
19 | }
20 |
21 | public function arrange(): void
22 | {
23 | apply(new DatabaseCleaner(), [$this->entityManager]);
24 | }
25 |
26 | public function close(): void
27 | {
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tests/src/Mooc/Videos/Application/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/php-ddd-skeleton-deprecated/d8dec6b574d14071635c041619c8a02ec60b5cb2/tests/src/Mooc/Videos/Application/.gitkeep
--------------------------------------------------------------------------------
/tests/src/Mooc/Videos/Domain/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/php-ddd-skeleton-deprecated/d8dec6b574d14071635c041619c8a02ec60b5cb2/tests/src/Mooc/Videos/Domain/.gitkeep
--------------------------------------------------------------------------------
/tests/src/Mooc/Videos/Infrastructure/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/php-ddd-skeleton-deprecated/d8dec6b574d14071635c041619c8a02ec60b5cb2/tests/src/Mooc/Videos/Infrastructure/.gitkeep
--------------------------------------------------------------------------------
/tests/src/Shared/Domain/Criteria/CriteriaMother.php:
--------------------------------------------------------------------------------
1 | getName()])) {
21 | $property->setAccessible(true);
22 | $property->setValue($duplicated, $newParams[$property->getName()]);
23 | }
24 | },
25 | $reflection->getProperties()
26 | );
27 |
28 | return $duplicated;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/src/Shared/Domain/IntegerMother.php:
--------------------------------------------------------------------------------
1 | numberBetween($min, $max);
12 | }
13 |
14 | public static function lessThan($max): int
15 | {
16 | return self::between(1, $max);
17 | }
18 |
19 | public static function moreThan($min): int
20 | {
21 | return self::between($min);
22 | }
23 |
24 | public static function random(): int
25 | {
26 | return self::between(1);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/src/Shared/Domain/MotherCreator.php:
--------------------------------------------------------------------------------
1 | randomElement($elements);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/tests/src/Shared/Domain/Repeater.php:
--------------------------------------------------------------------------------
1 | evaluate($actual, '', true);
17 | }
18 |
19 | public static function assertSimilar($expected, $actual): void
20 | {
21 | $constraint = new CodelyTvConstraintIsSimilar($expected);
22 |
23 | $constraint->evaluate($actual);
24 | }
25 |
26 | public static function similarTo($value, $delta = 0.0): CodelyTvMatcherIsSimilar
27 | {
28 | return new CodelyTvMatcherIsSimilar($value, $delta);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/src/Shared/Domain/UuidMother.php:
--------------------------------------------------------------------------------
1 | unique()->uuid;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/tests/src/Shared/Domain/WordMother.php:
--------------------------------------------------------------------------------
1 | word;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/tests/src/Shared/Infrastructure/Arranger/EnvironmentArranger.php:
--------------------------------------------------------------------------------
1 | connections = $connections;
25 | $this->bus = $bus;
26 | $this->deserializer = $deserializer;
27 | }
28 |
29 | /** @BeforeScenario */
30 | public function cleanEnvironment(): void
31 | {
32 | $this->connections->clear();
33 | $this->connections->truncate();
34 | }
35 |
36 | /**
37 | * @Given /^I send an event to the event bus:$/
38 | */
39 | public function iSendAnEventToTheEventBus(PyStringNode $event)
40 | {
41 | $domainEvent = $this->deserializer->deserialize($event->getRaw());
42 |
43 | $this->bus->publish($domainEvent);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/tests/src/Shared/Infrastructure/Bus/Command/FakeCommand.php:
--------------------------------------------------------------------------------
1 | commandBus = new InMemorySymfonyCommandBus([$this->commandHandler()]);
22 | }
23 |
24 | /** @test */
25 | public function it_should_be_able_to_handle_a_command(): void
26 | {
27 | $this->expectException(RuntimeException::class);
28 |
29 | $this->commandBus->dispatch(new FakeCommand());
30 | }
31 |
32 | /** @test */
33 | public function it_should_raise_an_exception_dispatching_a_non_registered_command(): void
34 | {
35 | $this->expectException(CommandNotRegisteredError::class);
36 |
37 | $this->commandBus->dispatch($this->mock(Command::class));
38 | }
39 |
40 | private function commandHandler()
41 | {
42 | return new class
43 | {
44 | public function __invoke(FakeCommand $command)
45 | {
46 | throw new RuntimeException('This works fine!');
47 | }
48 | };
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/tests/src/Shared/Infrastructure/Bus/Event/RabbitMq/TestAllWorksOnRabbitMqEventsPublished.php:
--------------------------------------------------------------------------------
1 | number = $number;
16 | }
17 |
18 | public function number(): int
19 | {
20 | return $this->number;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/src/Shared/Infrastructure/Bus/Query/InMemorySymfonyQueryBusTest.php:
--------------------------------------------------------------------------------
1 | queryBus = new InMemorySymfonyQueryBus([$this->queryHandler()]);
22 | }
23 |
24 | /** @test */
25 | public function it_should_return_a_response_successfully(): void
26 | {
27 | $this->expectException(RuntimeException::class);
28 |
29 | $this->queryBus->ask(new FakeQuery());
30 | }
31 |
32 | /** @test */
33 | public function it_should_raise_an_exception_dispatching_a_non_registered_query(): void
34 | {
35 | $this->expectException(QueryNotRegisteredError::class);
36 |
37 | $this->queryBus->ask($this->mock(Query::class));
38 | }
39 |
40 | private function queryHandler()
41 | {
42 | return new class
43 | {
44 | public function __invoke(FakeQuery $query)
45 | {
46 | throw new RuntimeException('This works fine!');
47 | }
48 | };
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/tests/src/Shared/Infrastructure/ConstantRandomNumberGenerator.php:
--------------------------------------------------------------------------------
1 | getConnection();
17 |
18 | $tables = $this->tables($connection);
19 | $truncateTablesSql = $this->truncateDatabaseSql($tables);
20 |
21 | $connection->exec($truncateTablesSql);
22 | }
23 |
24 | private function truncateDatabaseSql(array $tables): string
25 | {
26 | $truncateTables = map($this->truncateTableSql(), $tables);
27 |
28 | return sprintf('SET FOREIGN_KEY_CHECKS = 0; %s SET FOREIGN_KEY_CHECKS = 1;', implode(' ', $truncateTables));
29 | }
30 |
31 | private function truncateTableSql(): callable
32 | {
33 | return function (array $table): string {
34 | return sprintf('TRUNCATE TABLE `%s`;', first($table));
35 | };
36 | }
37 |
38 | private function tables(Connection $connection): array
39 | {
40 | return $connection->query('SHOW TABLES')->fetchAll();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tests/src/Shared/Infrastructure/Mink/MinkSessionRequestHelper.php:
--------------------------------------------------------------------------------
1 | sessionHelper = $sessionHelper;
18 | }
19 |
20 | public function sendRequest($method, $url, array $optionalParams = []): void
21 | {
22 | $this->request($method, $url, $optionalParams);
23 | }
24 |
25 | public function sendRequestWithPyStringNode($method, $url, PyStringNode $body): void
26 | {
27 | $this->request($method, $url, ['content' => $body->getRaw()]);
28 | }
29 |
30 | public function request($method, $url, array $optionalParams = []): Crawler
31 | {
32 | return $this->sessionHelper->sendRequest($method, $url, $optionalParams);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tests/src/Shared/Infrastructure/Mockery/CodelyTvMatcherIsSimilar.php:
--------------------------------------------------------------------------------
1 | constraint = new CodelyTvConstraintIsSimilar($value, $delta);
19 | }
20 |
21 | public function match(&$actual): bool
22 | {
23 | return $this->constraint->evaluate($actual, '', true);
24 | }
25 |
26 | public function __toString(): string
27 | {
28 | return 'Is similar';
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/src/Shared/Infrastructure/PhpUnit/InfrastructureTestCase.php:
--------------------------------------------------------------------------------
1 | 'test']);
17 |
18 | parent::setUp();
19 |
20 | // @todo This should be for the "Shared Infrastructure" connection
21 | $arranger = new MoocEnvironmentArranger($this->service(EntityManager::class));
22 |
23 | $arranger->arrange();
24 | }
25 |
26 | protected function assertSimilar($expected, $actual): void
27 | {
28 | TestUtils::assertSimilar($expected, $actual);
29 | }
30 |
31 | /** @return mixed */
32 | protected function service($id)
33 | {
34 | return self::$container->get($id);
35 | }
36 |
37 | /** @return mixed */
38 | protected function parameter($parameter)
39 | {
40 | return self::$container->getParameter($parameter);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------