├── var
├── cache
│ └── .gitkeep
├── logs
│ └── .gitkeep
└── sessions
│ └── .gitkeep
├── tests
├── _data
│ └── .gitkeep
├── unit
│ └── notes.md
├── functional
│ └── notes.md
├── _output
│ └── .gitignore
├── _support
│ ├── _generated
│ │ └── .gitignore
│ ├── Helper
│ │ ├── Api.php
│ │ ├── Unit.php
│ │ ├── Acceptance.php
│ │ └── Functional.php
│ ├── UnitTester.php
│ ├── AcceptanceTester.php
│ └── FunctionalTester.php
├── unit.suite.yml
├── api.suite.yml
├── api
│ ├── CreateRideCest.php
│ ├── CreatePassengerCest.php
│ ├── MarkRideAcceptedByDriverCest.php
│ ├── MarkRideInProgressCest.php
│ ├── MarkRideCompletedCest.php
│ └── AuthenticationCest.php
├── acceptance.suite.yml
├── functional.suite.yml
├── AppBundle
│ ├── Entity
│ │ ├── RideEventTypeTest.php
│ │ └── AppRoleTest.php
│ ├── Location
│ │ ├── LocationServiceTest.php
│ │ └── LocationRepositoryTest.php
│ ├── User
│ │ ├── FakeUser.php
│ │ ├── UserServiceTest.php
│ │ ├── FakeUserManager.php
│ │ └── UserRepositoryTest.php
│ ├── DTO
│ │ ├── UserDtoTest.php
│ │ └── RideDtoTest.php
│ ├── Production
│ │ ├── LocationApi.php
│ │ └── UserApi.php
│ ├── Ride
│ │ ├── RideRepositoryTest.php
│ │ ├── RideTransitionTest.php
│ │ └── RideEventRepositoryTest.php
│ └── AppTestCase.php
└── acceptance
│ └── FirstCest.php
├── web
├── favicon.ico
├── apple-touch-icon.png
├── robots.txt
├── app.php
├── app_dev.php
└── .htaccess
├── app
├── config
│ ├── routing_test.yml
│ ├── routing_dev.yml
│ ├── services.yml
│ ├── config_prod.yml
│ ├── parameters.yml.dist
│ ├── routing.yml
│ ├── config_dev.yml
│ ├── config_test.yml
│ ├── config.yml
│ ├── security.yml
│ └── frameworks.yml
├── AppCache.php
├── .htaccess
├── autoload.php
├── Resources
│ └── views
│ │ ├── base.html.twig
│ │ └── default
│ │ └── index.html.twig
├── DoctrineMigrations
│ ├── Version20180330191116.php
│ ├── Version20180208222831.php
│ ├── Version20180212012515.php
│ ├── Version20180212012516.php
│ ├── Version20180131035822.php
│ ├── Version20181027033505.php
│ ├── Version20180131035823.php
│ ├── Version20180208222514.php
│ ├── Version20180211230225.php
│ └── Version20180131035821.php
└── AppKernel.php
├── .idea
├── encodings.xml
├── misc.xml
├── vcs.xml
├── scopes
│ ├── web_scope.xml
│ └── psr2_scope.xml
├── modules.xml
├── symfony2.xml
├── runConfigurations
│ ├── API.xml
│ └── Tests.xml
├── php-test-framework.xml
├── php.xml
└── kata_tdd_php_symfony.iml
├── src
├── .htaccess
└── AppBundle
│ ├── AppBundle.php
│ ├── Exception
│ ├── DuplicateRoleAssignmentException.php
│ ├── RideLifeCycleException.php
│ ├── RideNotFoundException.php
│ ├── UserNotFoundException.php
│ ├── UnauthorizedOperationException.php
│ ├── UserNotInDriverRoleException.php
│ ├── UserNotInPassengerRoleException.php
│ └── ActingDriverIsNotAssignedDriverException.php
│ ├── Repository
│ ├── LocationRepositoryInterface.php
│ ├── AppRepository.php
│ ├── RideRepositoryInterface.php
│ ├── RideEventRepositoryInterface.php
│ ├── UserRepositoryInterface.php
│ ├── RideRepository.php
│ ├── LocationRepository.php
│ ├── RideEventRepository.php
│ └── UserRepository.php
│ ├── Entity
│ ├── Client.php
│ ├── AuthCode.php
│ ├── AccessToken.php
│ ├── RefreshToken.php
│ ├── AppRole.php
│ ├── RideEvent.php
│ ├── RideEventType.php
│ ├── AppLocation.php
│ ├── Ride.php
│ └── AppUser.php
│ ├── Controller
│ ├── DefaultController.php
│ ├── UserController.php
│ ├── AppController.php
│ └── RideController.php
│ ├── Service
│ ├── LocationService.php
│ ├── UserService.php
│ ├── RideTransitionService.php
│ └── RideService.php
│ └── DTO
│ ├── RideDto.php
│ └── UserDto.php
├── codeception.yml
├── .gitignore
├── bin
├── console
└── symfony_requirements
├── phpunit.xml
├── Kata-Tasks.md
├── template-phpunit.xml
├── README.md
└── composer.json
/var/cache/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/var/logs/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/_data/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/unit/notes.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/var/sessions/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/functional/notes.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/_output/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/tests/_support/_generated/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/web/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elchris/kata_tdd_php_symfony/HEAD/web/favicon.ico
--------------------------------------------------------------------------------
/app/config/routing_test.yml:
--------------------------------------------------------------------------------
1 | app:
2 | resource: "@AppBundle/Controller/"
3 | type: annotation
--------------------------------------------------------------------------------
/web/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elchris/kata_tdd_php_symfony/HEAD/web/apple-touch-icon.png
--------------------------------------------------------------------------------
/app/AppCache.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/.htaccess:
--------------------------------------------------------------------------------
1 |
2 | Require all denied
3 |
4 |
5 | Order deny,allow
6 | Deny from all
7 |
8 |
--------------------------------------------------------------------------------
/src/.htaccess:
--------------------------------------------------------------------------------
1 |
2 | Require all denied
3 |
4 |
5 | Order deny,allow
6 | Deny from all
7 |
8 |
--------------------------------------------------------------------------------
/src/AppBundle/AppBundle.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/tests/unit.suite.yml:
--------------------------------------------------------------------------------
1 | # Codeception Test Suite Configuration
2 | #
3 | # Suite for unit or integration tests.
4 |
5 | actor: UnitTester
6 | modules:
7 | enabled:
8 | - Asserts
9 | - \Helper\Unit
--------------------------------------------------------------------------------
/.idea/scopes/web_scope.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/codeception.yml:
--------------------------------------------------------------------------------
1 | paths:
2 | tests: tests
3 | output: tests/_output
4 | data: tests/_data
5 | support: tests/_support
6 | envs: tests/_envs
7 | actor_suffix: Tester
8 | extensions:
9 | enabled:
10 | - Codeception\Extension\RunFailed
11 |
--------------------------------------------------------------------------------
/tests/_support/Helper/Api.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app/autoload.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/symfony2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/tests/api/CreateRideCest.php:
--------------------------------------------------------------------------------
1 | getNewRide();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/AppBundle/Exception/DuplicateRoleAssignmentException.php:
--------------------------------------------------------------------------------
1 | getNewPassenger();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/acceptance.suite.yml:
--------------------------------------------------------------------------------
1 | # Codeception Test Suite Configuration
2 | #
3 | # Suite for acceptance tests.
4 | # Perform tests in browser using the WebDriver or PhpBrowser.
5 | # If you need both WebDriver and PHPBrowser tests - create a separate suite.
6 |
7 | actor: AcceptanceTester
8 | modules:
9 | enabled:
10 | - PhpBrowser:
11 | url: http://127.0.0.1:8000
12 | - \Helper\Acceptance
--------------------------------------------------------------------------------
/app/config/routing_dev.yml:
--------------------------------------------------------------------------------
1 | _wdt:
2 | resource: "@WebProfilerBundle/Resources/config/routing/wdt.xml"
3 | prefix: /_wdt
4 |
5 | _profiler:
6 | resource: "@WebProfilerBundle/Resources/config/routing/profiler.xml"
7 | prefix: /_profiler
8 |
9 | _errors:
10 | resource: "@TwigBundle/Resources/config/routing/errors.xml"
11 | prefix: /_error
12 |
13 | _main:
14 | resource: routing.yml
15 |
--------------------------------------------------------------------------------
/tests/functional.suite.yml:
--------------------------------------------------------------------------------
1 | # Codeception Test Suite Configuration
2 | #
3 | # Suite for functional tests
4 | # Emulate web requests and make application process them
5 | # Include one of framework modules (Symfony2, Yii2, Laravel5) to use it
6 | # Remove this suite if you don't use frameworks
7 |
8 | actor: FunctionalTester
9 | modules:
10 | enabled:
11 | # add a framework module here
12 | - \Helper\Functional
--------------------------------------------------------------------------------
/.idea/runConfigurations/API.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/AppBundle/Exception/RideLifeCycleException.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/AppBundle/Exception/UserNotFoundException.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {% block title %}Welcome!{% endblock %}
6 | {% block stylesheets %}{% endblock %}
7 |
8 |
9 |
10 | {% block body %}{% endblock %}
11 | {% block javascripts %}{% endblock %}
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/AppBundle/Exception/UnauthorizedOperationException.php:
--------------------------------------------------------------------------------
1 | equals(
15 | RideEventType::newById(RideEventType::REQUESTED_ID)
16 | )
17 | );
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tests/acceptance/FirstCest.php:
--------------------------------------------------------------------------------
1 | amOnPage("/");
21 | $I->see('Welcome');
22 | $I->see('Symfony 3.4.37');
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/AppBundle/Exception/ActingDriverIsNotAssignedDriverException.php:
--------------------------------------------------------------------------------
1 | em = $em;
21 | }
22 |
23 | public function save($object): void
24 | {
25 | $this->em->persist($object);
26 | $this->em->flush();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/api/MarkRideAcceptedByDriverCest.php:
--------------------------------------------------------------------------------
1 | getNewRide();
15 | $driver = $I->getNewDriver();
16 |
17 | $driverId = $driver['id'];
18 | $rideId = $requestedRide['id'];
19 | $I->acceptRideByDriver(
20 | $rideId,
21 | $driverId
22 | );
23 | $I->assignWorkDestinationToRide($rideId);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/web/app.php:
--------------------------------------------------------------------------------
1 | loadClassCache();
11 | //$kernel = new AppCache($kernel);
12 |
13 | // When using the HttpCache, you need to call the method in your front controller instead of relying on the configuration parameter
14 | //Request::enableHttpMethodParameterOverride();
15 | $request = Request::createFromGlobals();
16 | $response = $kernel->handle($request);
17 | $response->send();
18 | $kernel->terminate($request, $response);
19 |
--------------------------------------------------------------------------------
/src/AppBundle/Entity/AuthCode.php:
--------------------------------------------------------------------------------
1 | getNewRide();
15 | $driver = $I->getNewDriver();
16 |
17 | $driverId = $driver['id'];
18 | $rideId = $requestedRide['id'];
19 |
20 | $I->acceptRideByDriver(
21 | $rideId,
22 | $driverId
23 | );
24 | $I->assignWorkDestinationToRide($rideId);
25 | $I->markRideInProgress($rideId, $driverId);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/AppBundle/Repository/RideEventRepositoryInterface.php:
--------------------------------------------------------------------------------
1 | location()->getLocation(
19 | $referenceLocation->getLat(),
20 | $referenceLocation->getLong()
21 | );
22 |
23 | self::assertTrue($retrievedLocation->isSameAs($referenceLocation));
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tests/api/MarkRideCompletedCest.php:
--------------------------------------------------------------------------------
1 | getNewRide();
15 | $driver = $I->getNewDriver();
16 |
17 | $driverId = $driver['id'];
18 | $rideId = $requestedRide['id'];
19 |
20 | $I->acceptRideByDriver(
21 | $rideId,
22 | $driverId
23 | );
24 | $I->assignWorkDestinationToRide($rideId);
25 | $I->markRideInProgress($rideId, $driverId);
26 | $I->markRideCompleted($rideId, $driverId);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/AppBundle/Controller/DefaultController.php:
--------------------------------------------------------------------------------
1 | render('default/index.html.twig', [
21 | 'base_dir' => realpath($this->getParameter('kernel.root_dir').'/..').DIRECTORY_SEPARATOR,
22 | ]);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/config/parameters.yml.dist:
--------------------------------------------------------------------------------
1 | # This file is a "template" of what your parameters.yml file should look like
2 | # Set parameters here that may be different on each deployment target of the app, e.g. development, staging, production.
3 | # http://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration
4 | parameters:
5 | database_host: 127.0.0.1
6 | database_port: ~
7 | database_name: symfony
8 | database_user: root
9 | database_password: ~
10 | # You should uncomment this if you want use pdo_sqlite
11 | # database_path: "%kernel.root_dir%/data.db3"
12 |
13 | mailer_transport: smtp
14 | mailer_host: 127.0.0.1
15 | mailer_user: ~
16 | mailer_password: ~
17 |
18 | # A secret key that's used to generate certain security-related tokens
19 | secret: ThisTokenIsNotSoSecretChangeIt
20 |
--------------------------------------------------------------------------------
/tests/api/AuthenticationCest.php:
--------------------------------------------------------------------------------
1 | getRegisteredUserWithToken(
17 | 'Joe',
18 | 'Passenger',
19 | true
20 | );
21 | $userName = $newUser['user']['username'];
22 | $userId = $newUser['user']['id'];
23 | $I->nukeToken();
24 | $response = $I->sendGetApiRequest('/user/' . $userId);
25 | $I->seeResponseContainsJson([
26 | 'error' => 'access_denied',
27 | 'error_description' => 'OAuth2 authentication required'
28 | ]);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/AppBundle/Repository/UserRepositoryInterface.php:
--------------------------------------------------------------------------------
1 | getParameterOption(['--env', '-e'], getenv('SYMFONY_ENV') ?: 'dev');
20 | $debug = getenv('SYMFONY_DEBUG') !== '0' && !$input->hasParameterOption(['--no-debug', '']) && $env !== 'prod';
21 |
22 | if ($debug) {
23 | Debug::enable();
24 | }
25 |
26 | $kernel = new AppKernel($env, $debug);
27 | $application = new Application($kernel);
28 | $application->run($input);
29 |
--------------------------------------------------------------------------------
/tests/AppBundle/User/FakeUser.php:
--------------------------------------------------------------------------------
1 | first = $first;
24 | $this->last = $last;
25 | $baseUsername = $first . $last;
26 | $this->username = $baseUsername .microtime(true);
27 | $this->email = $this->username.'@'.$first.$last.'.com';
28 | $this->password = 'password';
29 | }
30 |
31 | public function toEntity()
32 | {
33 | return new AppUser(
34 | $this->first,
35 | $this->last,
36 | $this->email,
37 | $this->username,
38 | $this->password
39 | );
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/AppBundle/Service/LocationService.php:
--------------------------------------------------------------------------------
1 | locationRepository = $locationRepository;
22 | }
23 |
24 | /**
25 | * @param $lat
26 | * @param $long
27 | * @return AppLocation
28 | * @throws \Exception
29 | */
30 | public function getLocation($lat, $long): AppLocation
31 | {
32 | return $this->locationRepository->getLocation(
33 | new AppLocation(
34 | $lat,
35 | $long
36 | )
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/AppBundle/DTO/RideDto.php:
--------------------------------------------------------------------------------
1 | id = $ride->getId()->toString();
31 | $this->passengerId = $passenger->getId()->toString();
32 | if ($ride->hasDriver()) {
33 | $this->driverId = $driver->getId()->toString();
34 | }
35 | if ($ride->hasDestination()) {
36 | $this->destination = $destination;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/config/config_dev.yml:
--------------------------------------------------------------------------------
1 | imports:
2 | - { resource: config.yml }
3 | - { resource: frameworks.yml }
4 |
5 | framework:
6 | router:
7 | resource: "%kernel.root_dir%/config/routing_dev.yml"
8 | strict_requirements: true
9 | profiler: { only_exceptions: false }
10 |
11 | web_profiler:
12 | toolbar: true
13 | intercept_redirects: false
14 |
15 | monolog:
16 | handlers:
17 | main:
18 | type: stream
19 | path: "%kernel.logs_dir%/%kernel.environment%.log"
20 | level: debug
21 | channels: ["!event"]
22 | console:
23 | type: console
24 | channels: ["!event", "!doctrine"]
25 | # uncomment to get logging in your browser
26 | # you may have to allow bigger header sizes in your Web server configuration
27 | #firephp:
28 | # type: firephp
29 | # level: info
30 | #chromephp:
31 | # type: chromephp
32 | # level: info
33 |
34 | #swiftmailer:
35 | # delivery_address: me@example.com
36 |
--------------------------------------------------------------------------------
/.idea/php-test-framework.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/AppBundle/DTO/UserDto.php:
--------------------------------------------------------------------------------
1 | id = $id;
37 | $this->isDriver = $isDriver;
38 | $this->isPassenger = $isPassenger;
39 | $this->fullName = $fullName;
40 | $this->username = $username;
41 | $this->email = $email;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/DoctrineMigrations/Version20180330191116.php:
--------------------------------------------------------------------------------
1 | abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
17 |
18 | $this->addSql('CREATE INDEX events_idx ON rideEvents (rideId, created, id)');
19 | }
20 |
21 | public function down(Schema $schema)
22 | {
23 | // this down() migration is auto-generated, please modify it to your needs
24 | $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
25 |
26 | $this->addSql('DROP INDEX events_idx ON rideEvents');
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | tests
19 |
20 |
21 |
22 |
23 |
24 | src
25 |
26 | src/*Bundle/Resources
27 | src/*/*Bundle/Resources
28 | src/*/Bundle/*Bundle/Resources
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/Kata-Tasks.md:
--------------------------------------------------------------------------------
1 | Kata Tasks
2 | ==========
3 |
4 | ## Tutorial Plan
5 |
6 | ### Users Basic
7 |
8 | * UserRepositoryTest
9 | * Create / Get User
10 | * Assign Passenger Role
11 |
12 | * UserServiceTest
13 | * Create / Get User
14 |
15 | * RegisterUserCest
16 | * POST /register-user
17 | * first: 'fist name'
18 | * last: 'last name'
19 |
20 | * Doctrine Diff & Migrate
21 | * users, roles, users_roles
22 |
23 |
24 | ### Users & Roles
25 |
26 | * UserRepositoryTest
27 | * Assign Driver Role
28 |
29 | * AssignRoleToUserCest
30 | * PATCH /user/{id}
31 | role: 'Passenger'
32 | * PATCH /user/{id}
33 | role: 'Driver'
34 |
35 | * Migration
36 | * roles:
37 | 1 - Passenger
38 | 2 - Driver
39 |
40 | ### Locations & Rides
41 |
42 | * LocationRepositoryTest & LocationServiceTest
43 | * getOrCreateLocation
44 |
45 | * RideRepositoryTest & RideServiceTest
46 | * newRide($departure, $passenger)
47 |
48 | * CreateNewRideCest
49 | * POST /ride
50 | * departure [37.773160, -122.432444]
51 | * passengerId
52 |
53 | * AssignDestinationCest
54 |
55 | * AssignDriverCest
56 |
57 |
--------------------------------------------------------------------------------
/template-phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
15 |
16 |
17 |
18 |
19 |
20 | tests
21 |
22 |
23 |
24 |
25 |
26 | src
27 |
28 | src/*Bundle/Resources
29 | src/*/*Bundle/Resources
30 | src/*/Bundle/*Bundle/Resources
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/app/DoctrineMigrations/Version20180208222831.php:
--------------------------------------------------------------------------------
1 | abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
20 |
21 | $this->addSql('ALTER TABLE rides ADD created DATETIME DEFAULT NULL');
22 | }
23 |
24 | /**
25 | * @param Schema $schema
26 | */
27 | public function down(Schema $schema)
28 | {
29 | // this down() migration is auto-generated, please modify it to your needs
30 | $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
31 |
32 | $this->addSql('ALTER TABLE rides DROP created');
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/web/app_dev.php:
--------------------------------------------------------------------------------
1 | loadClassCache();
27 | $request = Request::createFromGlobals();
28 | $response = $kernel->handle($request);
29 | $response->send();
30 | $kernel->terminate($request, $response);
31 |
--------------------------------------------------------------------------------
/app/DoctrineMigrations/Version20180212012515.php:
--------------------------------------------------------------------------------
1 | abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
17 |
18 | $this->addSql('ALTER TABLE users CHANGE first_name first_name VARCHAR(255) DEFAULT NULL, CHANGE last_name last_name VARCHAR(255) DEFAULT NULL');
19 | }
20 |
21 | public function down(Schema $schema)
22 | {
23 | // this down() migration is auto-generated, please modify it to your needs
24 | $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
25 |
26 | $this->addSql('ALTER TABLE users CHANGE first_name first_name VARCHAR(255) NOT NULL COLLATE utf8_unicode_ci, CHANGE last_name last_name VARCHAR(255) NOT NULL COLLATE utf8_unicode_ci');
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/DoctrineMigrations/Version20180212012516.php:
--------------------------------------------------------------------------------
1 | abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
17 |
18 | $this->addSql(
19 | "INSERT INTO `oauth2_clients` VALUES (NULL, '3bcbxd9e24g0gk4swg0kwgcwg4o8k8g4g888kwc44gcc0gwwk4', 'a:1:{i:0;s:22:\"http://localhost:8000/\";}', '4ok2x70rlfokc8g0wws8c8kwcokw80k44sg48goc0ok4w0so0k', 'a:2:{i:0;s:8:\"password\";i:1;s:5:\"token\";}');"
20 | );
21 | }
22 |
23 | public function down(Schema $schema)
24 | {
25 | // this down() migration is auto-generated, please modify it to your needs
26 | $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
27 |
28 | $this->addSql('delete from oauth2_clients');
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/DoctrineMigrations/Version20180131035822.php:
--------------------------------------------------------------------------------
1 | abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
22 |
23 | $this->addSql('INSERT INTO `roles` (`id`, `name`) VALUES (1, \'Driver\'),(2, \'Passenger\');');
24 | }
25 |
26 | /**
27 | * @param Schema $schema
28 | * @throws \Doctrine\DBAL\Migrations\AbortMigrationException
29 | */
30 | public function down(Schema $schema)
31 | {
32 | // this down() migration is auto-generated, please modify it to your needs
33 | $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
34 | $this->addSql('delete from `roles`;');
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/AppBundle/Repository/RideRepository.php:
--------------------------------------------------------------------------------
1 | assignDestination($destination);
16 | $this->save($ride);
17 | }
18 |
19 | public function saveRide(Ride $ride): void
20 | {
21 | $this->save($ride);
22 | }
23 |
24 | /**
25 | * @param Uuid $id
26 | * @return Ride
27 | * @throws RideNotFoundException
28 | */
29 | public function getRideById(Uuid $id): Ride
30 | {
31 | try {
32 | return $this->em->createQuery(
33 | 'select r from E:Ride r where r.id = :id'
34 | )
35 | ->setParameter('id', $id)
36 | ->getSingleResult();
37 | } catch (\Exception $e) {
38 | throw new RideNotFoundException();
39 | }
40 | }
41 |
42 | public function assignDriverToRide(Ride $ride, AppUser $driver): void
43 | {
44 | $ride->assignDriver($driver);
45 | $this->save($ride);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/AppBundle/Entity/AppRole.php:
--------------------------------------------------------------------------------
1 | id = $id;
36 | $this->name = $name;
37 | }
38 |
39 | public static function driver(): AppRole
40 | {
41 | return new self(self::DRIVER_ID, self::DRIVER);
42 | }
43 |
44 | public static function passenger(): AppRole
45 | {
46 | return new self(self::PASSENGER_ID, self::PASSENGER);
47 | }
48 |
49 | public static function isPassenger($role): bool
50 | {
51 | return $role === self::PASSENGER;
52 | }
53 |
54 | public static function isDriver($role): bool
55 | {
56 | return $role === self::DRIVER;
57 | }
58 |
59 | public function getId(): int
60 | {
61 | return $this->id;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/app/DoctrineMigrations/Version20181027033505.php:
--------------------------------------------------------------------------------
1 | abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
17 |
18 | $this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E992FC23A8 ON users (username_canonical)');
19 | $this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E9A0D96FBF ON users (email_canonical)');
20 | $this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E9C05FB297 ON users (confirmation_token)');
21 | }
22 |
23 | public function down(Schema $schema) : void
24 | {
25 | // this down() migration is auto-generated, please modify it to your needs
26 | $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
27 |
28 | $this->addSql('DROP INDEX UNIQ_1483A5E992FC23A8 ON users');
29 | $this->addSql('DROP INDEX UNIQ_1483A5E9A0D96FBF ON users');
30 | $this->addSql('DROP INDEX UNIQ_1483A5E9C05FB297 ON users');
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/DoctrineMigrations/Version20180131035823.php:
--------------------------------------------------------------------------------
1 | abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
22 |
23 | $this->addSql('
24 | INSERT INTO `rideEventTypes` (`id`, `name`)
25 | VALUES
26 | (1, \'Requested\'),
27 | (2, \'Accepted\'),
28 | (3,\'In Progress\'),
29 | (4,\'Cancelled\'),
30 | (5,\'Completed\'),
31 | (6,\'Rejected\')
32 | ;
33 | ');
34 | }
35 |
36 | /**
37 | * @param Schema $schema
38 | * @throws \Doctrine\DBAL\Migrations\AbortMigrationException
39 | */
40 | public function down(Schema $schema)
41 | {
42 | // this down() migration is auto-generated, please modify it to your needs
43 | $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
44 | $this->addSql('delete from `rideEventTypes`;');
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/AppBundle/Repository/LocationRepository.php:
--------------------------------------------------------------------------------
1 | em
21 | ->createQuery(
22 | 'SELECT l FROM E:AppLocation l WHERE l.lat = :lat AND l.long = :long'
23 | )
24 | ->setParameter('lat', $lookupLocation->getLat())
25 | ->setParameter('long', $lookupLocation->getLong())
26 | ->getSingleResult();
27 | } catch (NoResultException $e) {
28 | $this->save($lookupLocation);
29 | return $this->getLocation($lookupLocation);
30 | } catch (NonUniqueResultException $nur) {
31 | //TODO : possibly log what should be a rare occurrence
32 | //This would only happen if some rogue process inserts duplicate Locations
33 | //in the Data Store.
34 | return ($this->em->createQuery(
35 | 'SELECT l FROM E:AppLocation l WHERE l.lat = :lat AND l.long = :long order by l.created asc'
36 | )
37 | ->setParameter('lat', $lookupLocation->getLat())
38 | ->setParameter('long', $lookupLocation->getLong())
39 | ->getResult())[0];
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/config/config_test.yml:
--------------------------------------------------------------------------------
1 | imports:
2 | - { resource: config.yml }
3 |
4 | #framework:
5 | # test: ~
6 | # session:
7 | # storage_id: session.storage.mock_file
8 | # profiler:
9 | # collect: false
10 |
11 | framework:
12 | test: ~
13 | secret: "%secret%"
14 | router:
15 | resource: "%kernel.root_dir%/config/routing_test.yml"
16 | strict_requirements: ~
17 | form: ~
18 | csrf_protection: ~
19 | validation: { enable_annotations: true }
20 | #serializer: { enable_annotations: true }
21 | templating:
22 | engines: ['twig']
23 | default_locale: "%locale%"
24 | trusted_hosts: ~
25 | session:
26 | storage_id: session.storage.mock_file
27 | # http://symfony.com/doc/current/reference/configuration/framework.html#handler-id
28 | handler_id: ~
29 | # save_path: "%kernel.root_dir%/../var/sessions/%kernel.environment%"
30 | fragments: ~
31 | http_method_override: true
32 | assets: ~
33 | php_errors:
34 | log: true
35 | profiler:
36 | collect: false
37 |
38 |
39 | security:
40 | encoders:
41 | AppBundle\Entity\AppUser: plaintext
42 |
43 | providers:
44 | in_memory:
45 | memory: ~
46 |
47 | firewalls:
48 | # disables authentication for assets and the profiler, adapt it according to your needs
49 | dev:
50 | pattern: ^/(_(profiler|wdt)|css|images|js)/
51 | security: false
52 |
53 | doctrine:
54 | dbal:
55 | driver: pdo_sqlite
56 |
57 | web_profiler:
58 | toolbar: false
59 | intercept_redirects: false
60 |
61 | swiftmailer:
62 | disable_delivery: true
63 |
--------------------------------------------------------------------------------
/app/config/config.yml:
--------------------------------------------------------------------------------
1 | imports:
2 | - { resource: parameters.yml }
3 |
4 | # Put parameters here that don't need to change on each machine where the app is deployed
5 | # http://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
6 | parameters:
7 | locale: en
8 |
9 | # Twig Configuration
10 | twig:
11 | debug: "%kernel.debug%"
12 | strict_variables: "%kernel.debug%"
13 |
14 | # Doctrine Configuration
15 | doctrine:
16 | dbal:
17 | driver: pdo_mysql
18 | host: "%database_host%"
19 | port: "%database_port%"
20 | dbname: "%database_name%"
21 | user: "%database_user%"
22 | password: "%database_password%"
23 | charset: UTF8
24 | # if using pdo_sqlite as your database driver:
25 | # 1. add the path in parameters.yml
26 | # e.g. database_path: "%kernel.root_dir%/data/data.db3"
27 | # 2. Uncomment database_path in parameters.yml.dist
28 | # 3. Uncomment next line:
29 | # path: "%database_path%"
30 | types:
31 | uuid: Ramsey\Uuid\Doctrine\UuidType
32 |
33 | orm:
34 | auto_generate_proxy_classes: "%kernel.debug%"
35 | naming_strategy: doctrine.orm.naming_strategy.underscore
36 | auto_mapping: true
37 |
38 | # TODO: ADD
39 | mappings:
40 | AppBundle:
41 | mapping: true
42 | prefix: AppBundle\Entity
43 | alias: E
44 |
45 | # Swiftmailer Configuration
46 | swiftmailer:
47 | transport: "%mailer_transport%"
48 | host: "%mailer_host%"
49 | username: "%mailer_user%"
50 | password: "%mailer_password%"
51 | spool: { type: memory }
--------------------------------------------------------------------------------
/tests/AppBundle/DTO/UserDtoTest.php:
--------------------------------------------------------------------------------
1 | newNamedUser(
15 | 'chris',
16 | 'holland'
17 | ))->toDto();
18 |
19 |
20 | self::assertNotNull($userDto->id);
21 | self::assertFalse($userDto->isDriver);
22 | self::assertFalse($userDto->isPassenger);
23 | self::assertEquals('chris holland', $userDto->fullName);
24 | }
25 |
26 | public function testUserDtoDriver()
27 | {
28 | $driver = $this->newNamedUser('Joe', 'Driver');
29 | self::assertTrue($driver->isNamed('Joe Driver'));
30 | $newFirstName = 'NotJoe';
31 | $newLastName = 'NotDriver';
32 | $driver->setFirstName($newFirstName);
33 | $driver->setLastName($newLastName);
34 | self::assertSame($newFirstName, $driver->getFirstName());
35 | self::assertSame($newLastName, $driver->getLastName());
36 | $driver->assignRole(AppRole::driver());
37 | $userDto = $driver->toDto();
38 | self::assertTrue($userDto->isDriver);
39 | self::assertFalse($userDto->isPassenger);
40 | self::assertSame($driver->getUsername(), $userDto->username);
41 | }
42 |
43 | public function testUserDtoPassenger()
44 | {
45 | $passenger = $this->newNamedUser('Bob', 'Passenger');
46 | $passenger->assignRole(AppRole::passenger());
47 | $userDto = $passenger->toDto();
48 | self::assertTrue($userDto->isPassenger);
49 | self::assertFalse($userDto->isDriver);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/app/config/security.yml:
--------------------------------------------------------------------------------
1 | # To get started with security, check out the documentation:
2 | # http://symfony.com/doc/current/security.html
3 | security:
4 | encoders:
5 | AppBundle\Entity\AppUser: bcrypt
6 |
7 | # http://symfony.com/doc/current/security.html#b-configuring-how-users-are-loaded
8 | providers:
9 | in_memory:
10 | memory: ~
11 | fos_userbundle:
12 | id: fos_user.user_provider.username
13 |
14 | firewalls:
15 | # disables authentication for assets and the profiler, adapt it according to your needs
16 | dev:
17 | pattern: ^/(_(profiler|wdt)|css|images|js)/
18 | security: false
19 |
20 | oauth_token: # Everyone can access the access token URL.
21 | pattern: ^/oauth/v2/token
22 | security: false
23 |
24 | api-public:
25 | pattern: ^/api/v1/register-user
26 | security: false
27 |
28 | api:
29 | pattern: ^/api/v1 # All URLs are protected
30 | fos_oauth: true # OAuth2 protected resource
31 | stateless: true # Do no set session cookies
32 | anonymous: false
33 | provider: fos_userbundle
34 |
35 | main:
36 | anonymous: ~
37 | # activate different ways to authenticate
38 |
39 | # http_basic: ~
40 | # http://symfony.com/doc/current/security.html#a-configuring-how-your-users-will-authenticate
41 | # http://symfony.com/doc/current/cookbook/security/form_login_setup.html
42 |
43 | pattern: ^/
44 | form_login:
45 | provider: fos_userbundle
46 | check_path: fos_user_security_check
47 | #csrf_provider: security.csrf.token_manager
48 | logout: true
49 | anonymous: true
50 | logout_on_user_change: true
51 |
--------------------------------------------------------------------------------
/tests/AppBundle/DTO/RideDtoTest.php:
--------------------------------------------------------------------------------
1 | newNamedUser($firstName, $lastName);
19 | $passenger->assignRole(AppRole::passenger());
20 | $home = new AppLocation(
21 | LocationApi::HOME_LOCATION_LAT,
22 | LocationApi::HOME_LOCATION_LONG
23 | );
24 | $ride = new Ride(
25 | $passenger,
26 | $home
27 | );
28 | $rideDto = $ride->toDto();
29 |
30 | self::assertSame($passenger->getId()->toString(), $rideDto->passengerId);
31 | self::assertNull($rideDto->driverId);
32 | self::assertNull($rideDto->destination);
33 | }
34 |
35 | public function testRideDtoWithDriverAndDestination()
36 | {
37 | $passenger = $this->newNamedUser('Joe', 'Passenger');
38 | $passenger->assignRole(AppRole::passenger());
39 | $driver = $this->newNamedUser('Bob', 'Driver');
40 | $driver->assignRole(AppRole::driver());
41 | $home = new AppLocation(
42 | LocationApi::HOME_LOCATION_LAT,
43 | LocationApi::HOME_LOCATION_LONG
44 | );
45 | $work = new AppLocation(
46 | LocationApi::WORK_LOCATION_LAT,
47 | LocationApi::WORK_LOCATION_LONG
48 | );
49 | $ride = new Ride(
50 | $passenger,
51 | $home
52 | );
53 | $ride->assignDriver($driver);
54 | $ride->assignDestination($work);
55 | $rideDto = $ride->toDto();
56 |
57 | self::assertSame($driver->getId()->toString(), $rideDto->driverId);
58 | self::assertTrue($work->isSameAs($rideDto->destination));
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/tests/AppBundle/Production/LocationApi.php:
--------------------------------------------------------------------------------
1 | locationRepository = new LocationRepository(
25 | $entityManager
26 | );
27 | $this->locationService = new LocationService(
28 | $this->locationRepository
29 | );
30 | }
31 |
32 | /**
33 | * @return LocationRepositoryInterface
34 | */
35 | public function getRepo()
36 | {
37 | return $this->locationRepository;
38 | }
39 |
40 | /**
41 | * @return AppLocation
42 | */
43 | public function getSavedHomeLocation()
44 | {
45 | return $this->locationService->getLocation(
46 | self::HOME_LOCATION_LAT,
47 | self::HOME_LOCATION_LONG
48 | );
49 | }
50 |
51 | /**
52 | * @param $lat
53 | * @param $long
54 | * @return AppLocation
55 | */
56 | public function getLocation($lat, $long)
57 | {
58 | return $this->locationService->getLocation(
59 | $lat,
60 | $long
61 | );
62 | }
63 |
64 | public function getWorkLocation()
65 | {
66 | return $this->getLocation(
67 | self::WORK_LOCATION_LAT,
68 | self::WORK_LOCATION_LONG
69 | );
70 | }
71 |
72 | /**
73 | * @return LocationService
74 | */
75 | public function getService() : LocationService
76 | {
77 | return $this->locationService;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/AppBundle/Repository/RideEventRepository.php:
--------------------------------------------------------------------------------
1 | em->createQuery(
22 | 'select e from E:RideEvent e where e.ride = :ride order by e.created desc, e.id desc'
23 | )
24 | ->setMaxResults(1)
25 | ->setParameter('ride', $ride)
26 | ->getSingleResult();
27 | } catch (\Exception $e) {
28 | throw new RideNotFoundException();
29 | }
30 | }
31 |
32 | public function markRideStatusByActor(
33 | Ride $ride,
34 | AppUser $actor,
35 | RideEventType $status
36 | ): RideEvent {
37 | $newEvent = new RideEvent(
38 | $ride,
39 | $actor,
40 | $this->getStatusReference($status)
41 | );
42 |
43 | $this->save($newEvent);
44 |
45 | return $newEvent;
46 | }
47 |
48 | public function markRideStatusByPassenger(Ride $ride, RideEventType $status): RideEvent
49 | {
50 | $passengerEvent = $ride->getPassengerTransaction($this->getStatusReference($status));
51 | $this->save($passengerEvent);
52 | return $passengerEvent;
53 | }
54 |
55 | /**
56 | * @param RideEventType $status
57 | * @return RideEventType
58 | */
59 | private function getStatusReference(RideEventType $status) : RideEventType
60 | {
61 | /** @var RideEventType $status */
62 | $status = $this->em->getRepository(
63 | RideEventType::class
64 | )->find($status->getId());
65 |
66 | return $status;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/AppBundle/Entity/RideEvent.php:
--------------------------------------------------------------------------------
1 | ride = $ride;
67 | $this->actor = $actor;
68 | $this->type = $type;
69 | $this->created = new \DateTime();
70 | }
71 |
72 | public function getId(): int
73 | {
74 | return $this->id;
75 | }
76 |
77 | public function is(RideEventType $typeToCompare): bool
78 | {
79 | return $this->type->equals($typeToCompare);
80 | }
81 |
82 | public function getStatus(): RideEventType
83 | {
84 | return $this->type;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/tests/AppBundle/Ride/RideRepositoryTest.php:
--------------------------------------------------------------------------------
1 | user()->getSavedUser(),
21 | $this->location()->getSavedHomeLocation()
22 | );
23 | $this->verifyExceptionWithMessage(
24 | RideNotFoundException::class,
25 | RideNotFoundException::MESSAGE
26 | );
27 |
28 | $this->ride()->getRepoRideById($nonExistentRide->getId());
29 | }
30 |
31 | /**
32 | * @throws DuplicateRoleAssignmentException
33 | * @throws UserNotFoundException
34 | * @throws UnauthorizedOperationException
35 | */
36 | public function testCreateRideWithDepartureAndPassenger()
37 | {
38 | $ride = $this->ride()->getRepoSavedRide();
39 |
40 | self::assertNotEmpty($ride->getId());
41 | }
42 |
43 | /**
44 | * @throws DuplicateRoleAssignmentException
45 | * @throws UserNotFoundException
46 | * @throws UnauthorizedOperationException
47 | */
48 | public function testAssignDestinationToRide()
49 | {
50 | $retrievedRide = $this->ride()->getRepoRideWithDestination();
51 |
52 | self::assertTrue($retrievedRide->isDestinedFor($this->location()->getWorkLocation()));
53 | }
54 |
55 | /**
56 | * @throws DuplicateRoleAssignmentException
57 | * @throws UserNotFoundException
58 | * @throws UnauthorizedOperationException
59 | */
60 | public function testAssignDriverToRide()
61 | {
62 | /** @var AppUser $driver */
63 | $driver = $this->user()->getSavedUserWithName('Jamie', 'Isaacs');
64 | $rideWithDestination = $this->ride()->getRepoRideWithDestination();
65 |
66 | $this->ride()->assignRepoDriverToRide($rideWithDestination, $driver);
67 | $retrievedRide = $this->ride()->getRepoRideById($rideWithDestination->getId());
68 |
69 | self::assertTrue($retrievedRide->isDrivenBy($driver));
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/AppBundle/Entity/RideEventType.php:
--------------------------------------------------------------------------------
1 | id = $id;
45 | $this->name = $name;
46 | }
47 |
48 | public static function requested(): RideEventType
49 | {
50 | return new self(self::REQUESTED_ID, self::REQUESTED);
51 | }
52 |
53 | public static function accepted(): RideEventType
54 | {
55 | return new self(self::ACCEPTED_ID, self::ACCEPTED);
56 | }
57 |
58 | public static function inProgress(): RideEventType
59 | {
60 | return new self(self::IN_PROGRESS_ID, self::IN_PROGRESS_STATUS);
61 | }
62 |
63 | public static function cancelled(): RideEventType
64 | {
65 | return new self(self::CANCELLED_ID, self::CANCELLED);
66 | }
67 |
68 | public static function completed(): RideEventType
69 | {
70 | return new self(self::COMPLETED_ID, self::COMPLETED);
71 | }
72 |
73 | public static function rejected(): RideEventType
74 | {
75 | return new self(self::REJECTED_ID, self::REJECTED);
76 | }
77 |
78 | public static function newById($eventTypeId): RideEventType
79 | {
80 | return new self($eventTypeId, '');
81 | }
82 |
83 | public function equals(RideEventType $typeToCompare): bool
84 | {
85 | return $this->id === $typeToCompare->id;
86 | }
87 |
88 | public function getId(): int
89 | {
90 | return $this->id;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/AppBundle/Entity/AppLocation.php:
--------------------------------------------------------------------------------
1 | id = Uuid::uuid4();
52 | $this->lat = $lat;
53 | $this->long = $long;
54 | $this->created = new \DateTime();
55 | }
56 |
57 | /**
58 | * @param AppLocation $toClone
59 | * @return AppLocation
60 | * @throws \Exception
61 | */
62 | public static function cloneFrom(AppLocation $toClone): AppLocation
63 | {
64 | return new self($toClone->lat, $toClone->long);
65 | }
66 |
67 | public function getLat(): float
68 | {
69 | return $this->lat;
70 | }
71 |
72 | public function getLong(): float
73 | {
74 | return $this->long;
75 | }
76 |
77 | public function isSameAs(AppLocation $compareLocation): bool
78 | {
79 | return (
80 | ($compareLocation->lat === $this->lat)
81 | &&
82 | ($compareLocation->long === $this->long)
83 | );
84 | }
85 |
86 | public function preDates(AppLocation $compareLocation): bool
87 | {
88 | return $this->created < $compareLocation->created;
89 | }
90 |
91 | public function equals(AppLocation $compareLocation): bool
92 | {
93 | return $this->id->equals($compareLocation->id);
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/AppBundle/Controller/UserController.php:
--------------------------------------------------------------------------------
1 | user()->newUser(
24 | $request->get('firstName'),
25 | $request->get('lastName'),
26 | $request->get('email'),
27 | $request->get('username'),
28 | $request->get('password')
29 | )->toDto();
30 | }
31 |
32 | /**
33 | * @Rest\Get("/api/v1/user/{id}")
34 | * @param string $id
35 | * @return UserDto
36 | * @throws UserNotFoundException
37 | * @throws UnauthorizedOperationException
38 | */
39 | public function idAction(string $id): UserDto
40 | {
41 | return $this->getUserById($id)->toDto();
42 | }
43 |
44 | /**
45 | * @Rest\Patch("/api/v1/user/{id}")
46 | * @param string $id
47 | * @param Request $request
48 | * @return UserDto
49 | * @throws UserNotFoundException
50 | * @throws DuplicateRoleAssignmentException
51 | * @throws UnauthorizedOperationException
52 | */
53 | public function patchAction(string $id, Request $request): UserDto
54 | {
55 | $userToPatch = $this->getUserById($id);
56 | $this->patchRole($request, $userToPatch);
57 | return $userToPatch->toDto();
58 | }
59 |
60 | /**
61 | * @param Request $request
62 | * @param $userToPatch
63 | * @throws DuplicateRoleAssignmentException
64 | * @throws UnauthorizedOperationException
65 | */
66 | private function patchRole(Request $request, $userToPatch): void
67 | {
68 | $roleToAssign = $request->get('role');
69 | if (AppRole::isPassenger($roleToAssign)) {
70 | $this->user()->makeUserPassenger($userToPatch);
71 | } elseif (AppRole::isDriver($roleToAssign)) {
72 | $this->user()->makeUserDriver($userToPatch);
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/app/AppKernel.php:
--------------------------------------------------------------------------------
1 | getEnvironment(), ['test'], true)) {
23 | $bundles[] = new FOS\RestBundle\FOSRestBundle();
24 | $bundles[] = new JMS\SerializerBundle\JMSSerializerBundle();
25 | $bundles[] = new Nelmio\CorsBundle\NelmioCorsBundle();
26 | $bundles[] = new Symfony\Bundle\WebServerBundle\WebServerBundle();
27 | $bundles[] = new FOS\UserBundle\FOSUserBundle();
28 | $bundles[] = new FOS\OAuthServerBundle\FOSOAuthServerBundle();
29 | }
30 |
31 | if (in_array($this->getEnvironment(), ['dev', 'test'], true)) {
32 | $bundles[] = new Symfony\Bundle\DebugBundle\DebugBundle();
33 | $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
34 | $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle();
35 | $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle();
36 | }
37 |
38 | return $bundles;
39 | }
40 |
41 | public function getRootDir()
42 | {
43 | return __DIR__;
44 | }
45 |
46 | public function getCacheDir()
47 | {
48 | return dirname(__DIR__).'/var/cache/'.$this->getEnvironment();
49 | }
50 |
51 | public function getLogDir()
52 | {
53 | return dirname(__DIR__).'/var/logs';
54 | }
55 |
56 | /**
57 | * @param LoaderInterface $loader
58 | * @throws Exception
59 | */
60 | public function registerContainerConfiguration(LoaderInterface $loader)
61 | {
62 | $loader->load($this->getRootDir().'/config/config_'.$this->getEnvironment().'.yml');
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/tests/AppBundle/Location/LocationRepositoryTest.php:
--------------------------------------------------------------------------------
1 | getSavedLocation();
13 |
14 | self::assertNotNull($homeLocation);
15 | }
16 |
17 | public function testGetDupeLocationsReturnsFirst()
18 | {
19 | $homeLocation = $this->getSavedLocation();
20 | $dupeHomeLocation = $this->getSavedLocation();
21 | self::assertTrue($homeLocation->preDates($dupeHomeLocation));
22 | self::assertTrue($homeLocation->isSameAs($dupeHomeLocation));
23 | self::assertFalse($dupeHomeLocation->equals($homeLocation));
24 | $lookupLocation = AppLocation::cloneFrom($homeLocation);
25 |
26 | $retrievedLocation = $this->getOrCreateLocation($lookupLocation);
27 |
28 | self::assertTrue($homeLocation->isSameAs($retrievedLocation));
29 | self::assertTrue($dupeHomeLocation->isSameAs($retrievedLocation));
30 | self::assertTrue($homeLocation->equals($retrievedLocation));
31 | self::assertFalse($dupeHomeLocation->equals($retrievedLocation));
32 | }
33 |
34 | public function testGetExistingLocationByLatLong()
35 | {
36 | $savedLocation = $this->getSavedLocation();
37 | $lookupLocation = AppLocation::cloneFrom($savedLocation);
38 |
39 | $retrievedLocation = $this->getOrCreateLocation($lookupLocation);
40 |
41 | self::assertTrue($retrievedLocation->isSameAs($savedLocation));
42 | }
43 |
44 | public function testCreateAndGetNewLocation()
45 | {
46 | $workLocation = new AppLocation(
47 | LocationApi::WORK_LOCATION_LAT,
48 | LocationApi::WORK_LOCATION_LONG
49 | );
50 |
51 | $retrievedLocation = $this->getOrCreateLocation($workLocation);
52 |
53 | self::assertTrue($retrievedLocation->isSameAs($workLocation));
54 | }
55 |
56 | /**
57 | * @return AppLocation
58 | */
59 | private function getSavedLocation()
60 | {
61 | $homeLocation = new AppLocation(
62 | LocationApi::HOME_LOCATION_LAT,
63 | LocationApi::HOME_LOCATION_LONG
64 | );
65 |
66 | $this->save($homeLocation);
67 |
68 | return $homeLocation;
69 | }
70 |
71 | protected function getOrCreateLocation(AppLocation $lookupLocation)
72 | {
73 | return $this->location()->getRepo()->getLocation($lookupLocation);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | TDD Kata with Symfony and Doctrine
2 | ==================================
3 |
4 | ## Introduction:
5 |
6 | * Consult ride-hailing.svg to digest some key application concepts.
7 | * Consult Kata-Tasks.md to get an idea of the various tests you'll be writing and help shape your sequencing.
8 | * With this said, you do not have to follow the sequencing outlined.
9 |
10 | ## Initial Set-Up
11 |
12 | What you need:
13 |
14 | * php 7.2
15 | * mysql 5.7
16 | * composer
17 |
18 | Possible OS X Installation: (Adapt to your OS)
19 |
20 | * ( Install Brew: https://brew.sh )
21 | * brew unlink php@5.6 (if you already have php56)
22 | * You can later undo this if you wish:
23 | * brew unlink php@7.2
24 | * brew link php@5.6
25 | * brew link php@7.2
26 | * or brew install php@7.2
27 | * brew install sqlite
28 | * brew install mysql@5.7
29 | * brew install composer
30 |
31 | Checkout Code:
32 |
33 | * git clone https://github.com/elchris/kata_tdd_php_symfony.git
34 | * cd kata_tdd_php_symfony
35 | * switch to **clean-slate-with-acceptance** branch
36 | * git checkout **clean-slate-with-acceptance**
37 | * create new working branch from **clean-slate-with-acceptance**
38 | * git branch kata-run-1
39 | * git checkout kata-run-1
40 |
41 | Configure DB:
42 |
43 | * cd app/config
44 | * cp parameters.yml.dist parameters.yml
45 | * mysql.server start
46 | * log into mysql
47 | * create database symfony;
48 |
49 | Run:
50 |
51 | * cd ../..
52 | * composer install
53 | * vendor/bin/phpunit
54 | * bin/console server:start
55 | * vendor/bin/codecept run
56 |
57 | ## References
58 |
59 | Migrations:
60 |
61 | * Generate a single migration from the current state of the Entity Graph
62 | * bin/console doctrine:migrations:diff
63 | * Execute all current migrations
64 | * bin/console doctrine:migrations:migrate
65 |
66 | Generate:
67 |
68 | * vendor/bin/codecept generate:suite api
69 | * vendor/bin/codecept generate:cept api CreateUser
70 |
71 | References:
72 |
73 | * https://www.jetbrains.com/help/phpstorm/testing-with-codeception.html
74 | * https://www.jetbrains.com/help/idea/testing-with-codeception.html
75 | * https://laravel.com/docs/5.5/homestead#first-steps
76 | * https://gist.github.com/diegonobre/341eb7b793fc841c0bba3f2b865b8d66
77 |
78 | Alternatives:
79 |
80 | * https://github.com/msgphp/user-bundle
81 |
82 | Testing:
83 |
84 | * Implicit:
85 | * http://localhost:8000/oauth/v2/auth?client_id=1_3bcbxd9e24g0gk4swg0kwgcwg4o8k8g4g888kwc44gcc0gwwk4&redirect_uri=http://localhost:8000/&response_type=token
86 |
87 | Issues:
88 |
89 | * https://youtrack.jetbrains.com/issue/WI-40950
90 | * https://github.com/doctrine/doctrine2/issues/7306
91 |
92 |
93 | Stats:
94 | * [ Get more details at **codescene.io**.](http://codescene.io/projects/2090/jobs/latest-successful/results)
95 |
--------------------------------------------------------------------------------
/tests/AppBundle/User/UserServiceTest.php:
--------------------------------------------------------------------------------
1 | user()->getSavedUser();
16 | self::assertTrue($user->isNamed('chris holland'));
17 | }
18 |
19 | /**
20 | * @throws DuplicateRoleAssignmentException
21 | * @throws UnauthorizedOperationException
22 | */
23 | public function testRogueUserRoleAssignmentException()
24 | {
25 | $rogueUser = $this->user()->getSavedUserWithName('Rogue', 'User');
26 | $authenticatedUser = $this->user()->getSavedUserWithName('Authenticated', 'User');
27 | $this->user()->setAuthenticatedUser($authenticatedUser);
28 |
29 | $this->verifyExceptionWithMessage(
30 | UnauthorizedOperationException::class,
31 | UnauthorizedOperationException::MESSAGE
32 | );
33 | $this->user()->makeUserPassenger($rogueUser);
34 | }
35 |
36 | /**
37 | * @throws UserNotFoundException
38 | * @throws UnauthorizedOperationException
39 | */
40 | public function testRogueUserAccessException()
41 | {
42 | $rogueUser = $this->user()->getSavedUserWithName('Rogue', 'User');
43 | $authenticatedUser = $this->user()->getSavedUserWithName('Authenticated', 'User');
44 | $this->user()->setAuthenticatedUser($authenticatedUser);
45 |
46 | $this->verifyExceptionWithMessage(
47 | UnauthorizedOperationException::class,
48 | UnauthorizedOperationException::MESSAGE
49 | );
50 |
51 | $this->user()->getServiceUserById($rogueUser->getId());
52 | }
53 |
54 | /**
55 | * @throws DuplicateRoleAssignmentException
56 | * @throws UserNotFoundException
57 | * @throws UnauthorizedOperationException
58 | */
59 | public function testMakeUserDriver()
60 | {
61 | $savedUser = $this->user()->getSavedUser();
62 | $this->user()->makeUserDriver($savedUser);
63 | $retrievedUser = $this->user()->getServiceUserById($savedUser->getId());
64 |
65 | self::assertTrue($retrievedUser->userHasRole(AppRole::driver()));
66 | }
67 |
68 | /**
69 | * @throws DuplicateRoleAssignmentException
70 | * @throws UserNotFoundException
71 | * @throws UnauthorizedOperationException
72 | */
73 | public function testMakeUserPassenger()
74 | {
75 | $savedUser = $this->user()->getSavedUser();
76 | $this->user()->makeUserPassenger($savedUser);
77 | $retrievedUser = $this->user()->getServiceUserById($savedUser->getId());
78 |
79 | self::assertTrue($retrievedUser->userHasRole(AppRole::passenger()));
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/AppBundle/Repository/UserRepository.php:
--------------------------------------------------------------------------------
1 | userManager = $userManager;
26 | }
27 |
28 | /**
29 | * @param Uuid $userId
30 | * @return AppUser
31 | * @throws UserNotFoundException
32 | */
33 | public function getUserById(Uuid $userId): AppUser
34 | {
35 | try {
36 | return $this->em->createQuery(
37 | 'select u from E:AppUser u where u.id = :userId'
38 | )
39 | ->setParameter('userId', $userId)
40 | ->getSingleResult();
41 | } catch (\Exception $e) {
42 | throw new UserNotFoundException();
43 | }
44 | }
45 |
46 | /**
47 | * @param AppUser $user
48 | * @param AppRole $role
49 | * @throws DuplicateRoleAssignmentException
50 | */
51 | public function assignRoleToUser(AppUser $user, AppRole $role): void
52 | {
53 | if ($user->userHasRole($role)) {
54 | throw new DuplicateRoleAssignmentException();
55 | }
56 | $role = $this->getRoleReference($role);
57 | $user->assignRole($role);
58 | $this->save($user);
59 | }
60 |
61 | /**
62 | * @param AppUser $passedUser
63 | * @return AppUser
64 | */
65 | public function saveNewUser(AppUser $passedUser): AppUser
66 | {
67 | /** @var AppUser $user */
68 | $user = $this->userManager->createUser();
69 | $user->setFirstName($passedUser->getFirstName());
70 | $user->setLastName($passedUser->getLastName());
71 | $user->setUsername($passedUser->getUsername());
72 | $user->setEmail($passedUser->getEmail());
73 | $user->setPlainPassword($passedUser->getPlainPassword());
74 | $user->setEnabled(true);
75 | $this->userManager->updateUser($user);
76 | return $user;
77 | }
78 |
79 | /**
80 | * @param AppRole $role
81 | * @return null | AppRole
82 | */
83 | private function getRoleReference(AppRole $role): AppRole
84 | {
85 | /** @var AppRole $role */
86 | $role = $this->em->getRepository(AppRole::class)->findOneBy(
87 | [
88 | 'id' => $role->getId()
89 | ]
90 | );
91 | return $role;
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/app/config/frameworks.yml:
--------------------------------------------------------------------------------
1 | imports:
2 | - { resource: security.yml }
3 | - { resource: services.yml }
4 |
5 | framework:
6 | #esi: ~
7 | #translator: { fallbacks: ["%locale%"] }
8 | secret: "%secret%"
9 | router:
10 | resource: "%kernel.root_dir%/config/routing.yml"
11 | strict_requirements: ~
12 | form: ~
13 | csrf_protection: ~
14 | validation: { enable_annotations: true }
15 | #serializer: { enable_annotations: true }
16 | templating:
17 | engines: ['twig']
18 | default_locale: "%locale%"
19 | trusted_hosts: ~
20 | session:
21 | # http://symfony.com/doc/current/reference/configuration/framework.html#handler-id
22 | handler_id: session.handler.native_file
23 | save_path: "%kernel.root_dir%/../var/sessions/%kernel.environment%"
24 | fragments: ~
25 | http_method_override: true
26 | assets: ~
27 | php_errors:
28 | log: true
29 |
30 | doctrine_migrations:
31 | dir_name: "%kernel.root_dir%/DoctrineMigrations"
32 | namespace: Application\Migrations
33 | table_name: migration_versions
34 | name: Application Migrations
35 | organize_migrations: false
36 |
37 | # Nelmio CORS Configuration
38 | nelmio_cors:
39 | defaults:
40 | allow_credentials: false
41 | allow_origin: ['*']
42 | allow_headers: ['*']
43 | allow_methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS']
44 | max_age: 3600
45 | hosts: []
46 | origin_regex: false
47 |
48 | # FOSRest Configuration
49 | fos_rest:
50 | routing_loader:
51 | default_format: json
52 | include_format: false
53 | exception:
54 | enabled: true
55 | body_listener: true
56 | format_listener:
57 | rules:
58 | - { path: '^/api|oauth/token', priorities: ['json'], fallback_format: json, prefer_extension: false }
59 | - { path: '^/login', priorities: ['html'], fallback_format: html, prefer_extension: false }
60 | - { path: '^/oauth/v2/auth', priorities: ['html'], fallback_format: html, prefer_extension: false }
61 | - { path: '^/', priorities: ['html'], fallback_format: html, prefer_extension: false }
62 | param_fetcher_listener: true
63 | view:
64 | view_response_listener: 'force'
65 | formats:
66 | json: true
67 |
68 | fos_user:
69 | db_driver: orm # other valid values are 'mongodb', 'couchdb' and 'propel'
70 | firewall_name: api
71 | user_class: AppBundle\Entity\AppUser
72 | from_email:
73 | address: "noreply@yourcompany.com"
74 | sender_name: "No Reply"
75 |
76 | fos_oauth_server:
77 | db_driver: orm
78 | client_class: AppBundle\Entity\Client
79 | access_token_class: AppBundle\Entity\AccessToken
80 | refresh_token_class: AppBundle\Entity\RefreshToken
81 | auth_code_class: AppBundle\Entity\AuthCode
82 | service:
83 | user_provider: fos_user.user_provider.username # This property will be used when valid credentials are given to load the user upon access token creation
84 |
--------------------------------------------------------------------------------
/app/DoctrineMigrations/Version20180208222514.php:
--------------------------------------------------------------------------------
1 | abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
20 |
21 | $this->addSql('ALTER TABLE locations CHANGE id id CHAR(36) NOT NULL COMMENT \'(DC2Type:uuid)\'');
22 | $this->addSql('ALTER TABLE users ADD created DATETIME DEFAULT NULL, CHANGE id id CHAR(36) NOT NULL COMMENT \'(DC2Type:uuid)\'');
23 | $this->addSql('ALTER TABLE users_roles CHANGE userId userId CHAR(36) NOT NULL COMMENT \'(DC2Type:uuid)\'');
24 | $this->addSql('ALTER TABLE rides CHANGE id id CHAR(36) NOT NULL COMMENT \'(DC2Type:uuid)\', CHANGE passengerId passengerId CHAR(36) DEFAULT NULL COMMENT \'(DC2Type:uuid)\', CHANGE driverId driverId CHAR(36) DEFAULT NULL COMMENT \'(DC2Type:uuid)\', CHANGE departureId departureId CHAR(36) DEFAULT NULL COMMENT \'(DC2Type:uuid)\', CHANGE destinationId destinationId CHAR(36) DEFAULT NULL COMMENT \'(DC2Type:uuid)\'');
25 | $this->addSql('ALTER TABLE rideEvents CHANGE rideId rideId CHAR(36) DEFAULT NULL COMMENT \'(DC2Type:uuid)\', CHANGE userId userId CHAR(36) DEFAULT NULL COMMENT \'(DC2Type:uuid)\'');
26 | }
27 |
28 | /**
29 | * @param Schema $schema
30 | */
31 | public function down(Schema $schema)
32 | {
33 | // this down() migration is auto-generated, please modify it to your needs
34 | $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
35 |
36 | $this->addSql('ALTER TABLE locations CHANGE id id CHAR(36) NOT NULL COLLATE utf8_unicode_ci COMMENT \'(DC2Type:guid)\'');
37 | $this->addSql('ALTER TABLE rideEvents CHANGE rideId rideId CHAR(36) DEFAULT NULL COLLATE utf8_unicode_ci COMMENT \'(DC2Type:guid)\', CHANGE userId userId CHAR(36) DEFAULT NULL COLLATE utf8_unicode_ci COMMENT \'(DC2Type:guid)\'');
38 | $this->addSql('ALTER TABLE rides CHANGE id id CHAR(36) NOT NULL COLLATE utf8_unicode_ci COMMENT \'(DC2Type:guid)\', CHANGE passengerId passengerId CHAR(36) DEFAULT NULL COLLATE utf8_unicode_ci COMMENT \'(DC2Type:guid)\', CHANGE driverId driverId CHAR(36) DEFAULT NULL COLLATE utf8_unicode_ci COMMENT \'(DC2Type:guid)\', CHANGE departureId departureId CHAR(36) DEFAULT NULL COLLATE utf8_unicode_ci COMMENT \'(DC2Type:guid)\', CHANGE destinationId destinationId CHAR(36) DEFAULT NULL COLLATE utf8_unicode_ci COMMENT \'(DC2Type:guid)\'');
39 | $this->addSql('ALTER TABLE users DROP created, CHANGE id id CHAR(36) NOT NULL COLLATE utf8_unicode_ci COMMENT \'(DC2Type:guid)\'');
40 | $this->addSql('ALTER TABLE users_roles CHANGE userId userId CHAR(36) NOT NULL COLLATE utf8_unicode_ci COMMENT \'(DC2Type:guid)\'');
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chrisholland/kata_tdd_php_symfony",
3 | "type": "project",
4 | "description" : "Various exercises in Test-Driven Development of projects built in PHP with Symfony 3",
5 | "keywords": ["TDD", "kata", "symfony", "php"],
6 | "homepage": "https://github.com/elchris/kata_tdd_php_symfony",
7 | "license": "MIT",
8 | "authors": [
9 | {
10 | "name": "Chris Holland",
11 | "email": "frenchy@gmail.com",
12 | "homepage": "http://www.linkedin.com/in/chrisholland",
13 | "role": "Developer"
14 | }
15 | ],
16 | "autoload": {
17 | "psr-4": {
18 | "": "src/"
19 | },
20 | "classmap": [
21 | "app/AppKernel.php",
22 | "app/AppCache.php"
23 | ]
24 | },
25 | "autoload-dev": {
26 | "psr-4": {
27 | "Tests\\": "tests/"
28 | }
29 | },
30 | "require": {
31 | "php": ">=7.2",
32 | "doctrine/orm": "^2.6",
33 | "doctrine/doctrine-bundle": "^1.6",
34 | "doctrine/doctrine-cache-bundle": "^1.3",
35 | "symfony/swiftmailer-bundle": "^2.3",
36 | "symfony/monolog-bundle": "^3.0",
37 | "symfony/polyfill-apcu": "^1.0",
38 | "sensio/distribution-bundle": "^5.0",
39 | "sensio/framework-extra-bundle": "^3.0.2",
40 | "incenteev/composer-parameter-handler": "^2.0",
41 | "doctrine/doctrine-migrations-bundle": "^1.3",
42 | "squizlabs/php_codesniffer": "*",
43 | "friendsofsymfony/rest-bundle": "^2.3",
44 | "jms/serializer-bundle": "^2.3",
45 | "nelmio/cors-bundle": "^1.5",
46 | "ramsey/uuid-doctrine": "^1.5",
47 | "symfony/symfony": "3.4.*",
48 | "friendsofsymfony/user-bundle": "^2.0",
49 | "friendsofsymfony/oauth-server-bundle": "^1.5"
50 | },
51 | "require-dev": {
52 | "sensio/generator-bundle": "^3.0",
53 | "symfony/phpunit-bridge": "^4.2",
54 | "phpunit/phpunit": "^7.0",
55 | "codeception/codeception": "^2.4"
56 | },
57 | "scripts": {
58 | "symfony-scripts": [
59 | "Incenteev\\ParameterHandler\\ScriptHandler::buildParameters",
60 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap",
61 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::clearCache",
62 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installAssets",
63 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installRequirementsFile",
64 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::prepareDeploymentTarget"
65 | ],
66 | "post-install-cmd": [
67 | "@symfony-scripts"
68 | ],
69 | "post-update-cmd": [
70 | "@symfony-scripts"
71 | ]
72 | },
73 | "extra": {
74 | "symfony-app-dir": "app",
75 | "symfony-bin-dir": "bin",
76 | "symfony-var-dir": "var",
77 | "symfony-web-dir": "web",
78 | "symfony-tests-dir": "tests",
79 | "symfony-assets-install": "relative",
80 | "incenteev-parameters": {
81 | "file": "app/config/parameters.yml"
82 | },
83 | "branch-alias": null
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/web/.htaccess:
--------------------------------------------------------------------------------
1 | # Use the front controller as index file. It serves as a fallback solution when
2 | # every other rewrite/redirect fails (e.g. in an aliased environment without
3 | # mod_rewrite). Additionally, this reduces the matching process for the
4 | # start page (path "/") because otherwise Apache will apply the rewriting rules
5 | # to each configured DirectoryIndex file (e.g. index.php, index.html, index.pl).
6 | DirectoryIndex app.php
7 |
8 | # By default, Apache does not evaluate symbolic links if you did not enable this
9 | # feature in your server configuration. Uncomment the following line if you
10 | # install assets as symlinks or if you experience problems related to symlinks
11 | # when compiling LESS/Sass/CoffeScript assets.
12 | # Options FollowSymlinks
13 |
14 | # Disabling MultiViews prevents unwanted negotiation, e.g. "/app" should not resolve
15 | # to the front controller "/app.php" but be rewritten to "/app.php/app".
16 |
17 | Options -MultiViews
18 |
19 |
20 |
21 | RewriteEngine On
22 |
23 | # Determine the RewriteBase automatically and set it as environment variable.
24 | # If you are using Apache aliases to do mass virtual hosting or installed the
25 | # project in a subdirectory, the base path will be prepended to allow proper
26 | # resolution of the app.php file and to redirect to the correct URI. It will
27 | # work in environments without path prefix as well, providing a safe, one-size
28 | # fits all solution. But as you do not need it in this case, you can comment
29 | # the following 2 lines to eliminate the overhead.
30 | RewriteCond %{REQUEST_URI}::$1 ^(/.+)/(.*)::\2$
31 | RewriteRule ^(.*) - [E=BASE:%1]
32 |
33 | # Sets the HTTP_AUTHORIZATION header removed by Apache
34 | RewriteCond %{HTTP:Authorization} .
35 | RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
36 |
37 | # Redirect to URI without front controller to prevent duplicate content
38 | # (with and without `/app.php`). Only do this redirect on the initial
39 | # rewrite by Apache and not on subsequent cycles. Otherwise we would get an
40 | # endless redirect loop (request -> rewrite to front controller ->
41 | # redirect -> request -> ...).
42 | # So in case you get a "too many redirects" error or you always get redirected
43 | # to the start page because your Apache does not expose the REDIRECT_STATUS
44 | # environment variable, you have 2 choices:
45 | # - disable this feature by commenting the following 2 lines or
46 | # - use Apache >= 2.3.9 and replace all L flags by END flags and remove the
47 | # following RewriteCond (best solution)
48 | RewriteCond %{ENV:REDIRECT_STATUS} ^$
49 | RewriteRule ^app\.php(?:/(.*)|$) %{ENV:BASE}/$1 [R=301,L]
50 |
51 | # If the requested filename exists, simply serve it.
52 | # We only want to let Apache serve files and not directories.
53 | RewriteCond %{REQUEST_FILENAME} -f
54 | RewriteRule ^ - [L]
55 |
56 | # Rewrite all other queries to the front controller.
57 | RewriteRule ^ %{ENV:BASE}/app.php [L]
58 |
59 |
60 |
61 |
62 | # When mod_rewrite is not available, we instruct a temporary redirect of
63 | # the start page to the front controller explicitly so that the website
64 | # and the generated links can still be used.
65 | RedirectMatch 302 ^/$ /app.php/
66 | # RedirectTemp cannot be used instead
67 |
68 |
69 |
--------------------------------------------------------------------------------
/src/AppBundle/Service/UserService.php:
--------------------------------------------------------------------------------
1 | userRepository = $userRepository;
28 | }
29 |
30 | public function setAuthenticatedUser(AppUser $user): void
31 | {
32 | $this->authenticatedUser = $user;
33 | }
34 |
35 | /**
36 | * @param $firstName
37 | * @param $lastName
38 | * @param $email
39 | * @param $username
40 | * @param $password
41 | * @return AppUser
42 | * @throws \Exception
43 | */
44 | public function newUser($firstName, $lastName, $email, $username, $password) : AppUser
45 | {
46 | $newUser = new AppUser($firstName, $lastName, $email, $username, $password);
47 | return $this->userRepository->saveNewUser($newUser);
48 | }
49 |
50 | /**
51 | * @param Uuid $userId
52 | * @return AppUser
53 | * @throws UserNotFoundException
54 | * @throws UnauthorizedOperationException
55 | */
56 | public function getUserById(Uuid $userId) : AppUser
57 | {
58 | $this->verifyAuthenticatedId($userId);
59 | return $this->userRepository->getUserById($userId);
60 | }
61 |
62 | /**
63 | * @param AppUser $user
64 | * @throws DuplicateRoleAssignmentException
65 | * @throws UnauthorizedOperationException
66 | */
67 | public function makeUserDriver(AppUser $user): void
68 | {
69 | $this->assignRole($user, AppRole::driver());
70 | }
71 |
72 | /**
73 | * @param AppUser $user
74 | * @throws DuplicateRoleAssignmentException
75 | * @throws UnauthorizedOperationException
76 | */
77 | public function makeUserPassenger(AppUser $user): void
78 | {
79 | $this->assignRole($user, AppRole::passenger());
80 | }
81 |
82 | /**
83 | * @param AppUser $user
84 | * @param AppRole $role
85 | * @throws DuplicateRoleAssignmentException
86 | * @throws UnauthorizedOperationException
87 | */
88 | private function assignRole(AppUser $user, AppRole $role): void
89 | {
90 | $this->verifyAuthenticatedUser($user);
91 | $this->userRepository->assignRoleToUser($user, $role);
92 | }
93 |
94 | /**
95 | * @param Uuid $userId
96 | * @throws UnauthorizedOperationException
97 | */
98 | private function verifyAuthenticatedId(Uuid $userId): void
99 | {
100 | if (!$this->authenticatedUser->getId()->equals($userId)) {
101 | throw new UnauthorizedOperationException();
102 | }
103 | }
104 |
105 | /**
106 | * @param AppUser $user
107 | * @throws UnauthorizedOperationException
108 | */
109 | private function verifyAuthenticatedUser(AppUser $user): void
110 | {
111 | if (!$user->is($this->authenticatedUser)) {
112 | throw new UnauthorizedOperationException();
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/tests/AppBundle/AppTestCase.php:
--------------------------------------------------------------------------------
1 | em = static::$kernel->getContainer()->get('doctrine')->getManager();
40 | $this->userManager = new FakeUserManager($this->em());
41 | $this->setUpEntityManager();
42 |
43 | $this->ride()->bootStrapRideEventTypes();
44 | $this->user()->bootStrapRoles();
45 | }
46 |
47 | protected function em()
48 | {
49 | return $this->em;
50 | }
51 |
52 | protected function save($entity)
53 | {
54 | $this->em->persist($entity);
55 | $this->em->flush();
56 | return $entity;
57 | }
58 |
59 | /**
60 | * @param $firstName
61 | * @param $lastName
62 | * @return AppUser
63 | */
64 | protected function newNamedUser($firstName, $lastName): AppUser
65 | {
66 | return (new FakeUser($firstName, $lastName))->toEntity();
67 | }
68 |
69 | private function setUpEntityManager()
70 | {
71 | $classes = $this->em()->getMetadataFactory()->getAllMetadata();
72 | $tool = new SchemaTool($this->em);
73 | $tool->dropSchema($classes);
74 | try {
75 | $tool->createSchema($classes);
76 | } catch (ToolsException $e) {
77 | }
78 | }
79 |
80 | /**
81 | * @param string $class
82 | * @param string $message
83 | */
84 | protected function verifyExceptionWithMessage(string $class, string $message): void
85 | {
86 | $this->expectException($class);
87 | $this->expectExceptionMessage($message);
88 | }
89 |
90 | /**
91 | * @return UserApi
92 | */
93 | protected function user()
94 | {
95 | if (is_null($this->userApi)) {
96 | $this->userApi = new UserApi(
97 | $this->em(),
98 | $this->userManager
99 | );
100 | }
101 | return $this->userApi;
102 | }
103 |
104 | /**
105 | * @return LocationApi
106 | */
107 | protected function location()
108 | {
109 | if (is_null($this->locationApi)) {
110 | $this->locationApi = new LocationApi($this->em());
111 | }
112 | return $this->locationApi;
113 | }
114 |
115 | /**
116 | * @return RideApi
117 | */
118 | protected function ride()
119 | {
120 | if (is_null($this->rideApi)) {
121 | $this->rideApi = new RideApi(
122 | $this->em(),
123 | $this->user(),
124 | $this->location()
125 | );
126 | }
127 | return $this->rideApi;
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/AppBundle/Entity/Ride.php:
--------------------------------------------------------------------------------
1 | id = Uuid::uuid4();
70 | $this->passenger = $passenger;
71 | $this->departure = $departure;
72 | $this->created = new \DateTime(null, new \DateTimeZone('UTC'));
73 | }
74 |
75 | public function getId(): Uuid
76 | {
77 | return $this->id;
78 | }
79 |
80 | public function assignDestination(AppLocation $destination): void
81 | {
82 | $this->destination = $destination;
83 | }
84 |
85 | public function hasDestination(): bool
86 | {
87 | return ! is_null($this->destination);
88 | }
89 |
90 | public function assignDriver(AppUser $driver): void
91 | {
92 | $this->driver = $driver;
93 | }
94 |
95 | public function isDrivenBy(AppUser $driver): bool
96 | {
97 | return $this->driver->is($driver);
98 | }
99 |
100 | public function hasDriver(): bool
101 | {
102 | return ! is_null($this->driver);
103 | }
104 |
105 | public function isDestinedFor(AppLocation $destinationLocation): bool
106 | {
107 | return $this->destination->isSameAs($destinationLocation);
108 | }
109 |
110 | public function getPassengerTransaction(RideEventType $status): RideEvent
111 | {
112 | return new RideEvent(
113 | $this,
114 | $this->passenger,
115 | $status
116 | );
117 | }
118 |
119 | public function is(Ride $rideToCompare): bool
120 | {
121 | return $this->id->equals($rideToCompare->id);
122 | }
123 |
124 | public function toDto(): RideDto
125 | {
126 | return new RideDto(
127 | $this,
128 | $this->passenger,
129 | $this->driver,
130 | $this->destination
131 | );
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/tests/AppBundle/Ride/RideTransitionTest.php:
--------------------------------------------------------------------------------
1 | user()->getNewDriver();
32 | $ride = $this->ride()->getSavedNewRideWithPassengerAndDestination();
33 |
34 | $this->assertRidePatchEvent($ride, RideEventType::ACCEPTED_ID, $driver);
35 | $this->assertRidePatchEvent($ride, RideEventType::IN_PROGRESS_ID, $driver);
36 | $this->assertRidePatchEvent($ride, RideEventType::COMPLETED_ID, $driver);
37 | $this->assertRidePatchEvent($ride, null, $driver);
38 | }
39 |
40 | /**
41 | * @throws ActingDriverIsNotAssignedDriverException
42 | * @throws DuplicateRoleAssignmentException
43 | * @throws RideLifeCycleException
44 | * @throws RideNotFoundException
45 | * @throws UserNotFoundException
46 | * @throws UserNotInDriverRoleException
47 | * @throws UserNotInPassengerRoleException
48 | * @throws UnauthorizedOperationException
49 | */
50 | public function testPatchRideLifeCycleNullDriverIdAndEventId()
51 | {
52 | $ride = $this->ride()->getSavedNewRideWithPassengerAndDestination();
53 | $patchedRide = $this->ride()->updateRideByDriverAndEventId(
54 | $ride,
55 | null,
56 | null
57 | );
58 | self::assertTrue(
59 | RideEventType::requested()->equals(
60 | $this->ride()->getRideStatus($patchedRide)
61 | )
62 | );
63 | }
64 |
65 | /**
66 | * @param Ride $ride
67 | * @param string $eventId |null
68 | * @param AppUser $driver
69 | * @return Ride
70 | * @throws ActingDriverIsNotAssignedDriverException
71 | * @throws RideLifeCycleException
72 | * @throws RideNotFoundException
73 | * @throws UserNotInDriverRoleException
74 | * @throws UserNotFoundException
75 | * @throws UnauthorizedOperationException
76 | */
77 | private function assertRidePatchEvent(Ride $ride, string $eventId = null, AppUser $driver): Ride
78 | {
79 | $patchedRide = $this->ride()->updateRideByDriverAndEventId(
80 | $ride,
81 | $eventId,
82 | $driver->getId()->toString()
83 | );
84 |
85 | self::assertTrue($ride->isDrivenBy($driver));
86 | if (! is_null($eventId)) {
87 | self::assertTrue(
88 | RideEventType::newById(intval($eventId))->equals(
89 | $this->ride()->getRideStatus($patchedRide)
90 | )
91 | );
92 | } else {
93 | self::assertFalse(
94 | RideEventType::newById(intval($eventId))->equals(
95 | $this->ride()->getRideStatus($patchedRide)
96 | )
97 | );
98 | }
99 | return $patchedRide;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/AppBundle/Service/RideTransitionService.php:
--------------------------------------------------------------------------------
1 | rideService = $rideService;
31 | $this->userService = $userService;
32 | }
33 |
34 | /**
35 | * @param Ride $ride
36 | * @param string $eventId |null
37 | * @param string $driverId |null
38 | * @return Ride
39 | * @throws ActingDriverIsNotAssignedDriverException
40 | * @throws RideLifeCycleException
41 | * @throws RideNotFoundException
42 | * @throws UserNotFoundException
43 | * @throws UserNotInDriverRoleException
44 | * @throws UnauthorizedOperationException
45 | */
46 | public function updateRideByDriverAndEventId(Ride $ride, string $eventId = null, string $driverId = null) : Ride
47 | {
48 | if (! is_null($driverId)) {
49 | /** @var Uuid $uuid */
50 | $uuid = Uuid::fromString($driverId);
51 | $driver = $this->userService->getUserById($uuid);
52 | $eventToProcess = RideEventType::newById(intval($eventId));
53 | $this->patchRideAcceptance($eventToProcess, $ride, $driver);
54 | $this->patchRideInProgress($eventToProcess, $ride, $driver);
55 | $this->patchRideCompleted($eventToProcess, $ride, $driver);
56 | }
57 | return $ride;
58 | }
59 |
60 | /**
61 | * @param RideEventType $eventToProcess
62 | * @param Ride $rideToPatch
63 | * @param AppUser $driver
64 | * @throws RideLifeCycleException
65 | * @throws RideNotFoundException
66 | * @throws UserNotInDriverRoleException
67 | */
68 | private function patchRideAcceptance(RideEventType $eventToProcess, Ride $rideToPatch, AppUser $driver): void
69 | {
70 | if (RideEventType::accepted()->equals($eventToProcess)) {
71 | $this->rideService->acceptRide($rideToPatch, $driver);
72 | }
73 | }
74 |
75 | /**
76 | * @param RideEventType $eventToProcess
77 | * @param Ride $rideToPatch
78 | * @param AppUser $driver
79 | * @throws ActingDriverIsNotAssignedDriverException
80 | * @throws RideLifeCycleException
81 | * @throws RideNotFoundException
82 | * @throws UserNotInDriverRoleException
83 | */
84 | private function patchRideInProgress(RideEventType $eventToProcess, Ride $rideToPatch, AppUser $driver): void
85 | {
86 | if (RideEventType::inProgress()->equals($eventToProcess)) {
87 | $this->rideService->markRideInProgress($rideToPatch, $driver);
88 | }
89 | }
90 |
91 | /**
92 | * @param RideEventType $eventToProcess
93 | * @param Ride $rideToPatch
94 | * @param AppUser $driver
95 | * @throws ActingDriverIsNotAssignedDriverException
96 | * @throws RideLifeCycleException
97 | * @throws RideNotFoundException
98 | * @throws UserNotInDriverRoleException
99 | */
100 | private function patchRideCompleted(RideEventType $eventToProcess, Ride $rideToPatch, AppUser $driver): void
101 | {
102 | if (RideEventType::completed()->equals($eventToProcess)) {
103 | $this->rideService->markRideCompleted($rideToPatch, $driver);
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/tests/AppBundle/User/FakeUserManager.php:
--------------------------------------------------------------------------------
1 | entityManager = $entityManager;
26 | }
27 |
28 | /**
29 | * Creates an empty user instance.
30 | *
31 | * @return UserInterface
32 | * @throws \Exception
33 | */
34 | public function createUser()
35 | {
36 | $this->user = new AppUser();
37 | return $this->user;
38 | }
39 |
40 | /**
41 | * Deletes a user.
42 | *
43 | * @param UserInterface $user
44 | */
45 | public function deleteUser(UserInterface $user)
46 | {
47 | //no-op
48 | }
49 |
50 | /**
51 | * Finds one user by the given criteria.
52 | *
53 | * @param array $criteria
54 | *
55 | * @return UserInterface
56 | */
57 | public function findUserBy(array $criteria)
58 | {
59 | return $this->user;
60 | }
61 |
62 | /**
63 | * Find a user by its username.
64 | *
65 | * @param string $username
66 | *
67 | * @return UserInterface or null if user does not exist
68 | */
69 | public function findUserByUsername($username)
70 | {
71 | return $this->user;
72 | }
73 |
74 | /**
75 | * Finds a user by its email.
76 | *
77 | * @param string $email
78 | *
79 | * @return UserInterface or null if user does not exist
80 | */
81 | public function findUserByEmail($email)
82 | {
83 | return $this->user;
84 | }
85 |
86 | /**
87 | * Finds a user by its username or email.
88 | *
89 | * @param string $usernameOrEmail
90 | *
91 | * @return UserInterface or null if user does not exist
92 | */
93 | public function findUserByUsernameOrEmail($usernameOrEmail)
94 | {
95 | return $this->user;
96 | }
97 |
98 | /**
99 | * Finds a user by its confirmationToken.
100 | *
101 | * @param string $token
102 | *
103 | * @return UserInterface or null if user does not exist
104 | */
105 | public function findUserByConfirmationToken($token)
106 | {
107 | return $this->user;
108 | }
109 |
110 | /**
111 | * Returns a collection with all user instances.
112 | *
113 | * @return \Traversable
114 | */
115 | public function findUsers()
116 | {
117 | return new ArrayCollection([$this->user]);
118 | }
119 |
120 | /**
121 | * Returns the user's fully qualified class name.
122 | *
123 | * @return string
124 | */
125 | public function getClass()
126 | {
127 | return '';
128 | }
129 |
130 | /**
131 | * Reloads a user.
132 | *
133 | * @param UserInterface $user
134 | */
135 | public function reloadUser(UserInterface $user)
136 | {
137 | //no-op
138 | }
139 |
140 | /**
141 | * Updates a user.
142 | *
143 | * @param UserInterface $user
144 | */
145 | public function updateUser(UserInterface $user)
146 | {
147 | $this->entityManager->persist($user);
148 | $this->entityManager->flush();
149 | }
150 |
151 | /**
152 | * Updates the canonical username and email fields for a user.
153 | *
154 | * @param UserInterface $user
155 | */
156 | public function updateCanonicalFields(UserInterface $user)
157 | {
158 | //no-op
159 | }
160 |
161 | /**
162 | * Updates a user password if a plain password is set.
163 | *
164 | * @param UserInterface $user
165 | */
166 | public function updatePassword(UserInterface $user)
167 | {
168 | //no-op
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/src/AppBundle/Controller/AppController.php:
--------------------------------------------------------------------------------
1 | container->get('fos_user.user_manager.public');
43 | return $manager;
44 | }
45 |
46 | /**
47 | * @return UserService
48 | */
49 | protected function user(): UserService
50 | {
51 | $authenticatedUser = $this->getUser();
52 | if (is_null($this->userService)) {
53 | $this->userService = new UserService(new UserRepository(
54 | $this->em(),
55 | $this->getUserManager()
56 | ));
57 | if (! is_null($authenticatedUser)) {
58 | $this->userService->setAuthenticatedUser(
59 | $authenticatedUser
60 | );
61 | }
62 | }
63 | return $this->userService;
64 | }
65 |
66 | /**
67 | * @return RideService
68 | */
69 | protected function ride(): RideService
70 | {
71 | if (is_null($this->rideService)) {
72 | $this->rideService = new RideService(
73 | new RideRepository($this->em()),
74 | new RideEventRepository($this->em())
75 | );
76 | }
77 |
78 | return $this->rideService;
79 | }
80 |
81 | protected function rideTransition(): RideTransitionService
82 | {
83 | if (is_null($this->rideTransitionService)) {
84 | $this->rideTransitionService = new RideTransitionService(
85 | $this->ride(),
86 | $this->user()
87 | );
88 | }
89 |
90 | return $this->rideTransitionService;
91 | }
92 |
93 | /**
94 | * @return LocationService
95 | */
96 | protected function location(): LocationService
97 | {
98 | if (is_null($this->locationService)) {
99 | $this->locationService = new LocationService(
100 | new LocationRepository($this->em())
101 | );
102 | }
103 |
104 | return $this->locationService;
105 | }
106 |
107 | /**
108 | * @param string $id
109 | * @return Uuid
110 | */
111 | protected function id(string $id): Uuid
112 | {
113 | /** @var Uuid $uuid */
114 | $uuid = Uuid::fromString($id);
115 | return $uuid;
116 | }
117 |
118 | /**
119 | * @return EntityManagerInterface
120 | */
121 | private function em(): EntityManagerInterface
122 | {
123 | /** @var EntityManagerInterface $em */
124 | $em = $this->getDoctrine()->getManager();
125 | return $em;
126 | }
127 |
128 | /**
129 | * @param string $id
130 | * @return AppUser
131 | * @throws UserNotFoundException
132 | * @throws UnauthorizedOperationException
133 | */
134 | protected function getUserById(string $id): AppUser
135 | {
136 | return $this->user()->getUserById($this->id($id));
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/AppBundle/Controller/RideController.php:
--------------------------------------------------------------------------------
1 | getUserById(
33 | $request->get('passengerId')
34 | );
35 | $departure = $this->location()->getLocation(
36 | $request->get('departureLat'),
37 | $request->get('departureLong')
38 | );
39 | return $this
40 | ->ride()
41 | ->newRide($passenger, $departure)
42 | ->toDto()
43 | ;
44 | }
45 |
46 | /**
47 | * @Rest\Get("/api/v1/ride/{rideId}")
48 | * @param string $rideId
49 | * @return RideDto
50 | * @throws RideNotFoundException
51 | */
52 | public function idAction(string $rideId): RideDto
53 | {
54 | return $this->getRide($rideId)->toDto();
55 | }
56 |
57 | /**
58 | * @Rest\Get("/api/v1/ride/{rideId}/status")
59 | * @param string $rideId
60 | * @return RideEventType
61 | * @throws RideNotFoundException
62 | */
63 | public function statusAction(string $rideId): RideEventType
64 | {
65 | return $this->ride()->getRideStatus(
66 | $this->getRide($rideId)
67 | );
68 | }
69 |
70 | /**
71 | * @Rest\Patch("/api/v1/ride/{rideId}")
72 | * @param string $rideId
73 | * @param Request $request
74 | * @return RideDto
75 | * @throws RideNotFoundException
76 | * @throws UserNotFoundException
77 | * @throws RideLifeCycleException
78 | * @throws UserNotInDriverRoleException
79 | * @throws ActingDriverIsNotAssignedDriverException
80 | * @throws UnauthorizedOperationException
81 | * @throws \Exception
82 | */
83 | public function patchAction(string $rideId, Request $request): RideDto
84 | {
85 | $rideToPatch = $this->getRide($rideId);
86 | $eventId = $request->get('eventId');
87 | $driverId = $request->get('driverId');
88 | $destinationLat = $request->get('destinationLat');
89 | $destinationLong = $request->get('destinationLong');
90 | $this->rideTransition()->updateRideByDriverAndEventId(
91 | $rideToPatch,
92 | $eventId,
93 | $driverId
94 | );
95 | $this->patchRideDestination($destinationLat, $destinationLong, $rideToPatch);
96 | return $rideToPatch->toDto();
97 | }
98 |
99 | /**
100 | * @param string $rideId
101 | * @return Ride
102 | * @throws RideNotFoundException
103 | */
104 | private function getRide(string $rideId): Ride
105 | {
106 | return $this->ride()->getRide($this->id($rideId));
107 | }
108 |
109 | /**
110 | * @param $destinationLat
111 | * @param $destinationLong
112 | * @param Ride $rideToPatch
113 | * @throws \Exception
114 | */
115 | private function patchRideDestination($destinationLat, $destinationLong, Ride $rideToPatch): void
116 | {
117 | if (!is_null($destinationLat) && !is_null($destinationLong)) {
118 | $this->ride()->assignDestinationToRide(
119 | $rideToPatch,
120 | $this->location()->getLocation(
121 | floatval($destinationLat),
122 | floatval($destinationLong)
123 | )
124 | );
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/bin/symfony_requirements:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | getPhpIniConfigPath();
9 |
10 | echo_title('Symfony Requirements Checker');
11 |
12 | echo '> PHP is using the following php.ini file:'.PHP_EOL;
13 | if ($iniPath) {
14 | echo_style('green', ' '.$iniPath);
15 | } else {
16 | echo_style('yellow', ' WARNING: No configuration file (php.ini) used by PHP!');
17 | }
18 |
19 | echo PHP_EOL.PHP_EOL;
20 |
21 | echo '> Checking Symfony requirements:'.PHP_EOL.' ';
22 |
23 | $messages = array();
24 | foreach ($symfonyRequirements->getRequirements() as $req) {
25 | if ($helpText = get_error_message($req, $lineSize)) {
26 | echo_style('red', 'E');
27 | $messages['error'][] = $helpText;
28 | } else {
29 | echo_style('green', '.');
30 | }
31 | }
32 |
33 | $checkPassed = empty($messages['error']);
34 |
35 | foreach ($symfonyRequirements->getRecommendations() as $req) {
36 | if ($helpText = get_error_message($req, $lineSize)) {
37 | echo_style('yellow', 'W');
38 | $messages['warning'][] = $helpText;
39 | } else {
40 | echo_style('green', '.');
41 | }
42 | }
43 |
44 | if ($checkPassed) {
45 | echo_block('success', 'OK', 'Your system is ready to run Symfony projects');
46 | } else {
47 | echo_block('error', 'ERROR', 'Your system is not ready to run Symfony projects');
48 |
49 | echo_title('Fix the following mandatory requirements', 'red');
50 |
51 | foreach ($messages['error'] as $helpText) {
52 | echo ' * '.$helpText.PHP_EOL;
53 | }
54 | }
55 |
56 | if (!empty($messages['warning'])) {
57 | echo_title('Optional recommendations to improve your setup', 'yellow');
58 |
59 | foreach ($messages['warning'] as $helpText) {
60 | echo ' * '.$helpText.PHP_EOL;
61 | }
62 | }
63 |
64 | echo PHP_EOL;
65 | echo_style('title', 'Note');
66 | echo ' The command console could use a different php.ini file'.PHP_EOL;
67 | echo_style('title', '~~~~');
68 | echo ' than the one used with your web server. To be on the'.PHP_EOL;
69 | echo ' safe side, please check the requirements from your web'.PHP_EOL;
70 | echo ' server using the ';
71 | echo_style('yellow', 'web/config.php');
72 | echo ' script.'.PHP_EOL;
73 | echo PHP_EOL;
74 |
75 | exit($checkPassed ? 0 : 1);
76 |
77 | function get_error_message(Requirement $requirement, $lineSize)
78 | {
79 | if ($requirement->isFulfilled()) {
80 | return;
81 | }
82 |
83 | $errorMessage = wordwrap($requirement->getTestMessage(), $lineSize - 3, PHP_EOL.' ').PHP_EOL;
84 | $errorMessage .= ' > '.wordwrap($requirement->getHelpText(), $lineSize - 5, PHP_EOL.' > ').PHP_EOL;
85 |
86 | return $errorMessage;
87 | }
88 |
89 | function echo_title($title, $style = null)
90 | {
91 | $style = $style ?: 'title';
92 |
93 | echo PHP_EOL;
94 | echo_style($style, $title.PHP_EOL);
95 | echo_style($style, str_repeat('~', strlen($title)).PHP_EOL);
96 | echo PHP_EOL;
97 | }
98 |
99 | function echo_style($style, $message)
100 | {
101 | // ANSI color codes
102 | $styles = array(
103 | 'reset' => "\033[0m",
104 | 'red' => "\033[31m",
105 | 'green' => "\033[32m",
106 | 'yellow' => "\033[33m",
107 | 'error' => "\033[37;41m",
108 | 'success' => "\033[37;42m",
109 | 'title' => "\033[34m",
110 | );
111 | $supports = has_color_support();
112 |
113 | echo($supports ? $styles[$style] : '').$message.($supports ? $styles['reset'] : '');
114 | }
115 |
116 | function echo_block($style, $title, $message)
117 | {
118 | $message = ' '.trim($message).' ';
119 | $width = strlen($message);
120 |
121 | echo PHP_EOL.PHP_EOL;
122 |
123 | echo_style($style, str_repeat(' ', $width));
124 | echo PHP_EOL;
125 | echo_style($style, str_pad(' ['.$title.']', $width, ' ', STR_PAD_RIGHT));
126 | echo PHP_EOL;
127 | echo_style($style, $message);
128 | echo PHP_EOL;
129 | echo_style($style, str_repeat(' ', $width));
130 | echo PHP_EOL;
131 | }
132 |
133 | function has_color_support()
134 | {
135 | static $support;
136 |
137 | if (null === $support) {
138 | if (DIRECTORY_SEPARATOR == '\\') {
139 | $support = false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI');
140 | } else {
141 | $support = function_exists('posix_isatty') && @posix_isatty(STDOUT);
142 | }
143 | }
144 |
145 | return $support;
146 | }
147 |
--------------------------------------------------------------------------------
/tests/AppBundle/User/UserRepositoryTest.php:
--------------------------------------------------------------------------------
1 | user()->getSavedUser();
21 | $this->em()->clear();
22 | $retrievedUser = $this->user()->getRepo()->getUserById($user->getId());
23 | self::assertTrue($retrievedUser->is($user));
24 | }
25 |
26 | public function testCreateAndSaveNewUser(): void
27 | {
28 | $user = $this->user()->getSavedUser();
29 |
30 | self::assertNotNull($user);
31 | }
32 |
33 | /**
34 | * @throws UserNotFoundException
35 | */
36 | public function testGetUserById(): void
37 | {
38 | $savedUser = $this->user()->getSavedUser();
39 |
40 | $retrievedUser = $this->getRepoUserById($savedUser->getId());
41 |
42 | self::assertTrue($savedUser->is($retrievedUser));
43 | }
44 |
45 | /**
46 | * @throws UserNotFoundException
47 | * @throws Exception
48 | */
49 | public function testBadUserIdThrowsUserNotFoundException(): void
50 | {
51 | /** @var Uuid $nonExistentId */
52 | $nonExistentId = Uuid::uuid4();
53 |
54 | $this->verifyExceptionWithMessage(
55 | UserNotFoundException::class,
56 | UserNotFoundException::MESSAGE
57 | );
58 | $this->getRepoUserById($nonExistentId);
59 | }
60 |
61 | /**
62 | * @throws DuplicateRoleAssignmentException
63 | * @throws UserNotFoundException
64 | */
65 | public function testAssignDriverRoleToUser(): void
66 | {
67 | $this->assertUserHasExpectedRole(AppRole::driver());
68 | }
69 |
70 | /**
71 | * @throws DuplicateRoleAssignmentException
72 | * @throws UserNotFoundException
73 | */
74 | public function testAssignPassengerRoleToUser(): void
75 | {
76 | $this->assertUserHasExpectedRole(AppRole::passenger());
77 | }
78 |
79 | /**
80 | * @throws DuplicateRoleAssignmentException
81 | * @throws UserNotFoundException
82 | */
83 | public function testUserCanHaveBothRoles(): void
84 | {
85 | $savedUser = $this->user()->getSavedUser();
86 |
87 | $this->assignRepoRoleToUser($savedUser, AppRole::driver());
88 | $this->assignRepoRoleToUser($savedUser, AppRole::passenger());
89 |
90 | $retrievedUser = $this->getRepoUserById($savedUser->getId());
91 |
92 | self::assertTrue($retrievedUser->userHasRole(AppRole::driver()));
93 | self::assertTrue($retrievedUser->userHasRole(AppRole::passenger()));
94 | }
95 |
96 | /**
97 | * @throws DuplicateRoleAssignmentException
98 | */
99 | public function testDuplicateRoleAssignmentThrows(): void
100 | {
101 | $savedUser = $this->user()->getSavedUser();
102 |
103 | $this->assignRepoRoleToUser($savedUser, AppRole::driver());
104 | $this->verifyExceptionWithMessage(
105 | DuplicateRoleAssignmentException::class,
106 | DuplicateRoleAssignmentException::MESSAGE
107 | );
108 |
109 | $this->assignRepoRoleToUser($savedUser, AppRole::driver());
110 | }
111 |
112 | /**
113 | * @param AppRole $role
114 | * @throws DuplicateRoleAssignmentException
115 | * @throws UserNotFoundException
116 | */
117 | private function assertUserHasExpectedRole(AppRole $role): void
118 | {
119 | $savedUser = $this->user()->getSavedUser();
120 |
121 | $this->assignRepoRoleToUser($savedUser, $role);
122 | $retrievedUser = $this->getRepoUserById($savedUser->getId());
123 |
124 | self::assertTrue($retrievedUser->userHasRole($role));
125 | }
126 |
127 | /**
128 | * @param AppUser $user
129 | * @param AppRole $role
130 | * @throws DuplicateRoleAssignmentException
131 | */
132 | protected function assignRepoRoleToUser(AppUser $user, AppRole $role): void
133 | {
134 | $this->user()->getRepo()->assignRoleToUser($user, $role);
135 | }
136 |
137 | /**
138 | * @param Uuid $userId
139 | * @return AppUser
140 | * @throws UserNotFoundException
141 | */
142 | private function getRepoUserById(Uuid $userId): AppUser
143 | {
144 | return $this->user()->getRepo()->getUserById($userId);
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/AppBundle/Entity/AppUser.php:
--------------------------------------------------------------------------------
1 | id = Uuid::uuid4();
82 | $this->firstName = $firstName;
83 | $this->lastName = $lastName;
84 | $this->email = $email;
85 | $this->username = $username;
86 | $this->setPlainPassword($password);
87 | $this->appRoles = new ArrayCollection();
88 | $this->created = new \DateTime(null, new \DateTimeZone('UTC'));
89 | }
90 |
91 | public function assignRole(AppRole $role): void
92 | {
93 | $this->appRoles->add($role);
94 | }
95 |
96 | public function userHasRole(AppRole $role): bool
97 | {
98 | $hasRoleCriteria =
99 | Criteria::create()->andWhere(
100 | Criteria::expr()->eq(
101 | 'id',
102 | $role->getId()
103 | )
104 | );
105 | return $this->appRoles->matching($hasRoleCriteria)->count() > 0;
106 | }
107 |
108 | public function isNamed(string $nameToCheck): bool
109 | {
110 | return $this->getFullName() === $nameToCheck;
111 | }
112 |
113 | public function is(AppUser $userToCompare): bool
114 | {
115 | return $this->getId()->equals($userToCompare->getId());
116 | }
117 |
118 | public function toDto(): UserDto
119 | {
120 | return new UserDto(
121 | $this->id->toString(),
122 | $this->userHasRole(AppRole::driver()),
123 | $this->userHasRole(AppRole::passenger()),
124 | $this->getFullName(),
125 | $this->getUsername(),
126 | $this->getEmail()
127 | );
128 | }
129 |
130 | /**
131 | * @return string
132 | */
133 | private function getFullName(): string
134 | {
135 | return trim($this->firstName . ' ' . $this->lastName);
136 | }
137 |
138 | public function getFirstName(): string
139 | {
140 | return $this->firstName;
141 | }
142 |
143 | public function getLastName(): string
144 | {
145 | return $this->lastName;
146 | }
147 |
148 | /**
149 | * @param string $firstName
150 | */
151 | public function setFirstName(string $firstName): void
152 | {
153 | $this->firstName = $firstName;
154 | }
155 |
156 | /**
157 | * @param string $lastName
158 | */
159 | public function setLastName(string $lastName): void
160 | {
161 | $this->lastName = $lastName;
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/app/Resources/views/default/index.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'base.html.twig' %}
2 |
3 | {% block body %}
4 |
5 |
6 |
7 |
Welcome to Symfony {{ constant('Symfony\\Component\\HttpKernel\\Kernel::VERSION') }}
8 |
9 |
10 |
11 |
12 |
13 |
14 | Your application is now ready. You can start working on it at:
15 | {{ base_dir }}
16 |
17 |
18 |
19 |
44 |
45 |
46 |
47 | {% endblock %}
48 |
49 | {% block stylesheets %}
50 |
76 | {% endblock %}
77 |
--------------------------------------------------------------------------------
/app/DoctrineMigrations/Version20180211230225.php:
--------------------------------------------------------------------------------
1 | abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
17 |
18 | $this->addSql('CREATE TABLE oauth2_access_tokens (id INT AUTO_INCREMENT NOT NULL, client_id INT NOT NULL, user_id CHAR(36) DEFAULT NULL COMMENT \'(DC2Type:uuid)\', token VARCHAR(255) NOT NULL, expires_at INT DEFAULT NULL, scope VARCHAR(255) DEFAULT NULL, UNIQUE INDEX UNIQ_D247A21B5F37A13B (token), INDEX IDX_D247A21B19EB6921 (client_id), INDEX IDX_D247A21BA76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
19 | $this->addSql('CREATE TABLE oauth2_auth_codes (id INT AUTO_INCREMENT NOT NULL, client_id INT NOT NULL, user_id CHAR(36) DEFAULT NULL COMMENT \'(DC2Type:uuid)\', token VARCHAR(255) NOT NULL, redirect_uri LONGTEXT NOT NULL, expires_at INT DEFAULT NULL, scope VARCHAR(255) DEFAULT NULL, UNIQUE INDEX UNIQ_A018A10D5F37A13B (token), INDEX IDX_A018A10D19EB6921 (client_id), INDEX IDX_A018A10DA76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
20 | $this->addSql('CREATE TABLE oauth2_clients (id INT AUTO_INCREMENT NOT NULL, random_id VARCHAR(255) NOT NULL, redirect_uris LONGTEXT NOT NULL COMMENT \'(DC2Type:array)\', secret VARCHAR(255) NOT NULL, allowed_grant_types LONGTEXT NOT NULL COMMENT \'(DC2Type:array)\', PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
21 | $this->addSql('CREATE TABLE oauth2_refresh_tokens (id INT AUTO_INCREMENT NOT NULL, client_id INT NOT NULL, user_id CHAR(36) DEFAULT NULL COMMENT \'(DC2Type:uuid)\', token VARCHAR(255) NOT NULL, expires_at INT DEFAULT NULL, scope VARCHAR(255) DEFAULT NULL, UNIQUE INDEX UNIQ_D394478C5F37A13B (token), INDEX IDX_D394478C19EB6921 (client_id), INDEX IDX_D394478CA76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
22 | $this->addSql('ALTER TABLE oauth2_access_tokens ADD CONSTRAINT FK_D247A21B19EB6921 FOREIGN KEY (client_id) REFERENCES oauth2_clients (id)');
23 | $this->addSql('ALTER TABLE oauth2_access_tokens ADD CONSTRAINT FK_D247A21BA76ED395 FOREIGN KEY (user_id) REFERENCES users (id)');
24 | $this->addSql('ALTER TABLE oauth2_auth_codes ADD CONSTRAINT FK_A018A10D19EB6921 FOREIGN KEY (client_id) REFERENCES oauth2_clients (id)');
25 | $this->addSql('ALTER TABLE oauth2_auth_codes ADD CONSTRAINT FK_A018A10DA76ED395 FOREIGN KEY (user_id) REFERENCES users (id)');
26 | $this->addSql('ALTER TABLE oauth2_refresh_tokens ADD CONSTRAINT FK_D394478C19EB6921 FOREIGN KEY (client_id) REFERENCES oauth2_clients (id)');
27 | $this->addSql('ALTER TABLE oauth2_refresh_tokens ADD CONSTRAINT FK_D394478CA76ED395 FOREIGN KEY (user_id) REFERENCES users (id)');
28 | $this->addSql('ALTER TABLE users ADD username VARCHAR(180) NOT NULL, ADD username_canonical VARCHAR(180) NOT NULL, ADD email VARCHAR(180) NOT NULL, ADD email_canonical VARCHAR(180) NOT NULL, ADD enabled TINYINT(1) NOT NULL, ADD salt VARCHAR(255) DEFAULT NULL, ADD password VARCHAR(255) NOT NULL, ADD last_login DATETIME DEFAULT NULL, ADD confirmation_token VARCHAR(180) DEFAULT NULL, ADD password_requested_at DATETIME DEFAULT NULL, ADD roles LONGTEXT NOT NULL COMMENT \'(DC2Type:array)\'');
29 | $this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E992FC23A8 ON users (username_canonical)');
30 | $this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E9A0D96FBF ON users (email_canonical)');
31 | $this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E9C05FB297 ON users (confirmation_token)');
32 | }
33 |
34 | public function down(Schema $schema)
35 | {
36 | // this down() migration is auto-generated, please modify it to your needs
37 | $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
38 |
39 | $this->addSql('ALTER TABLE oauth2_access_tokens DROP FOREIGN KEY FK_D247A21B19EB6921');
40 | $this->addSql('ALTER TABLE oauth2_auth_codes DROP FOREIGN KEY FK_A018A10D19EB6921');
41 | $this->addSql('ALTER TABLE oauth2_refresh_tokens DROP FOREIGN KEY FK_D394478C19EB6921');
42 | $this->addSql('DROP TABLE oauth2_access_tokens');
43 | $this->addSql('DROP TABLE oauth2_auth_codes');
44 | $this->addSql('DROP TABLE oauth2_clients');
45 | $this->addSql('DROP TABLE oauth2_refresh_tokens');
46 | $this->addSql('DROP INDEX UNIQ_1483A5E992FC23A8 ON users');
47 | $this->addSql('DROP INDEX UNIQ_1483A5E9A0D96FBF ON users');
48 | $this->addSql('DROP INDEX UNIQ_1483A5E9C05FB297 ON users');
49 | $this->addSql('ALTER TABLE users DROP username, DROP username_canonical, DROP email, DROP email_canonical, DROP enabled, DROP salt, DROP password, DROP last_login, DROP confirmation_token, DROP password_requested_at, DROP roles');
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/app/DoctrineMigrations/Version20180131035821.php:
--------------------------------------------------------------------------------
1 | abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
21 |
22 | $this->addSql('CREATE TABLE locations (id CHAR(36) NOT NULL COMMENT \'(DC2Type:guid)\', lat DOUBLE PRECISION NOT NULL, `longitude` DOUBLE PRECISION NOT NULL, created DATETIME NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
23 | $this->addSql('CREATE TABLE roles (id INT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
24 | $this->addSql('CREATE TABLE users (id CHAR(36) NOT NULL COMMENT \'(DC2Type:guid)\', first_name VARCHAR(255) NOT NULL, last_name VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
25 | $this->addSql('CREATE TABLE users_roles (userId CHAR(36) NOT NULL COMMENT \'(DC2Type:guid)\', roleId INT NOT NULL, INDEX IDX_51498A8E64B64DCC (userId), INDEX IDX_51498A8EB8C2FD88 (roleId), PRIMARY KEY(userId, roleId)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
26 | $this->addSql('CREATE TABLE rides (id CHAR(36) NOT NULL COMMENT \'(DC2Type:guid)\', passengerId CHAR(36) DEFAULT NULL COMMENT \'(DC2Type:guid)\', driverId CHAR(36) DEFAULT NULL COMMENT \'(DC2Type:guid)\', departureId CHAR(36) DEFAULT NULL COMMENT \'(DC2Type:guid)\', destinationId CHAR(36) DEFAULT NULL COMMENT \'(DC2Type:guid)\', INDEX IDX_9D4620A3B0FAA905 (passengerId), INDEX IDX_9D4620A323F411D5 (driverId), INDEX IDX_9D4620A36E9E2929 (departureId), INDEX IDX_9D4620A3BF3434FC (destinationId), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
27 | $this->addSql('CREATE TABLE rideEvents (id INT AUTO_INCREMENT NOT NULL, created DATETIME NOT NULL, rideId CHAR(36) DEFAULT NULL COMMENT \'(DC2Type:guid)\', userId CHAR(36) DEFAULT NULL COMMENT \'(DC2Type:guid)\', eventTypeId INT DEFAULT NULL, INDEX IDX_893034F0F23620C7 (rideId), INDEX IDX_893034F064B64DCC (userId), INDEX IDX_893034F0577BCC16 (eventTypeId), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
28 | $this->addSql('CREATE TABLE rideEventTypes (id INT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
29 | $this->addSql('ALTER TABLE users_roles ADD CONSTRAINT FK_51498A8E64B64DCC FOREIGN KEY (userId) REFERENCES users (id)');
30 | $this->addSql('ALTER TABLE users_roles ADD CONSTRAINT FK_51498A8EB8C2FD88 FOREIGN KEY (roleId) REFERENCES roles (id)');
31 | $this->addSql('ALTER TABLE rides ADD CONSTRAINT FK_9D4620A3B0FAA905 FOREIGN KEY (passengerId) REFERENCES users (id)');
32 | $this->addSql('ALTER TABLE rides ADD CONSTRAINT FK_9D4620A323F411D5 FOREIGN KEY (driverId) REFERENCES users (id)');
33 | $this->addSql('ALTER TABLE rides ADD CONSTRAINT FK_9D4620A36E9E2929 FOREIGN KEY (departureId) REFERENCES locations (id)');
34 | $this->addSql('ALTER TABLE rides ADD CONSTRAINT FK_9D4620A3BF3434FC FOREIGN KEY (destinationId) REFERENCES locations (id)');
35 | $this->addSql('ALTER TABLE rideEvents ADD CONSTRAINT FK_893034F0F23620C7 FOREIGN KEY (rideId) REFERENCES rides (id)');
36 | $this->addSql('ALTER TABLE rideEvents ADD CONSTRAINT FK_893034F064B64DCC FOREIGN KEY (userId) REFERENCES users (id)');
37 | $this->addSql('ALTER TABLE rideEvents ADD CONSTRAINT FK_893034F0577BCC16 FOREIGN KEY (eventTypeId) REFERENCES rideEventTypes (id)');
38 | }
39 |
40 | /**
41 | * @param Schema $schema
42 | * @throws \Doctrine\DBAL\Migrations\AbortMigrationException
43 | */
44 | public function down(Schema $schema)
45 | {
46 | // this down() migration is auto-generated, please modify it to your needs
47 | $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
48 |
49 | $this->addSql('ALTER TABLE rides DROP FOREIGN KEY FK_9D4620A36E9E2929');
50 | $this->addSql('ALTER TABLE rides DROP FOREIGN KEY FK_9D4620A3BF3434FC');
51 | $this->addSql('ALTER TABLE users_roles DROP FOREIGN KEY FK_51498A8EB8C2FD88');
52 | $this->addSql('ALTER TABLE users_roles DROP FOREIGN KEY FK_51498A8E64B64DCC');
53 | $this->addSql('ALTER TABLE rides DROP FOREIGN KEY FK_9D4620A3B0FAA905');
54 | $this->addSql('ALTER TABLE rides DROP FOREIGN KEY FK_9D4620A323F411D5');
55 | $this->addSql('ALTER TABLE rideEvents DROP FOREIGN KEY FK_893034F064B64DCC');
56 | $this->addSql('ALTER TABLE rideEvents DROP FOREIGN KEY FK_893034F0F23620C7');
57 | $this->addSql('ALTER TABLE rideEvents DROP FOREIGN KEY FK_893034F0577BCC16');
58 | $this->addSql('DROP TABLE locations');
59 | $this->addSql('DROP TABLE roles');
60 | $this->addSql('DROP TABLE users');
61 | $this->addSql('DROP TABLE users_roles');
62 | $this->addSql('DROP TABLE rides');
63 | $this->addSql('DROP TABLE rideEvents');
64 | $this->addSql('DROP TABLE rideEventTypes');
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/tests/AppBundle/Ride/RideEventRepositoryTest.php:
--------------------------------------------------------------------------------
1 | savedRide = $this->ride()->getRepoSavedRide();
27 | }
28 |
29 | public function testNonExistentRideThrowsException()
30 | {
31 | $nonExistentRide = new Ride(
32 | $this->user()->getSavedUser(),
33 | $this->location()->getSavedHomeLocation()
34 | );
35 | $this->verifyExceptionWithMessage(
36 | RideNotFoundException::class,
37 | RideNotFoundException::MESSAGE
38 | );
39 | $this->ride()->getRepoLastEvent($nonExistentRide);
40 | }
41 |
42 | /**
43 | * @throws DuplicateRoleAssignmentException
44 | * @throws UserNotFoundException
45 | * @throws UnauthorizedOperationException
46 | */
47 | public function testSaveNewRideEvent()
48 | {
49 | $rideEvent = $this->getSavedRequestedRideEvent();
50 |
51 | self::assertGreaterThan(0, $rideEvent->getId());
52 | }
53 |
54 | /**
55 | * @throws DuplicateRoleAssignmentException
56 | * @throws UserNotFoundException
57 | * @throws UnauthorizedOperationException
58 | */
59 | public function testRideIsCurrentlyRequested()
60 | {
61 | $this->getSavedRequestedRideEvent();
62 |
63 | $lastEventForRide = $this->ride()->getRepoLastEvent($this->savedRide);
64 |
65 | self::assertTrue($lastEventForRide->is(RideEventType::requested()));
66 | }
67 |
68 | /**
69 | * @throws DuplicateRoleAssignmentException
70 | * @throws UserNotFoundException
71 | * @throws UnauthorizedOperationException
72 | */
73 | public function testRideIsCurrentlyAccepted()
74 | {
75 | $this->assertLastEventIsOfType($this->ride()->accepted);
76 | }
77 |
78 | /**
79 | * @throws DuplicateRoleAssignmentException
80 | * @throws UserNotFoundException
81 | * @throws UnauthorizedOperationException
82 | */
83 | public function testRideIsCurrentlyInProgress()
84 | {
85 | $this->assertLastEventIsOfType($this->ride()->inProgress);
86 | }
87 |
88 | /**
89 | * @throws DuplicateRoleAssignmentException
90 | * @throws UserNotFoundException
91 | * @throws UnauthorizedOperationException
92 | */
93 | public function testRideIsCurrentlyCancelled()
94 | {
95 | $this->assertLastEventIsOfType($this->ride()->cancelled);
96 | }
97 |
98 | /**
99 | * @throws DuplicateRoleAssignmentException
100 | * @throws UserNotFoundException
101 | * @throws UnauthorizedOperationException
102 | */
103 | public function testRideIsCurrentlyCompleted()
104 | {
105 | $this->assertLastEventIsOfType($this->ride()->completed);
106 | }
107 |
108 | /**
109 | * @throws DuplicateRoleAssignmentException
110 | * @throws UserNotFoundException
111 | * @throws UnauthorizedOperationException
112 | */
113 | public function testRideIsCurrentlyRejected()
114 | {
115 | $this->assertLastEventIsOfType($this->ride()->rejected);
116 | }
117 |
118 | /**
119 | * @throws DuplicateRoleAssignmentException
120 | * @throws UserNotFoundException
121 | * @throws UnauthorizedOperationException
122 | */
123 | public function testMarkRideAsStatus()
124 | {
125 | $this->ride()->markRepoRide(
126 | $this->savedRide,
127 | $this->user()->getSavedPassenger(),
128 | RideEventType::requested()
129 | );
130 | $lastEventForRide = $this->ride()->getRepoLastEvent($this->savedRide);
131 |
132 | self::assertTrue($lastEventForRide->is(RideEventType::requested()));
133 | }
134 |
135 | /**
136 | * @return RideEvent
137 | * @throws DuplicateRoleAssignmentException
138 | * @throws UserNotFoundException
139 | * @throws UnauthorizedOperationException
140 | */
141 | private function getSavedRequestedRideEvent()
142 | {
143 | return $this->ride()->markRepoRide(
144 | $this->savedRide,
145 | $this->user()->getSavedPassenger(),
146 | $this->ride()->requested
147 | );
148 | }
149 |
150 | /**
151 | * @param RideEventType $eventTypeToAssert
152 | * @return mixed
153 | * @throws DuplicateRoleAssignmentException
154 | * @throws UserNotFoundException
155 | * @throws UnauthorizedOperationException
156 | */
157 | private function assertLastEventIsOfType(RideEventType $eventTypeToAssert)
158 | {
159 | $this->getSavedRequestedRideEvent();
160 |
161 | $this->save(new RideEvent(
162 | $this->savedRide,
163 | $this->user()->getSavedUserWithName('Jamie', 'Isaacs'),
164 | $eventTypeToAssert
165 | ));
166 |
167 | $lastEventForRide = $this->ride()->getRepoLastEvent($this->savedRide);
168 |
169 | self::assertFalse($lastEventForRide->is(RideEventType::requested()));
170 | self::assertTrue($lastEventForRide->is($eventTypeToAssert));
171 |
172 | return $lastEventForRide;
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/tests/AppBundle/Production/UserApi.php:
--------------------------------------------------------------------------------
1 | userRepository = new UserRepository(
37 | $entityManager,
38 | $userManager
39 | );
40 | $this->userService = new UserService(
41 | $this->userRepository
42 | );
43 | $this->entityManager = $entityManager;
44 | }
45 |
46 | const CLIENT_ID = '1_3bcbxd9e24g0gk4swg0kwgcwg4o8k8g4g888kwc44gcc0gwwk4';
47 | const CLIENT_SECRET = '4ok2x70rlfokc8g0wws8c8kwcokw80k44sg48goc0ok4w0so0k';
48 |
49 | /**
50 | * @return UserRepositoryInterface
51 | */
52 | public function getRepo()
53 | {
54 | return $this->userRepository;
55 | }
56 |
57 | /**
58 | * @param $userId
59 | * @return AppUser
60 | * @throws UserNotFoundException
61 | * @throws UnauthorizedOperationException
62 | */
63 | public function getServiceUserById($userId)
64 | {
65 | $this->authById($userId);
66 | return $this->userService->getUserById($userId);
67 | }
68 |
69 | public function getSavedUserWithName($first, $last)
70 | {
71 | $fakeUser = new FakeUser($first, $last);
72 | return $this->userService->newUser(
73 | $first,
74 | $last,
75 | $fakeUser->email,
76 | $fakeUser->username,
77 | $fakeUser->password
78 | );
79 | }
80 |
81 | /**
82 | * @return AppUser
83 | */
84 | public function getSavedUser()
85 | {
86 | return $this->getSavedUserWithName('chris', 'holland');
87 | }
88 |
89 | /**
90 | * @return AppUser
91 | * @throws DuplicateRoleAssignmentException
92 | * @throws UnauthorizedOperationException
93 | */
94 | public function getNewPassenger()
95 | {
96 | $passenger = $this->getSavedUser();
97 | $this->makeUserPassenger($this->getSavedUser());
98 | return $passenger;
99 | }
100 |
101 | /**
102 | * @param AppUser $user
103 | * @throws DuplicateRoleAssignmentException
104 | * @throws UnauthorizedOperationException
105 | */
106 | public function makeUserPassenger(AppUser $user)
107 | {
108 | $this->auth($user);
109 | $this->userService->makeUserPassenger($user);
110 | }
111 |
112 | /**
113 | * @return AppUser
114 | * @throws DuplicateRoleAssignmentException
115 | * @throws UserNotFoundException
116 | * @throws UnauthorizedOperationException
117 | */
118 | public function getSavedPassenger(): AppUser
119 | {
120 | $ridePassenger = $this->getNewPassenger();
121 | $savedPassenger = $this->getServiceUserById($ridePassenger->getId());
122 |
123 | return $savedPassenger;
124 | }
125 |
126 | /**
127 | * @param AppUser $driver
128 | * @throws DuplicateRoleAssignmentException
129 | * @throws UnauthorizedOperationException
130 | */
131 | public function makeUserDriver(AppUser $driver)
132 | {
133 | $this->auth($driver);
134 | $this->userService->makeUserDriver($driver);
135 | }
136 |
137 | /**
138 | * @return AppUser
139 | * @throws DuplicateRoleAssignmentException
140 | * @throws UnauthorizedOperationException
141 | */
142 | public function getNewDriver()
143 | {
144 | return $this->getNewDriverWithName('new', 'driver');
145 | }
146 |
147 | /**
148 | * @param $first
149 | * @param $last
150 | * @return AppUser
151 | * @throws DuplicateRoleAssignmentException
152 | * @throws UnauthorizedOperationException
153 | */
154 | public function getNewDriverWithName($first, $last)
155 | {
156 | $driver = $this->getSavedUserWithName($first, $last);
157 | $this->makeUserDriver($driver);
158 | return $driver;
159 | }
160 |
161 | private function saveRole(AppRole $role)
162 | {
163 | $this->entityManager->persist($role);
164 | $this->entityManager->flush();
165 | }
166 |
167 | public function bootStrapRoles()
168 | {
169 | $this->saveRole(AppRole::driver());
170 | $this->saveRole(AppRole::passenger());
171 | }
172 |
173 | /**
174 | * @return UserService
175 | */
176 | public function getService() : UserService
177 | {
178 | return $this->userService;
179 | }
180 |
181 | public function setAuthenticatedUser(AppUser $user)
182 | {
183 | $this->authUserIsSet = true;
184 | $this->userService->setAuthenticatedUser($user);
185 | }
186 |
187 | private function auth(AppUser $user)
188 | {
189 | if (! $this->authUserIsSet) {
190 | $this->userService->setAuthenticatedUser($user);
191 | }
192 | }
193 |
194 | /**
195 | * @param $userId
196 | * @throws UserNotFoundException
197 | */
198 | public function authById($userId): void
199 | {
200 | if (! $this->authUserIsSet) {
201 | $this->userService->setAuthenticatedUser(
202 | $this->userRepository->getUserById($userId)
203 | );
204 | }
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/.idea/php.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
--------------------------------------------------------------------------------
/src/AppBundle/Service/RideService.php:
--------------------------------------------------------------------------------
1 | rideRepository = $rideRepository;
40 | $this->rideEventRepository = $rideEventRepository;
41 | }
42 |
43 | /**
44 | * @param AppUser $passenger
45 | * @param AppLocation $departure
46 | * @return Ride
47 | * @throws UserNotInPassengerRoleException
48 | */
49 | public function newRide(AppUser $passenger, AppLocation $departure): Ride
50 | {
51 | $this->validateUserHasPassengerRole($passenger);
52 |
53 | $newRide = new Ride($passenger, $departure);
54 | $this->rideRepository->saveRide($newRide);
55 |
56 | $this->rideEventRepository->markRideStatusByPassenger(
57 | $newRide,
58 | RideEventType::requested()
59 | );
60 |
61 | return $newRide;
62 | }
63 |
64 | /**
65 | * @param Uuid $id
66 | * @return Ride
67 | * @throws RideNotFoundException
68 | */
69 | public function getRide(Uuid $id): Ride
70 | {
71 | return $this->rideRepository->getRideById($id);
72 | }
73 |
74 | public function assignDestinationToRide(Ride $ride, AppLocation $destination)
75 | {
76 | $this->rideRepository->assignDestinationToRide(
77 | $ride,
78 | $destination
79 | );
80 | return $ride;
81 | }
82 |
83 | /**
84 | * @param Ride $ride
85 | * @return RideEventType
86 | * @throws RideNotFoundException
87 | */
88 | public function getRideStatus(Ride $ride): RideEventType
89 | {
90 | return
91 | $this->rideEventRepository
92 | ->getLastEventForRide($ride)
93 | ->getStatus();
94 | }
95 |
96 | /**
97 | * @param Ride $requestedRide
98 | * @param AppUser $driver
99 | * @return Ride
100 | * @throws RideLifeCycleException
101 | * @throws RideNotFoundException
102 | * @throws UserNotInDriverRoleException
103 | */
104 | public function acceptRide(Ride $requestedRide, AppUser $driver): Ride
105 | {
106 | $this->validateRideIsRequested($requestedRide);
107 |
108 | $this->validateUserHasDriverRole($driver);
109 | $this->markRide(
110 | $requestedRide,
111 | $driver,
112 | RideEventType::accepted()
113 | );
114 |
115 | $this->rideRepository->assignDriverToRide(
116 | $requestedRide,
117 | $driver
118 | );
119 |
120 | return $requestedRide;
121 | }
122 |
123 | /**
124 | * @param Ride $acceptedRide
125 | * @param AppUser $driver
126 | * @return Ride
127 | * @throws RideLifeCycleException
128 | * @throws UserNotInDriverRoleException
129 | * @throws RideNotFoundException
130 | * @throws ActingDriverIsNotAssignedDriverException
131 | */
132 | public function markRideInProgress(Ride $acceptedRide, AppUser $driver): Ride
133 | {
134 | $this->validateRideIsAccepted($acceptedRide);
135 |
136 | $this->transitionToStatusByAssignedDriver(
137 | $acceptedRide,
138 | $driver,
139 | RideEventType::inProgress()
140 | );
141 |
142 | return $acceptedRide;
143 | }
144 |
145 | /**
146 | * @param Ride $rideInProgress
147 | * @param AppUser $driver
148 | * @return Ride
149 | * @throws ActingDriverIsNotAssignedDriverException
150 | * @throws RideLifeCycleException
151 | * @throws RideNotFoundException
152 | * @throws UserNotInDriverRoleException
153 | */
154 | public function markRideCompleted(Ride $rideInProgress, AppUser $driver): Ride
155 | {
156 | $this->validateRideIsInProgress($rideInProgress);
157 |
158 | $this->transitionToStatusByAssignedDriver(
159 | $rideInProgress,
160 | $driver,
161 | RideEventType::completed()
162 | );
163 | return $rideInProgress;
164 | }
165 |
166 | /**
167 | * @param AppUser $passenger
168 | * @throws UserNotInPassengerRoleException
169 | */
170 | private function validateUserHasPassengerRole(AppUser $passenger): void
171 | {
172 | if (!$passenger->userHasRole(AppRole::passenger())) {
173 | throw new UserNotInPassengerRoleException();
174 | }
175 | }
176 |
177 | /**
178 | * @param AppUser $driver
179 | * @throws UserNotInDriverRoleException
180 | */
181 | private function validateUserHasDriverRole(AppUser $driver): void
182 | {
183 | if (!$driver->userHasRole(AppRole::driver())) {
184 | throw new UserNotInDriverRoleException();
185 | }
186 | }
187 |
188 | /**
189 | * @param Ride $ride
190 | * @throws RideLifeCycleException
191 | * @throws RideNotFoundException
192 | */
193 | private function validateRideIsRequested(Ride $ride): void
194 | {
195 | if (!RideEventType::requested()->equals(
196 | $this->getRideStatus($ride)
197 | )) {
198 | throw new RideLifeCycleException();
199 | }
200 | }
201 |
202 | /**
203 | * @param Ride $acceptedRide
204 | * @throws RideLifeCycleException
205 | * @throws RideNotFoundException
206 | */
207 | private function validateRideIsAccepted(Ride $acceptedRide): void
208 | {
209 | if (!RideEventType::accepted()->equals(
210 | $this->getRideStatus($acceptedRide)
211 | )) {
212 | throw new RideLifeCycleException();
213 | }
214 | }
215 |
216 | /**
217 | * @param Ride $acceptedRide
218 | * @param AppUser $driver
219 | * @throws ActingDriverIsNotAssignedDriverException
220 | */
221 | private function validateAttemptingDriverIsAssignedDriver(Ride $acceptedRide, AppUser $driver): void
222 | {
223 | if (!$acceptedRide->isDrivenBy($driver)) {
224 | throw new ActingDriverIsNotAssignedDriverException();
225 | }
226 | }
227 |
228 | /**
229 | * @param Ride $rideInProgress
230 | * @throws RideLifeCycleException
231 | * @throws RideNotFoundException
232 | */
233 | private function validateRideIsInProgress(Ride $rideInProgress): void
234 | {
235 | if (!RideEventType::inProgress()->equals($this->getRideStatus($rideInProgress))) {
236 | throw new RideLifeCycleException();
237 | }
238 | }
239 |
240 | private function markRide(Ride $ride, AppUser $driver, RideEventType $status): void
241 | {
242 | $this->rideEventRepository->markRideStatusByActor(
243 | $ride,
244 | $driver,
245 | $status
246 | );
247 | }
248 |
249 | /**
250 | * @param Ride $ride
251 | * @param AppUser $driver
252 | * @param $statusToTransition
253 | * @throws UserNotInDriverRoleException
254 | * @throws ActingDriverIsNotAssignedDriverException
255 | */
256 | private function transitionToStatusByAssignedDriver(Ride $ride, AppUser $driver, $statusToTransition): void
257 | {
258 | $this->validateUserHasDriverRole($driver);
259 | $this->validateAttemptingDriverIsAssignedDriver($ride, $driver);
260 | $this->markRide(
261 | $ride,
262 | $driver,
263 | $statusToTransition
264 | );
265 | }
266 | }
267 |
--------------------------------------------------------------------------------
/.idea/kata_tdd_php_symfony.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
--------------------------------------------------------------------------------