├── .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 | Codely logo 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 | Sunset in the mountains 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 | --------------------------------------------------------------------------------